@linid-dm/directory-manager-client-core
Version:
Core package by providing a set of angular components for the Directory Manager app.
591 lines • 147 kB
JavaScript
/**
* Copyright (C) 2020-2024 Linagora
*
* This program is free software: you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License as published by the Free
* Software Foundation, either version 3 of the License, or (at your option) any
* later version, provided you comply with the Additional Terms applicable for
* LinID Directory Manager software by LINAGORA pursuant to Section 7 of the GNU
* Affero General Public License, subsections (b), (c), and (e), pursuant to
* which these Appropriate Legal Notices must notably (i) retain the display of
* the "LinID™" trademark/logo at the top of the interface window, the display
* of the “You are using the Open Source and free version of LinID™, powered by
* Linagora © 2009–2013. Contribute to LinID R&D by subscribing to an Enterprise
* offer!” infobox and in the e-mails sent with the Program, notice appended to
* any type of outbound messages (e.g. e-mail and meeting requests) as well as
* in the LinID Directory Manager user interface, (ii) retain all hypertext
* links between LinID Directory Manager and https://linid.org/, as well as
* between LINAGORA and LINAGORA.com, and (iii) refrain from infringing LINAGORA
* intellectual property rights over its trademarks and commercial brands. Other
* Additional Terms apply, see <http://www.linagora.com/licenses/> for more
* details.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
* details.
*
* You should have received a copy of the GNU Affero General Public License and
* its applicable Additional Terms for LinID Directory Manager along with this
* program. If not, see <http://www.gnu.org/licenses/> for the GNU Affero
* General Public License version 3 and <http://www.linagora.com/licenses/> for
* the Additional Terms applicable to the LinID Directory Manager software.
*/
import { trigger } from '@angular/animations';
import { SelectionModel } from '@angular/cdk/collections';
import { CdkVirtualScrollViewport } from '@angular/cdk/scrolling';
import { FlatTreeControl } from '@angular/cdk/tree';
import { Component, EventEmitter, Input, Output, ViewChild, } from '@angular/core';
import { MatTreeFlatDataSource, MatTreeFlattener, } from '@angular/material/tree';
import _ from 'lodash';
import { Subject, delay, filter, takeUntil, tap } from 'rxjs';
import { Data, ETreeSelectMode, EUserAction, addNodeInDataTree, cloneDataTree, fadeTree, filterTree, getAllChildrenLength, getFirstDisplayedIndex, getFlattenDisplayedTreeIds, getFlattenTree, getGreatestNodesMinWidth, getNodeById, getSelectedNodes, isNodeMatchingSearch, setNodeIdWidthMap, updateNodeName, updateSubTreelinks, } from '../../shared';
import * as i0 from "@angular/core";
import * as i1 from "@ngxs/store";
import * as i2 from "@angular/common";
import * as i3 from "@angular/router";
import * as i4 from "@angular/flex-layout/flex";
import * as i5 from "@angular/flex-layout/extended";
import * as i6 from "@angular/cdk/scrolling";
import * as i7 from "@angular/material/button";
import * as i8 from "@angular/material/checkbox";
import * as i9 from "@angular/material/icon";
import * as i10 from "@angular/material/tree";
import * as i11 from "../../shared/directives/color-search-letters.directive";
export class GenericTreeComponent {
set initialDataTree(initialDataTree) {
if (initialDataTree) {
this._initialDataTree = initialDataTree;
this.updateDataSourceOnInitAndOnSearch();
}
}
get initialDataTree() {
return this._initialDataTree;
}
set search(search) {
this._search = search;
if (!this.isSettingData) {
this.updateDataSourceOnInitAndOnSearch();
setTimeout(() => {
this.enableAnimation = false;
this.addedChildrenIds = [];
}, 1);
}
}
get search() {
return this._search;
}
set selectedResourceRootTypeId(selectedResourceRootTypeId) {
if (selectedResourceRootTypeId) {
this.isSettingData = true;
this.dataSource.data = [];
this.flattenDataTreeIds = [];
this.nodesIdWidth = new Map();
this.nodesMinWidthAsNumber = 0;
this._selectedResourceRootTypeId = selectedResourceRootTypeId;
}
}
get selectedResourceRootTypeId() {
return this._selectedResourceRootTypeId;
}
set refresh(refresh) {
if (refresh) {
this.isSettingData = true;
this._refresh = refresh;
refresh
.pipe(takeUntil(this._onDestroy$), filter((_) => !this.isSettingData), delay(10), tap((refreshActionDetail) => {
if (!!refreshActionDetail) {
this.updateDataTreeOnRefresh(refreshActionDetail);
this.updateFlattenTreePropertiesOnRefresh(refreshActionDetail);
this.updateDataSource();
}
}), delay(1), tap((refreshActionDetail) => {
if (refreshActionDetail.actionType === EUserAction.TREE_ADD ||
(refreshActionDetail.actionType === EUserAction.TREE_DELETE &&
refreshActionDetail.hasRemovedSelectedNode) ||
(refreshActionDetail.actionType === EUserAction.TREE_MOVE_NODE &&
refreshActionDetail.hasMovedSelectedNode)) {
this.scrollToSelectedNode();
}
}))
.subscribe();
}
}
get refresh() {
return this._refresh;
}
set advancedSearch(_advancedSearch) {
if (!this.isSettingData && this.isAdvancedSearchActivated) {
this.updateDataSourceOnInitAndOnSearch();
setTimeout(() => {
this.enableAnimation = false;
this.addedChildrenIds = [];
}, 1);
}
}
set isLargeScreen(isLargeScreen) {
this._isLargeScreen = isLargeScreen;
if (!this.isSettingData) {
this.viewportMinWidth = 'inherit';
setTimeout((_) => this.getVirtualScrollViewportMinWidth(), 1);
}
}
get isLargeScreen() {
return this._isLargeScreen;
}
constructor(_store$) {
this._store$ = _store$;
this.isSelectOnlyModeEnabled = false;
this.isAdvancedSearchActivated = false;
this.nodesIdsMatchingAdvancedSearch = {};
this.selectedNodesId = [];
this.isFilteringOnExternalId = false;
this.isScrollDisable = false;
this.widthUpdated = new EventEmitter();
this.selectedNode = new EventEmitter();
this.selectedNodes = new EventEmitter();
this._onDestroy$ = new Subject();
this._initialDataTree = [];
this._flattenDataTree = [];
this._mapDataNodeById = {};
this._hasMadeInitialScroll = false;
this.isSettingData = false;
this.viewportMinWidth = '100%';
this.STANDARD_NODE_PADDING_LEFT = 20;
this.STANDARD_NODE_HEIGHT = 40;
// Workaround to avoid scrollbar in mat-dialog-content
this.MAX_WINDOW_HEIGHT_PERCENTAGE = 0.5;
this.MAX_NB_DISPLAYED_NODES = 50;
this.nbDisplayedNodes = 0;
this.firstDisplayedIndex = 0;
this.enableAnimation = true;
this.addedChildrenIds = [];
this.nodesIdWidth = new Map();
this.nodesMinWidthAsNumber = 0;
this.allTreeSelectMode = ETreeSelectMode;
this.checklistSelection = new SelectionModel(true);
this._getLevel = (node) => node.level;
this._isExpandable = (node) => node.expandable;
this._getChildren = (node) => node.children;
this.hasChild = (_, node) => node.expandable;
this._transformer = (node, level) => {
return {
...node,
expandable: !!node.children && node.children.length > 0,
level,
};
};
this.treeControl = new FlatTreeControl(this._getLevel, this._isExpandable);
this.treeFlattener = new MatTreeFlattener(this._transformer, this._getLevel, this._isExpandable, this._getChildren);
this.dataSource = new MatTreeFlatDataSource(this.treeControl, this.treeFlattener);
}
ngAfterViewInit() {
this.virtualScroll.renderedRangeStream.subscribe((range) => {
if (this.firstDisplayedIndex !== range.start) {
this.firstDisplayedIndex = range.start;
this.updateDataSource();
}
});
}
ngOnDestroy() {
this._onDestroy$.next();
this._onDestroy$.complete();
}
expand(selectedNodesIds) {
const mapFlatDataNodeById = _.fromPairs(this.treeControl.dataNodes.map((node) => [node.ids.id, node]));
selectedNodesIds.forEach((id) => {
if (!!mapFlatDataNodeById[id]) {
this.treeControl.expand(mapFlatDataNodeById[id]);
}
});
}
getNodesForDeleteActions(node) {
let actionsKeys = Object.keys(this.actions[node.type].primitives);
if (node.children.length === 0 ||
(!actionsKeys.includes('deleteChild') &&
!actionsKeys.includes('deleteChildren'))) {
return [{ ...node.ids, label: node.name }];
}
else {
return node.children.map((child) => ({
...child.ids,
label: child.name,
}));
}
}
getNbChildren(children) {
return children.length > 1
? children.length
: children.some((child) => child.children.length > 0)
? 2
: children.length;
}
getLink(node) {
return node.link.toLowerCase();
}
getActiveNode(node) {
this.activeNode = node;
this.selectedNode.emit(this.activeNode);
}
onToggleFold(toggledNode) {
this.enableAnimation = true;
this.updateFlattenTreePropertiesOnToggle(toggledNode);
const isCollapsing = this.treeControl.isExpanded(toggledNode);
this.updateDataSourceOnToggle(isCollapsing);
if (isCollapsing) {
this.treeControl.collapse(toggledNode);
}
this.expand([...this.expandedNodesIds]);
if (this.isSelectOnlyModeEnabled) {
this._setVirtualScrollViewportHeight();
}
else {
this._store$.dispatch(new Data.ToggleFoldTreeNode({
treeNodeId: toggledNode.ids.id,
isFolding: !this.treeControl.isExpanded(toggledNode),
dataTypeId: this.selectedResourceRootTypeId,
}));
}
this.getVirtualScrollViewportMinWidth();
this.enableAnimation = false;
this.addedChildrenIds = [];
}
setEntryIdsInStore(ids, parentIds) {
this._store$.dispatch([
new Data.SetSelectedEntryIds({ ids }),
new Data.SetSelectedEntryParentIds({ ids: parentIds }),
]);
}
buildDisplayedTree(nodesByParentId, parentIdForDisplay = 'root') {
return nodesByParentId[parentIdForDisplay].map((node) => {
let children = node.children;
if (this.expandedNodesIds.has(node.ids.id) &&
!!nodesByParentId[node.ids.id]) {
const displayedChildren = this.buildDisplayedTree(nodesByParentId, node.ids.id);
children = displayedChildren.length > 0 ? displayedChildren : children;
}
return {
...node,
children,
};
});
}
setExpandedNodesIds() {
if (!!this.expandedTreeNodesUuids) {
this.expandedNodesIds = new Set(this.expandedTreeNodesUuids);
}
if (!!this.selectedNodeId) {
getSelectedNodes(this.selectedNodeId, this.initialDataTree).forEach((id) => this.expandedNodesIds.add(id));
}
}
updateFlattenTreePropertiesOnToggle(node) {
const updateStartIndex = this.flattenDataTreeIds.indexOf(node.ids.id) + 1;
if (this.treeControl.isExpanded(node)) {
const allChidrenLength = getAllChildrenLength(getNodeById(node.ids.id, this.dataTree), [...this.expandedNodesIds]);
this.flattenDataTreeIds.splice(updateStartIndex, allChidrenLength);
this.expandedNodesIds.delete(node.ids.id);
}
else {
this.addedChildrenIds = getFlattenDisplayedTreeIds(getNodeById(node.ids.id, this.dataTree).children, this.expandedNodesIds);
this.flattenDataTreeIds.splice(updateStartIndex, 0, ...this.addedChildrenIds);
this.nbDisplayedNodes =
this.flattenDataTreeIds.length - this.firstDisplayedIndex >
this.MAX_NB_DISPLAYED_NODES
? this.MAX_NB_DISPLAYED_NODES
: this.flattenDataTreeIds.length - this.firstDisplayedIndex;
this.expandedNodesIds.add(node.ids.id);
}
this.flattenDataTreeIds = [...this.flattenDataTreeIds];
}
updateDataTreeOnSetAndOnSearch() {
if (!this.isSelectOnlyModeEnabled &&
(this.search !== '' || this.isAdvancedSearchActivated)) {
this.filteredDataTree = filterTree(this.initialDataTree, this.allNodesIdsNames, this.search, this.isFilteringOnExternalId, this.isAdvancedSearchActivated, this.nodesIdsMatchingAdvancedSearch);
this.dataTree = cloneDataTree(this.filteredDataTree);
}
else {
this.dataTree = cloneDataTree(this.initialDataTree);
}
}
updateFlattenTreePropertiesOnSetAndOnSearch() {
if (!this.isSelectOnlyModeEnabled &&
(this.search !== '' || this.isAdvancedSearchActivated)) {
this.expandedNodesIds = new Set(this.allNodesIdsNames.map((nodeIds) => nodeIds.ids.id));
}
else {
this.expandedNodesIds = new Set();
if (!this.isSelectOnlyModeEnabled) {
this.setExpandedNodesIds();
}
}
this._flattenDataTree =
this.search !== '' || this.isAdvancedSearchActivated
? getFlattenTree(this.dataTree)
: this.flattenInitialDataTree;
this._mapDataNodeById = _.fromPairs(this._flattenDataTree.map((node) => [node.ids.id, node]));
this.flattenDataTreeIds = getFlattenDisplayedTreeIds(this.dataTree, this.expandedNodesIds);
}
updateDataTreeOnRefresh(refreshActionDetail) {
if (this.search !== '' || this.isAdvancedSearchActivated) {
switch (refreshActionDetail.actionType) {
case EUserAction.TREE_ADD:
if (isNodeMatchingSearch(this.search, refreshActionDetail.addedNode.name, this.isFilteringOnExternalId, refreshActionDetail.addedNode.ids, this.isAdvancedSearchActivated, this.nodesIdsMatchingAdvancedSearch)) {
addNodeInDataTree(this.filteredDataTree, refreshActionDetail.triggerNodeId, refreshActionDetail.addedNode);
}
break;
case EUserAction.TREE_DELETE:
this.filteredDataTree = filterTree(this.initialDataTree, this.allNodesIdsNames, this.search, this.isFilteringOnExternalId, this.isAdvancedSearchActivated, this.nodesIdsMatchingAdvancedSearch);
break;
case EUserAction.TREE_UPDATE:
const selectedNodeIds = this.allNodesIdsNames.find((nodeIds) => nodeIds.ids.id === this.selectedNodeId);
const isUpdatedNodeMatchingSearch = isNodeMatchingSearch(this.search, refreshActionDetail.newNodeName ?? selectedNodeIds.name, this.isFilteringOnExternalId, {
id: refreshActionDetail.triggerNodeId,
externalId: refreshActionDetail.newNodeExternalId,
} ?? selectedNodeIds.ids, this.isAdvancedSearchActivated, this.nodesIdsMatchingAdvancedSearch);
if (refreshActionDetail.newNodeName &&
isUpdatedNodeMatchingSearch &&
this.flattenDataTreeIds.includes(refreshActionDetail.triggerNodeId)) {
updateNodeName(refreshActionDetail.triggerNodeId, this.filteredDataTree, refreshActionDetail.newNodeName);
}
else if (refreshActionDetail.newNodeExternalId &&
isUpdatedNodeMatchingSearch &&
this.flattenDataTreeIds.includes(refreshActionDetail.triggerNodeId)) {
updateSubTreelinks(refreshActionDetail.triggerNodeId, this.filteredDataTree, refreshActionDetail.newNodeExternalId);
}
else {
this.filteredDataTree = filterTree(this.initialDataTree, this.allNodesIdsNames, this.search, this.isFilteringOnExternalId, this.isAdvancedSearchActivated, this.nodesIdsMatchingAdvancedSearch);
}
break;
case EUserAction.TREE_MOVE_NODE:
this.filteredDataTree = filterTree(this.initialDataTree, this.allNodesIdsNames, this.search, this.isFilteringOnExternalId, this.isAdvancedSearchActivated, this.nodesIdsMatchingAdvancedSearch);
break;
default:
break;
}
}
this.dataTree = cloneDataTree(this.search === '' && !this.isAdvancedSearchActivated
? this.initialDataTree
: this.filteredDataTree);
}
updateFlattenTreePropertiesOnRefresh(refreshActionDetail) {
let flattenDataTreeIdsBeforeUpdate = [];
switch (refreshActionDetail.actionType) {
case EUserAction.TREE_ADD:
if (isNodeMatchingSearch(this.search, refreshActionDetail.addedNode.name, this.isFilteringOnExternalId, refreshActionDetail.addedNode.ids, this.isAdvancedSearchActivated, this.nodesIdsMatchingAdvancedSearch)) {
if (this.search !== '' || this.isAdvancedSearchActivated) {
this.expandedNodesIds.add(refreshActionDetail.addedNode.ids.id);
}
this.expandedNodesIds.add(refreshActionDetail.addedNode.parentIds.id);
this.enableAnimation = true;
this.addedChildrenIds.push(refreshActionDetail.addedNode.ids.id);
}
break;
case EUserAction.TREE_DELETE:
this.expandedNodesIds.forEach((nodeId) => {
if (!this.allNodesIdsNames
.map((nodeIds) => nodeIds.ids.id)
.includes(nodeId)) {
this.expandedNodesIds.delete(nodeId);
}
});
break;
case EUserAction.TREE_UPDATE:
const selectedNodeIds = this.allNodesIdsNames.find((nodeIds) => nodeIds.ids.id === this.selectedNodeId);
const isUpdatedNodeMatchingSearch = isNodeMatchingSearch(this.search, refreshActionDetail.newNodeName ?? selectedNodeIds.name, this.isFilteringOnExternalId, {
id: refreshActionDetail.triggerNodeId,
externalId: refreshActionDetail.newNodeExternalId,
} ?? selectedNodeIds.ids, this.isAdvancedSearchActivated, this.nodesIdsMatchingAdvancedSearch);
if (isUpdatedNodeMatchingSearch &&
!this.flattenDataTreeIds.includes(refreshActionDetail.triggerNodeId)) {
flattenDataTreeIdsBeforeUpdate = [...this.flattenDataTreeIds];
}
break;
case EUserAction.TREE_MOVE_NODE:
const movedNode = getNodeById(refreshActionDetail.triggerNodeId, this.dataTree);
const allMovedNodesIds = [
movedNode.ids.id,
...getFlattenDisplayedTreeIds(movedNode.children, this.expandedNodesIds),
];
if (this.search !== '' ||
this.isAdvancedSearchActivated ||
refreshActionDetail.destinationNode === null ||
(this.expandedNodesIds.has(refreshActionDetail.destinationNode.ids.id) &&
this.flattenDataTreeIds.includes(refreshActionDetail.destinationNode.ids.id))) {
flattenDataTreeIdsBeforeUpdate = [...this.flattenDataTreeIds];
this.addedChildrenIds.push(...allMovedNodesIds);
}
break;
default:
break;
}
this._flattenDataTree =
this.search !== '' || this.isAdvancedSearchActivated
? getFlattenTree(this.dataTree)
: this.flattenInitialDataTree;
this._mapDataNodeById = _.fromPairs(this._flattenDataTree.map((node) => [node.ids.id, node]));
this.flattenDataTreeIds = getFlattenDisplayedTreeIds(this.dataTree, this.expandedNodesIds);
if (flattenDataTreeIdsBeforeUpdate.length > 0) {
this.enableAnimation = true;
this.addedChildrenIds.push(..._.difference(this.flattenDataTreeIds, flattenDataTreeIdsBeforeUpdate));
}
}
getDisplayedNodes() {
this.nbDisplayedNodes = 0;
const displayedNodesIds = this.flattenDataTreeIds.slice(this.firstDisplayedIndex, this.firstDisplayedIndex + this.MAX_NB_DISPLAYED_NODES);
this.nbDisplayedNodes = displayedNodesIds.length;
return displayedNodesIds.map((id) => ({
...this._mapDataNodeById[id],
parentIdForDisplay: displayedNodesIds.includes(this._mapDataNodeById[id].parentIdForDisplay)
? this._mapDataNodeById[id].parentIdForDisplay
: 'root',
}));
}
_getTreeForDataSource() {
const displayedNodes = this.getDisplayedNodes();
const nodesByParentIdForDisplay = _.groupBy(displayedNodes, 'parentIdForDisplay');
return Object.keys(nodesByParentIdForDisplay).length > 0
? this.buildDisplayedTree(nodesByParentIdForDisplay)
: [];
}
// TODO updateDataSource is call too many times on init tree
updateDataSource() {
this.setDataSource();
// TODO maybe find a solution for this
setTimeout(() => {
if (this.isSelectOnlyModeEnabled) {
this._setVirtualScrollViewportHeight();
}
this.getVirtualScrollViewportMinWidth();
}, 1);
}
setDataSource() {
if (this.isSettingData && !this.isSelectOnlyModeEnabled) {
this.firstDisplayedIndex = getFirstDisplayedIndex(this.flattenDataTreeIds, this.selectedNodeId, this.MAX_NB_DISPLAYED_NODES);
setTimeout(() => {
if (this.firstDisplayedIndex > 0 && !this._hasMadeInitialScroll) {
this.scrollToSelectedNode();
this._hasMadeInitialScroll = true;
}
else if (this.firstDisplayedIndex === 0 ||
this._hasMadeInitialScroll) {
this.dataSource.data = this._getTreeForDataSource();
this.expand([...this.expandedNodesIds]);
if (!this._hasMadeInitialScroll) {
this.scrollToSelectedNode();
}
this._hasMadeInitialScroll = false;
this.enableAnimation = false;
this.addedChildrenIds = [];
this.isSettingData = false;
}
}, 1);
}
else {
this.dataSource.data = this._getTreeForDataSource();
this.checklistSelection = new SelectionModel(true, this.treeControl.dataNodes.filter((node) => this.selectedNodesId.includes(node.ids.id)));
this.expand([...this.expandedNodesIds]);
this.enableAnimation = false;
this.addedChildrenIds = [];
}
}
updateDataSourceOnToggle(isCollapsing) {
if (isCollapsing) {
this.treeControl.dataNodes.push(..._.differenceBy(this.treeFlattener.flattenNodes(this._getTreeForDataSource()), this.treeControl.dataNodes, (node) => node.ids.id));
}
else {
const lastDisplayedNodeIndex = this.firstDisplayedIndex + this.nbDisplayedNodes - 1;
const mapFlatDataNodeIndexById = _.fromPairs(this.treeControl.dataNodes.map((node, index) => [
node.ids.id,
{ node, index },
]));
this.treeControl.dataNodes.splice(mapFlatDataNodeIndexById[this.flattenDataTreeIds[lastDisplayedNodeIndex]].index +
getAllChildrenLength(mapFlatDataNodeIndexById[this.flattenDataTreeIds[lastDisplayedNodeIndex]].node) +
1);
}
}
getVirtualScrollViewportMinWidth() {
setNodeIdWidthMap(this.nodesIdWidth, this.flattenDataTreeIds, this.isSelectOnlyModeEnabled);
this.nodesMinWidthAsNumber = getGreatestNodesMinWidth(this.nodesIdWidth, this.nodesMinWidthAsNumber);
let standardMinWidth = 0;
if (this.divMatTree) {
if (!this.isSelectOnlyModeEnabled) {
standardMinWidth = Number(getComputedStyle(this.divMatTree.nativeElement)
.getPropertyValue('min-width')
.replace('px', ''));
}
else {
standardMinWidth = this.divMatTree.nativeElement.offsetWidth;
}
}
if (this.nodesMinWidthAsNumber < standardMinWidth) {
if (this.isSelectOnlyModeEnabled) {
this.viewportMinWidth = '100%';
}
else {
this.nodesMinWidthAsNumber = standardMinWidth;
this.viewportMinWidth = this.nodesMinWidthAsNumber + 'px';
}
}
else {
if (this.isSelectOnlyModeEnabled ||
(!this.isSelectOnlyModeEnabled && this.isLargeScreen)) {
this.viewportMinWidth = this.nodesMinWidthAsNumber + 'px';
}
else {
this.viewportMinWidth = '100%';
}
}
this.widthUpdated.emit(this.viewportMinWidth);
}
updateDataSourceOnInitAndOnSearch() {
this.updateDataTreeOnSetAndOnSearch();
this.updateFlattenTreePropertiesOnSetAndOnSearch();
this.enableAnimation = true;
this.addedChildrenIds = [...this.flattenDataTreeIds];
this.updateDataSource();
}
scrollToSelectedNode() {
if (this.virtualScroll) {
const indexToScroll = this.flattenDataTreeIds.includes(this.selectedNodeId)
? this.flattenDataTreeIds.indexOf(this.selectedNodeId)
: 0;
this.virtualScroll.scrollToIndex(indexToScroll);
}
}
_setVirtualScrollViewportHeight() {
const displayedNodesHeight = (this.nbDisplayedNodes + 1) * this.STANDARD_NODE_HEIGHT;
const maxTreeHeightInDialog = this.MAX_WINDOW_HEIGHT_PERCENTAGE * window.innerHeight;
this.viewportHeight =
displayedNodesHeight < maxTreeHeightInDialog || this.isScrollDisable
? displayedNodesHeight
: maxTreeHeightInDialog;
}
isActiveNodeLink(nodeId, type = null, childrenType = null) {
return ((!this.isSelectOnlyModeEnabled && nodeId === this.selectedNodeId) ||
(this.isSelectOnlyModeEnabled &&
this.selectMode === ETreeSelectMode.SINGLE &&
this.activeNode &&
this.activeNode.ids.id === nodeId &&
(childrenType === null || type === childrenType)));
}
onCheckMatBox(node) {
this.checklistSelection.toggle(node);
this.selectedNodes.emit(this.checklistSelection.selected);
}
onClickOnTreeNode(node) {
if (this.isSelectOnlyModeEnabled &&
node.ids.id !== this.selectedNodeId &&
this.selectMode === ETreeSelectMode.SINGLE &&
node.type === this.selectedResourceTypeId) {
this.getActiveNode(node);
}
else if (!this.isSelectOnlyModeEnabled) {
this.setEntryIdsInStore(node.ids, node.parentIds);
}
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.4", ngImport: i0, type: GenericTreeComponent, deps: [{ token: i1.Store }], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "17.3.4", type: GenericTreeComponent, selector: "dm-generic-tree", inputs: { viewportHeight: "viewportHeight", allNodesIdsNames: "allNodesIdsNames", selectedNodeId: "selectedNodeId", flattenInitialDataTree: "flattenInitialDataTree", initialDataTree: "initialDataTree", search: "search", expandedTreeNodesUuids: "expandedTreeNodesUuids", actions: "actions", selectedResourceRootTypeId: "selectedResourceRootTypeId", selectedResourceTypeId: "selectedResourceTypeId", isSelectOnlyModeEnabled: "isSelectOnlyModeEnabled", isAdvancedSearchActivated: "isAdvancedSearchActivated", selectMode: "selectMode", refresh: "refresh", nodesIdsMatchingAdvancedSearch: "nodesIdsMatchingAdvancedSearch", advancedSearch: "advancedSearch", isLargeScreen: "isLargeScreen", selectedNodesId: "selectedNodesId", isFilteringOnExternalId: "isFilteringOnExternalId", isScrollDisable: "isScrollDisable", actionsMenuTemplate: "actionsMenuTemplate" }, outputs: { widthUpdated: "widthUpdated", selectedNode: "selectedNode", selectedNodes: "selectedNodes" }, viewQueries: [{ propertyName: "tree", first: true, predicate: ["tree"], descendants: true }, { propertyName: "divGenericTree", first: true, predicate: ["divGenericTree"], descendants: true }, { propertyName: "divMatTree", first: true, predicate: ["divMatTree"], descendants: true }, { propertyName: "virtualScroll", first: true, predicate: CdkVirtualScrollViewport, descendants: true }], ngImport: i0, template: "<!-- Copyright (C) 2020-2024 Linagora\n\nThis program is free software: you can redistribute it and/or modify it under\nthe terms of the GNU Affero General Public License as published by the Free\nSoftware Foundation, either version 3 of the License, or (at your option) any\nlater version, provided you comply with the Additional Terms applicable for\nLinID Directory Manager software by LINAGORA pursuant to Section 7 of the GNU\nAffero General Public License, subsections (b), (c), and (e), pursuant to\nwhich these Appropriate Legal Notices must notably (i) retain the display of\nthe \"LinID\u2122\" trademark/logo at the top of the interface window, the display\nof the \u201CYou are using the Open Source and free version of LinID\u2122, powered by\nLinagora \u00A9 2009\u20132013. Contribute to LinID R&D by subscribing to an Enterprise\noffer!\u201D infobox and in the e-mails sent with the Program, notice appended to\nany type of outbound messages (e.g. e-mail and meeting requests) as well as\nin the LinID Directory Manager user interface, (ii) retain all hypertext\nlinks between LinID Directory Manager and https://linid.org/, as well as\nbetween LINAGORA and LINAGORA.com, and (iii) refrain from infringing LINAGORA\nintellectual property rights over its trademarks and commercial brands. Other\nAdditional Terms apply, see <http://www.linagora.com/licenses/> for more\ndetails.\n\nThis program is distributed in the hope that it will be useful, but WITHOUT\nANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS\nFOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more\ndetails.\n\nYou should have received a copy of the GNU Affero General Public License and\nits applicable Additional Terms for LinID Directory Manager along with this\nprogram. If not, see <http://www.gnu.org/licenses/> for the GNU Affero\nGeneral Public License version 3 and <http://www.linagora.com/licenses/> for\nthe Additional Terms applicable to the LinID Directory Manager software. -->\n\n<div\n #divGenericTree\n fxLayout=\"column\"\n [ngClass]=\"\n isSelectOnlyModeEnabled\n ? 'div-generic-tree-dialog'\n : 'div-generic-tree-component'\n \"\n>\n <div\n [ngClass]=\"\n isSelectOnlyModeEnabled\n ? 'div-mat-tree-dialog'\n : isLargeScreen\n ? 'div-mat-tree-lg-screen'\n : 'div-mat-tree-sm-screen'\n \"\n #divMatTree\n >\n <cdk-virtual-scroll-viewport\n [style.height.px]=\"viewportHeight\"\n [style.width]=\"viewportMinWidth\"\n [itemSize]=\"STANDARD_NODE_HEIGHT\"\n [minBufferPx]=\"viewportHeight\"\n >\n <ng-container\n *cdkVirtualFor=\"let item of flattenDataTreeIds\"\n ></ng-container>\n <mat-tree\n #tree\n [dataSource]=\"dataSource\"\n [treeControl]=\"treeControl\"\n class=\"gnc-tree\"\n >\n <!-- This is the template for the common part of any node -->\n <ng-template #nodeInfo let-node=\"node\">\n <div\n fxFlex\n fxLayout=\"row\"\n [ngClass]=\"\n isSelectOnlyModeEnabled &&\n (node.ids.id === selectedNodeId ||\n node.type !== selectedResourceTypeId)\n ? 'disabled-div'\n : ''\n \"\n class=\"data-info\"\n >\n <button\n fxFlexFill\n fxLayout=\"row\"\n fxLayoutAlign=\"start center\"\n [title]=\"node.name\"\n class=\"btn-node\"\n >\n <span\n appColorSearchedLetters\n [name]=\"node.name\"\n [nodeIds]=\"node.ids\"\n [title]=\"node.name\"\n [search]=\"search\"\n [isFilteringOnExternalId]=\"isFilteringOnExternalId\"\n [isAdvancedSearchActivated]=\"isAdvancedSearchActivated\"\n [nodesIdsMatchingAdvancedSearch]=\"\n nodesIdsMatchingAdvancedSearch\n \"\n classToApply=\"highlight\"\n nodesMatchingSearchClass=\"node-matching-search\"\n class=\"data-name ellipsis\"\n id=\"{{ node.ids.id }}\"\n ></span>\n </button>\n </div>\n </ng-template>\n\n <!-- mat-tree-node is used for ending nodes (without children) -->\n <mat-tree-node\n *matTreeNodeDef=\"let node\"\n @fadeTree\n [@.disabled]=\"\n !(enableAnimation && addedChildrenIds.includes(node.ids.id))\n \"\n class=\"gnc-tree-node parent-node-without-children\"\n [style.paddingLeft.px]=\"\n node.levelForPadding * STANDARD_NODE_PADDING_LEFT\n \"\n id=\"{{\n !isSelectOnlyModeEnabled\n ? 'node-' + node.ids.id\n : 'selected-mode-node-' + node.ids.id\n }}\"\n >\n <div\n fxFlex\n fxLayout=\"row\"\n fxLayoutAlign=\"start center\"\n tabindex=\"-1\"\n [routerLink]=\"!isSelectOnlyModeEnabled ? [getLink(node)] : []\"\n [ngClass]=\"{\n 'active-node-link': isActiveNodeLink(\n node.ids.id,\n node.type,\n node.childrenType\n )\n }\"\n (click)=\"onClickOnTreeNode(node)\"\n class=\"gnc-tree-node-content\"\n >\n <button mat-icon-button disabled></button>\n <mat-checkbox\n *ngIf=\"\n isSelectOnlyModeEnabled &&\n selectMode === allTreeSelectMode.MULTIPLE &&\n node.type === selectedResourceTypeId\n \"\n [checked]=\"checklistSelection.isSelected(node)\"\n (change)=\"onCheckMatBox(node)\"\n ></mat-checkbox>\n <ng-container\n [ngTemplateOutlet]=\"nodeInfo\"\n [ngTemplateOutletContext]=\"{ node: node }\"\n ></ng-container>\n </div>\n <div\n *ngIf=\"!isSelectOnlyModeEnabled\"\n fxLayout=\"row\"\n class=\"ellipsis-menu\"\n >\n <ng-container\n [ngTemplateOutlet]=\"actionsMenuTemplate\"\n [ngTemplateOutletContext]=\"{\n node,\n selectedElementsUiIds: [\n {\n id: node.ids.id,\n externalId: node.ids.externalId,\n label: node.name\n }\n ]\n }\"\n ></ng-container>\n </div>\n </mat-tree-node>\n\n <!-- mat-nested-tree-node is used for branching nodes (with childrens) -->\n <mat-tree-node\n *matTreeNodeDef=\"let node; when: hasChild\"\n @fadeTree\n [@.disabled]=\"\n !(enableAnimation && addedChildrenIds.includes(node.ids.id))\n \"\n [ngClass]=\"\n !treeControl.isExpandable(node)\n ? 'parent-node-without-children'\n : ''\n \"\n class=\"gnc-tree-node\"\n [style.paddingLeft.px]=\"\n node.levelForPadding * STANDARD_NODE_PADDING_LEFT\n \"\n id=\"{{\n !isSelectOnlyModeEnabled\n ? 'node-' + node.ids.id\n : 'selected-mode-node-' + node.ids.id\n }}\"\n >\n <div fxLayout=\"row\">\n <div\n fxFlex\n fxLayout=\"row\"\n fxLayoutAlign=\"start center\"\n tabindex=\"-1\"\n [routerLink]=\"!isSelectOnlyModeEnabled ? [getLink(node)] : []\"\n [ngClass]=\"{\n 'active-node-link': isActiveNodeLink(\n node.ids.id,\n node.type,\n node.childrenType\n )\n }\"\n (click)=\"onClickOnTreeNode(node)\"\n class=\"gnc-tree-node-content\"\n >\n <button\n *ngIf=\"treeControl.isExpandable(node)\"\n mat-icon-button\n (click)=\"$event.stopPropagation(); onToggleFold(node)\"\n class=\"btn-toggle\"\n >\n <mat-icon aria-hidden=\"true\" class=\"mat-icon-rtl-mirror\">\n {{\n treeControl.isExpanded(node)\n ? 'arrow_drop_down'\n : 'arrow_right'\n }}\n </mat-icon>\n </button>\n <mat-checkbox\n *ngIf=\"\n isSelectOnlyModeEnabled &&\n selectMode === allTreeSelectMode.MULTIPLE &&\n node.type === selectedResourceTypeId\n \"\n [checked]=\"checklistSelection.isSelected(node)\"\n (click)=\"onClickOnTreeNode(node)\"\n ></mat-checkbox>\n <ng-container\n [ngTemplateOutlet]=\"nodeInfo\"\n [ngTemplateOutletContext]=\"{ node: node }\"\n ></ng-container>\n </div>\n <div\n *ngIf=\"!isSelectOnlyModeEnabled\"\n fxLayout=\"row\"\n class=\"ellipsis-menu\"\n >\n <ng-container\n [ngTemplateOutlet]=\"actionsMenuTemplate\"\n [ngTemplateOutletContext]=\"{\n node,\n selectedElementsUiIds: getNodesForDeleteActions(node)\n }\"\n ></ng-container>\n </div>\n </div>\n <div *ngIf=\"treeControl.isExpanded(node)\" class=\"data-info\">\n <ng-container matTreeNodeOutlet></ng-container>\n </div>\n </mat-tree-node>\n </mat-tree>\n </cdk-virtual-scroll-viewport>\n </div>\n</div>\n", styles: ["@charset \"UTF-8\";.gnc-tree{padding:0 10px 16px;z-index:1;position:relative}.gnc-tree-node{min-height:40px;height:40px;outline:none;border:none;display:block}.gnc-tree-node div{height:100%}.gnc-tree-node-content:before{content:\"\";min-height:40px;height:40px;position:absolute;left:0;right:0;z-index:-1;transition:all .2s ease}.gnc-tree-node-content:hover:before{background-color:#0000000a}.disabled-div{pointer-events:none;opacity:.4}.gnc-tree-node-content{min-width:0}.data-info:not(:first-of-type){padding-inline-start:20px}.data-name{text-align:left}.data-name:after{display:block;content:attr(title);font-weight:700;height:0;overflow:hidden;visibility:hidden}.btn-toggle{height:100%;background-color:transparent}.btn-toggle span{line-height:24px;height:100%}.data-info{overflow:hidden}.btn-node{background:transparent;outline:none;border:none}div.active-node-link:before{content:\"\";box-shadow:inset 4px 0;background-color:#0000000a;min-height:40px;height:40px;position:absolute;z-index:-1;left:0;right:0}mat-tree-node.parent-node-without-children{padding-inline-start:40px}.tree-node-hidden{display:none!important}.div-mat-tree-lg-screen{width:fit-content;min-width:290px;max-width:474px}.div-mat-tree-sm-screen{width:290px;min-width:290px;max-width:290px}.div-generic-tree-component{height:100%;width:fit-content}.div-generic-tree-dialog{height:100%}.div-mat-tree-dialog{min-width:100%}.active-node-link div:not(.disabled-div) button,.active-node-link mat-icon,.gnc-tree-node-content:hover div:not(.disabled-div) button,.gnc-tree-node-content:hover mat-icon{cursor:pointer;transition:.5s}.active-node-link .btn-node:focus{background:transparent;outline:none;border:none}div.div-mat-tree-dialog cdk-virtual-scroll-viewport{max-width:100%}\n"], dependencies: [{ kind: "directive", type: i2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i2.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: i3.RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "directive", type: i4.DefaultLayoutDirective, selector: " [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]", inputs: ["fxLayout", "fxLayout.xs", "fxLayout.sm", "fxLayout.md", "fxLayout.lg", "fxLayout.xl", "fxLayout.lt-sm", "fxLayout.lt-md", "fxLayout.lt-lg", "fxLayout.lt-xl", "fxLayout.gt-xs", "fxLayout.gt-sm", "fxLayout.gt-md", "fxLayout.gt-lg"] }, { kind: "directive", type: i4.DefaultLayoutAlignDirective, selector: " [fxLayoutAlign], [fxLayoutAlign.xs], [fxLayoutAlign.sm], [fxLayoutAlign.md], [fxLayoutAlign.lg], [fxLayoutAlign.xl], [fxLayoutAlign.lt-sm], [fxLayoutAlign.lt-md], [fxLayoutAlign.lt-lg], [fxLayoutAlign.lt-xl], [fxLayoutAlign.gt-xs], [fxLayoutAlign.gt-sm], [fxLayoutAlign.gt-md], [fxLayoutAlign.gt-lg]", inputs: ["fxLayoutAlign", "fxLayoutAlign.xs", "fxLayoutAlign.sm", "fxLayoutAlign.md", "fxLayoutAlign.lg", "fxLayoutAlign.xl", "fxLayoutAlign.lt-sm", "fxLayoutAlign.lt-md", "fxLayoutAlign.lt-lg", "fxLayoutAlign.lt-xl", "fxLayoutAlign.gt-xs", "fxLayoutAlign.gt-sm", "fxLayoutAlign.gt-md", "fxLayoutAlign.gt-lg"] }, { kind: "directive", type: i4.FlexFillDirective, selector: "[fxFill], [fxFlexFill]" }, { kind: "directive", type: i4.DefaultFlexDirective, selector: " [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]", inputs: ["fxFlex", "fxFlex.xs", "fxFlex.sm", "fxFlex.md", "fxFlex.lg", "fxFlex.xl", "fxFlex.lt-sm", "fxFlex.lt-md", "fxFlex.lt-lg", "fxFlex.lt-xl", "fxFlex.gt-xs", "fxFlex.gt-sm", "fxFlex.gt-md", "fxFlex.gt-lg"] }, { kind: "directive", type: i5.DefaultClassDirective, selector: " [ngClass], [ngClass.xs], [ngClass.sm], [ngClass.md], [ngClass.lg], [ngClass.xl], [ngClass.lt-sm], [ngClass.lt-md], [ngClass.lt-lg], [ngClass.lt-xl], [ngClass.gt-xs], [ngClass.gt-sm], [ngClass.gt-md], [ngClass.gt-lg]", inputs: ["ngClass", "ngClass.xs", "ngClass.sm", "ngClass.md", "ngClass.lg", "ngClass.xl", "ngClass.lt-sm", "ngClass.lt-md", "ngClass.lt-lg", "ngClass.lt-xl", "ngClass.gt-xs", "ngClass.gt-sm", "ngClass.gt-md", "ngClass.gt-lg"] }, { kind: "directive", type: i6.CdkFixedSizeVirtualScroll, selector: "cdk-virtual-scroll-viewport[itemSize]", inputs: ["itemSize", "minBufferPx", "maxBufferPx"] }, { kind: "directive", type: i6.CdkVirtualForOf, selector: "[cdkVirtualFor][cdkVirtualForOf]", inputs: ["cdkVirtualForOf", "cdkVirtualForTrackBy", "cdkVirtualForTemplate", "cdkVirtualForTemplateCacheSize"] }, { kind: "component", type: i6.CdkVirtualScrollViewport, selector: "cdk-virtual-scroll-viewport", inputs: ["orientation", "appendOnly"], outputs: ["scrolledIndexChange"] }, { kind: "component", type: i7.MatIconButton, selector: "button[mat-icon-button]", exportAs: ["matButton"] }, { kind: "component", type: i8.MatCheckbox, selector: "mat-checkbox", inputs: ["aria-label", "aria-labelledby", "aria-describedby", "id", "required", "labelPosition", "name", "value", "disableRipple", "tabIndex", "color", "checked", "disabled", "indeterminate"], outputs: ["change", "indeterminateChange"], exportAs: ["matCheckbox"] }, { kind: "component", type: i9.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "directive", type: i10.MatTreeNodeDef, selector: "[matTreeNodeDef]", inputs: ["matTreeNodeDefWhen", "matTreeNode"] }, { kind: "component", type: i10.MatTree, selector: "mat-tree", exportAs: ["matTree"] }, { kind: "directive", type: i10.MatTreeNode, selector: "mat-tree-node", inputs: ["disabled", "tabIndex"], exportAs: ["matTreeNode"] }, { kind: "directive", type: i10.MatTreeNodeOutlet, selector: "[matTreeNodeOutlet]" }, { kind: "directive", type: i11.ColorSearchedLettersDirective, selector: "[appColorSearchedLetters]", inputs: ["search", "name", "isFilteringOnExternalId", "nodeIds", "classToApply", "nodesMatchingSearchClass", "isAdvancedSearchActivated", "nodesIdsMatchingAdvancedSearch"] }], animations: [trigger('fadeTree', fadeTree(':enter'))] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.4", ngImport: i0, type: GenericTreeComponent, decorators: [{
type: Component,
args: [{ selector: 'dm-generic-tree', animations: [trigger('fadeTree', fadeTree(':enter'))], template: "<!-- Copyright (C) 2020-2024 Linagora\n\nThis program is free software: you can redistribute it and/or modify it under\nthe terms of the GNU Affero General Public License as published by the Free\nSoftware Foundation, either version 3 of the License, or (at your option) any\nlater version, provided you comply with the Additional Terms applicable for\nLinID Directory Manager software by LINAGORA pursuant to Section 7 of the GNU\nAffero General Public License, subsections (b), (c), and (e), pursuant to\nwhich these Appropriate Legal Notices must notably (i) retain the display of\nthe \"LinID\u2122\" trademark/logo at the top of the interface window, the display\nof the \u201CYou are using the Open Source and free version of LinID\u2122, powered by\nLinagora \u00A9 2009\u20132013. Contribute to LinID R&D by subscribing to an Enterprise\noffer!\u201D infobox and in the e-mails sent with the Program, notice appended to\nany type of outbound messages (e.g. e-mail and meeting requests) as well as\nin the LinID Directory Manager user interface, (ii) retain all hypertext\nlinks between LinID Directory Manager and https://linid.org/, as well as\nbetween LINAGORA and LINAGORA.com, and (iii) refrain from infringing LINAGORA\nintellectual property rights over its trademarks and commercial brands. Other\nAdditional Terms apply, see <http://www.linagora.com/licenses/> for more\ndetails.\n\nThis program is distributed in the hope that it will be useful, but WITHOUT\nANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS\nFOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more\ndetails.\n\nYou should have received a copy of the GNU Affero General Public License and\nits applicable Additional Terms for LinID Directory Manager along with this\nprogram. If not, see <http://www.gnu.org/licenses/> for the GNU Affero\nGeneral Public License version 3 and <http://www.linagora.com/licenses/> for\nthe Additional Terms applicable to the LinID Directory Manager software. -->\n\n<div\n #divGenericTree\n fxLayout=\"column\"\n [ngClass]=\"\n isSelectOnlyModeEnabled\n ? 'div-generic-tree-dialog'\n : 'div-generic-tree-component'\n \"\n>\n <div\n [ngClass]=\"\n isSelectOnlyModeEnabled\n ? 'div-mat-tree-dialog'\n : isLargeScreen\n ? 'div-mat-tree-lg-screen'\n : 'div-mat-tree-sm-screen'\n \"\n #divMatTree\n >\n <cdk-virtual-scroll-viewport\n [style.height.px]=\"viewportHeight\"\n [style.width]=\"viewportMinWidth\"\n [itemSize]=\"STANDARD_NODE_HEIGHT\"\n [minBufferPx]=\"viewportHeight\"\n >\n <ng-container\n *cdkVirtualFor=\"let item of flattenDataTreeIds\"\n ></ng-container>\n <mat-tree\n #tree\n [dataSource]=\"dataSource\"\n [treeControl]=\"treeControl\"\n class=\"gnc-tree\"\n >\n <!-- This is the template for the common part of any node -->\n <ng-template #no