UNPKG

@linid-dm/directory-manager-client-core

Version:

Core package by providing a set of angular components for the Directory Manager app.

492 lines 143 kB
import { __decorate } from "tslib"; /** * 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 { HttpErrorResponse } from '@angular/common/http'; import { Component, EventEmitter, Input, Output, ViewChild, } from '@angular/core'; import { MatTableDataSource } from '@angular/material/table'; import { Select } from '@ngxs/store'; import { Subject, catchError, concatMap, delay, filter, forkJoin, of, take, takeUntil, tap, throwError, } from 'rxjs'; import { DisplayedDetailArray, EComponentName, EUIComponent, EUpdateOperations, EUserAction, Error as ErrorActions, UiState, convertDataNodeToDataSourceRow, convertDataSourceRowToLinkObject, fadeIn, getActionType, getArrayAttributeDoingRequestsDetail, getArrayAttributeResourcesTypesIds, getAttributeValues, getClickableCellsEndpoints, getDataSourceArrayUsingDataFromBackend, getDataSourceForGenericArray, getDataSourceRowsWithAssociatedValue, getElementExternalId, getElementId, getNotSelectedRows, getOrderedKeysArrayAttributes, getRowId, getSelectedElements, getSucceededNotifications, isDataNode, setRequestsProperties, toScimPropertiesPatch, } from '../../shared'; import { ConfirmDialogComponent } from '../confirm-dialog/confirm-dialog.component'; import * as i0 from "@angular/core"; import * as i1 from "@ngxs/store"; import * as i2 from "@angular/router"; import * as i3 from "@angular/material/dialog"; import * as i4 from "../../shared"; import * as i5 from "@angular/common"; import * as i6 from "@angular/flex-layout/flex"; import * as i7 from "@angular/material/button"; import * as i8 from "@angular/material/icon"; import * as i9 from "@angular/material/tabs"; import * as i10 from "../../ui/generic-array-header/generic-array-header.component"; import * as i11 from "../ellipsis-menu/ellipsis-menu.component"; import * as i12 from "../generic-array/generic-array.component"; export class DataDetailArrayAttributesComponent { set selectedEntryIds(selectedEntryIds) { this._selectedEntryIds = selectedEntryIds; this._requestsSubscription?.unsubscribe(); this.allAttributesDisplayedAsArray = []; } get selectedEntryIds() { return this._selectedEntryIds; } set selectedResourceTypeId(selectedResourceTypeId) { if (selectedResourceTypeId) { this._selectedResourceTypeId = selectedResourceTypeId; this._arrayAttributesConfig = this.configs.byId[this._selectedResourceTypeId].attributes.filter((attribute) => attribute.value.field.componentName === EComponentName.array); } } get selectedResourceTypeId() { return this._selectedResourceTypeId; } set dataDetail(dataDetail) { this._dataDetail = dataDetail; if (this._dataDetail) { const config = this.configs.byId[this._selectedResourceTypeId]; this._addMandatoryArrays(config); const orderKeysByAttributeId = getOrderedKeysArrayAttributes(this._arrayAttributesConfig, this._dataDetail); const attributesDoingRequests = []; this.allAttributesDisplayedAsArray = []; this.canAssignOrMoveAuthorizedTypes = this.currentUser.canAssignOrMove; const editableArrayProperties = { selectedEntryExternalId: this.selectedEntryIds.externalId, selectedEntryParentExternalId: !!this.selectedEntryParentIds ? this.selectedEntryParentIds.externalId : null, }; orderKeysByAttributeId.forEach((key) => { // Get current attribute configuration const attributeConfig = config.attributes.find((attribute) => attribute.modelRef === key); const attributeComponentName = attributeConfig.value.field.componentName; if (attributeComponentName === EComponentName.array) { let allRequestsResourcesTypesIds = getArrayAttributeResourcesTypesIds(attributeConfig); let attributeDoingRequestsDetails = getArrayAttributeDoingRequestsDetail(attributeConfig, this._dataDetail[key]); let dataResources; if (this._dataDetail[key].length > 0 && attributeConfig.props.isUsingDataFromBackend) { dataResources = getDataSourceArrayUsingDataFromBackend(this._dataDetail[key], attributeConfig.value.field.props.columns.map((column) => column.id), allRequestsResourcesTypesIds, this.resourcesTypes, attributeConfig.value.field.props.keyForSquash); } if (attributeConfig.props.isUsingDataFromBackend || this.hasLoadedArray === false) { this.allAttributesDisplayedAsArray.push(new DisplayedDetailArray(this._selectedResourceTypeId, this.configs, attributeDoingRequestsDetails, allRequestsResourcesTypesIds, dataResources, editableArrayProperties, this._dataDetail.meta.modifiableAttributes.includes(key))); } else if (allRequestsResourcesTypesIds.length > 0 && allRequestsResourcesTypesIds.every((resourceTypeId) => this.resourcesTypes.allIds.includes(resourceTypeId))) { attributesDoingRequests.push(attributeDoingRequestsDetails); } } }); if (attributesDoingRequests.length > 0) { this._requestForArrayAttributes(attributesDoingRequests, editableArrayProperties); } else { this.evtArrayAttributesProcessed.emit(true); } } } get dataDetail() { return this._dataDetail; } constructor(_store$, _router, _dialog, _dataService, _interactionsService, _errorsHandlerService) { this._store$ = _store$; this._router = _router; this._dialog = _dialog; this._dataService = _dataService; this._interactionsService = _interactionsService; this._errorsHandlerService = _errorsHandlerService; this._onDestroy$ = new Subject(); this.uiComponent = EUIComponent.DATA_DETAIL; this.allAttributesDisplayedAsArray = []; this.updateOperations = EUpdateOperations; this.clickOnAssign = false; this.evtArrayAttributesProcessed = new EventEmitter(); this.evtSuccess = new EventEmitter(); } ngOnInit() { this._interactionsService.processingRequest$ .pipe(takeUntil(this._onDestroy$), tap((isProcessingRequest) => (this.isProcessingRequest = isProcessingRequest))) .subscribe(); } ngOnDestroy() { this._onDestroy$.next(); this._onDestroy$.complete(); } openAssignDialog(title, placeholder, noSearchResult, cancelButtonText, confirmButtonText, previousStepButtonText, nextStepButtonText, action, endpoint, assignArrayViewProperties, displayedArrayRowsIds, displayedArray, attributeModelRef, assignedElementsAttributes, updateActionsParams, fieldForUpdate) { const dialogRef = this._dialog.open(ConfirmDialogComponent, { restoreFocus: false, minWidth: '500px', panelClass: 'confirm-dialog-container', data: { selectedElements: [], title, placeholder, noSearchResult, cancelButtonText, confirmButtonText, previousStepButtonText, nextStepButtonText, dialogAction: action, endpoint, assignArrayViewProperties, displayedArrayRowsIds, assignedElementsAttributes, updateActionsParams, attributeModelRef, fieldForUpdate, }, }); dialogRef .afterClosed() .pipe(tap(() => { this.clickOnAssign = false; }), delay(0), tap((_) => this.assignBtn.focus())) .subscribe(); dialogRef.componentInstance.action$ .pipe(tap(() => { const selectedElements = { selectedElements: dialogRef.componentInstance.selectedElements, formValues: dialogRef.componentInstance.editForm?.getRawValue(), }; this.onUpdateArray(selectedElements, [EUpdateOperations.ADD], displayedArray, dialogRef); })) .subscribe(); } assignNewElements(displayedArray) { this.clickOnAssign = true; this.openAssignDialog(displayedArray.actions.assign.title, displayedArray.actions.assign.placeholder, displayedArray.actions.assign.noSearchResult, displayedArray.actions.assign.dialogCancelBtn, displayedArray.actions.assign.dialogConfirmBtn, displayedArray.actions.assign.dialogPreviousStepBtn, displayedArray.actions.assign.dialogNextStepBtn, 'assign', displayedArray.endpoint, { columns: displayedArray.viewProperties.getColumns(), displayedColumns: displayedArray.viewProperties.getDisplayedColumns(), name: displayedArray.typesLabels, icon: displayedArray.icon, resourceTypeConfig: displayedArray.resourceTypeConfig, accessibility: displayedArray.viewProperties.getAccessibility(), noFilterResult: displayedArray.actions.assign.noFilterResult, }, displayedArray.viewProperties .getDataSource() .data.map((row) => row.selectionIds.id), displayedArray, displayedArray.attributeId, displayedArray.viewProperties.getDisplayedColumns(), displayedArray.updateParams, displayedArray.fieldForUpdate); } patchArrayAttribute(event, operationsTypes, displayedArray, dialogRef) { displayedArray.succeededElements = []; const selectedElements = getSelectedElements(event); const selectedElementsIds = selectedElements.map((selectedElement) => getElementId(selectedElement)); if (operationsTypes.length > 0) { of(...operationsTypes) .pipe(take(operationsTypes.length), takeUntil(this._onDestroy$), concatMap((operationType) => this._patchOperationRequest(operationType, selectedElementsIds, selectedElements, event, displayedArray, operationsTypes, dialogRef)), catchError((err) => this._errorsHandlerService.handleError(err)), filter((_, index) => index === operationsTypes.length - 1), tap((_) => this.updateListAfterOperationsOnArray(operationsTypes, displayedArray))) .subscribe(); } } updateArrayAttribute(event, operationsTypes, displayedArray, dialogRef) { displayedArray.succeededElements = []; const fieldToUpdate = displayedArray.attributeId; let selectedElements = getSelectedElements(event); const selectedElementsIds = selectedElements.map((selectedElement) => getElementId(selectedElement)); let newValue; if (operationsTypes.length === 1) { const notSeletedElements = getNotSelectedRows(displayedArray.viewProperties.getDataSource().data, selectedElementsIds); switch (operationsTypes[0]) { case EUpdateOperations.REMOVE: newValue = notSeletedElements.map((row) => convertDataSourceRowToLinkObject(row, displayedArray.updateParams.selectionColumnId, displayedArray.updateParams)); break; case EUpdateOperations.EDIT: newValue = notSeletedElements .concat(event) .map((row) => convertDataSourceRowToLinkObject(row, displayedArray.updateParams.selectionColumnId, displayedArray.updateParams)); break; case EUpdateOperations.ADD: selectedElements = selectedElements.map((element) => convertDataNodeToDataSourceRow(element, displayedArray.viewProperties.getColumns(), this.resourcesTypes)); if (event.formValues) { selectedElements = getDataSourceRowsWithAssociatedValue(selectedElements, displayedArray.updateParams, event.formValues); } newValue = notSeletedElements .concat(selectedElements) .map((row) => convertDataSourceRowToLinkObject(row, displayedArray.updateParams.selectionColumnId, displayedArray.updateParams)); break; default: break; } } const requestBody = { schemas: [this.resourcesTypes.byId[this.selectedResourceTypeId].schema], id: this.selectedEntryIds.id, externalId: this.selectedEntryIds.externalId, [fieldToUpdate]: newValue, }; this._interactionsService.isProcessingRequest(true); this._dataService .updateData(this.endpoint, this.selectedEntryIds.id, requestBody) .pipe(takeUntil(this._onDestroy$), take(1), tap((_) => { this._interactionsService.isProcessingRequest(false); displayedArray.succeededElements = selectedElements; this.updateListAfterOperationsOnArray(operationsTypes, displayedArray); dialogRef?.close(); }), catchError((err) => { this._interactionsService.isProcessingRequest(false); this._store$.dispatch([ new ErrorActions.SetErrorDetail({ error: err, userActionOnError: EUserAction.DETAIL_UPDATE_ARRAY, }), new ErrorActions.IsHandlingError(true), ]); if (err.status === 404) { dialogRef.close(); this._router.navigate([this._router.url.replace(/[^/]+$/g, 'not-found')], { replaceUrl: true }); } return this._errorsHandlerService.handleError(err); })) .subscribe(); } updateArrayElements(event, operationsTypes, displayedArray, dialogRef) { displayedArray.succeededElements = []; displayedArray.failedElements404 = []; if (displayedArray.isEditableArray) { const requestEndpoint = displayedArray.endpoint; const fieldToUpdate = displayedArray.requestsParams.fieldForUpdate; const selectedElements = getSelectedElements(event); forkJoin(selectedElements.map((selectedElement) => { let newValue; if ('selectedNode' in event && event.selectedNode) { newValue = event.selectedNode.ids.id; } else if ('selectedItem' in event && event.selectedItem) { newValue = event.selectedItem.ids.id; } else { newValue = this.selectedEntryIds.id; } if (!!newValue) { return this._operationOnElement(operationsTypes, selectedElement, displayedArray, fieldToUpdate, newValue, requestEndpoint).pipe(takeUntil(this._onDestroy$), take(1), tap((_) => { this._interactionsService.isProcessingRequest(false); displayedArray.succeededElements = [ ...displayedArray.succeededElements, selectedElement, ]; }), catchError((err) => { this._interactionsService.isProcessingRequest(false); this._store$.dispatch([ new ErrorActions.SetErrorDetail({ error: err, userActionOnError: EUserAction.DETAIL_UPDATE_OTHER_RESOURCE, }), new ErrorActions.IsHandlingError(true), ]); if (err.status === 404) { displayedArray.failedElements404 = [ ...displayedArray.failedElements404, selectedElement, ]; } return this._errorsHandlerService.handleError(err); })); } else { return of(null); } })) .pipe(tap((_) => { this.updateListAfterOperationsOnArray(operationsTypes, displayedArray); dialogRef?.close(); })) .subscribe(); } } _operationOnElement(operationsTypes, selectedElement, displayedArray, fieldToUpdate, newValue, requestEndpoint) { const selectedElementId = getElementId(selectedElement); if (operationsTypes.includes(EUpdateOperations.REMOVE) && operationsTypes.length === 1) { this._interactionsService.isProcessingRequest(true); return this._dataService.removeData(requestEndpoint, selectedElementId); } else { const requestBody = { schemas: [ this.resourcesTypes.byId[displayedArray.requestsParams.requestResourceTypeId].schema, ], id: selectedElementId, externalId: getElementExternalId(selectedElement), [fieldToUpdate]: newValue, }; this._interactionsService.isProcessingRequest(true); return this._dataService.updateData(requestEndpoint, selectedElementId, requestBody); } } updateListAfterOperationsOnArray(operationsTypes, displayedArray) { if (operationsTypes.includes(EUpdateOperations.ADD) && !operationsTypes.includes(EUpdateOperations.REMOVE)) { displayedArray.initialSource = [ ...displayedArray.succeededElements.map((elt) => isDataNode(elt) ? convertDataNodeToDataSourceRow(elt, displayedArray.viewProperties.getColumns(), this.resourcesTypes) : elt), ...displayedArray.initialSource, ]; } else { const succeededElementIds = displayedArray.succeededElements.map((succeededElement) => getElementId(succeededElement)); const failedElement404Ids = displayedArray.failedElements404.map((failedElement) => getElementId(failedElement)); displayedArray.selectedElementsUiIds = displayedArray.selectedElementsUiIds.filter((element) => !succeededElementIds.includes(element.id) && !failedElement404Ids.includes(element.id)); if (operationsTypes.length === 1 && operationsTypes.includes(EUpdateOperations.EDIT)) { displayedArray.initialSource = displayedArray.initialSource.map((row) => { const rowId = getRowId(row, displayedArray.updateParams?.selectionColumnId); return (displayedArray.succeededElements.find((row) => row.selectionIds.id === rowId) ?? row); }); } else { displayedArray.initialSource = displayedArray.initialSource.filter((row) => { const rowId = getRowId(row, displayedArray.updateParams?.selectionColumnId); return (!succeededElementIds.includes(rowId) && !failedElement404Ids.includes(rowId)); }); } } displayedArray.viewProperties.setDataSource(new MatTableDataSource(displayedArray.initialSource)); if (displayedArray.succeededElements.length > 0) { const message = getSucceededNotifications(displayedArray.succeededElements, getActionType(displayedArray.succeededElements, displayedArray.actions, operationsTypes)); this.evtSuccess.emit(message); } } onUpdateArray(event, operationsTypes, displayedArray, dialogRef) { if (!this.editableAttributesFromBackend.includes(displayedArray.attributeId)) { this.updateArrayElements(event, operationsTypes, displayedArray, dialogRef); } else if (!!displayedArray.updateParams.patchOperationsParams) { this.patchArrayAttribute(event, operationsTypes, displayedArray, dialogRef); } else { this.updateArrayAttribute(event, operationsTypes, displayedArray, dialogRef); } } _patchOperationRequest(operationType, selectedElementsIds, selectedElements, event, displayedArray, operationsTypes, dialogRef) { let patchedEntryId; if (operationType === EUpdateOperations.ADD) { if (event && 'selectedNode' in event && event.selectedNode) { patchedEntryId = event.selectedNode.ids.id; } else if (event && 'selectedItem' in event && event.selectedItem) { patchedEntryId = event.selectedItem.ids.id; } else { patchedEntryId = this.selectedEntryIds.id; } } else { patchedEntryId = this.selectedEntryIds.id; } const currentPath = operationType === EUpdateOperations.REMOVE ? selectedElementsIds.map((id) => displayedArray.updateParams.patchOperationsParams.remove.replace('$1', '"' + id + '"')) : selectedElements.map((_) => displayedArray.updateParams.patchOperationsParams.add); this._interactionsService.isProcessingRequest(true); return this._dataService .patchData(this.endpoint, patchedEntryId, toScimPropertiesPatch(currentPath, selectedElementsIds, operationType)) .pipe(tap((_) => { this._interactionsService.isProcessingRequest(false); if ((operationType === EUpdateOperations.ADD || (operationType === EUpdateOperations.REMOVE && !operationsTypes.includes(EUpdateOperations.ADD))) && !!event) { displayedArray.succeededElements = [...selectedElements]; dialogRef?.close(); } }), catchError((err) => { this._interactionsService.isProcessingRequest(false); this._store$.dispatch([ new ErrorActions.SetErrorDetail({ error: err, userActionOnError: EUserAction.DETAIL_PATCH, }), new ErrorActions.IsHandlingError(true), ]); if (!!err && err instanceof HttpErrorResponse && err.status === 404 && this.selectedEntryIds.id === patchedEntryId) { this._router.navigate([this._router.url.replace(/[^/]+$/g, 'not-found')], { replaceUrl: true }); dialogRef?.close(); } if (operationType === EUpdateOperations.ADD && operationsTypes.includes(EUpdateOperations.REMOVE) && !!event) { this._patchOperationRequest(EUpdateOperations.ADD, selectedElementsIds, selectedElements, null, displayedArray, operationsTypes, dialogRef) .pipe(take(1), takeUntil(this._onDestroy$)) .subscribe(); } return throwError(() => err); })); } _requestForArrayAttributes(attributesDoingRequests, editableArrayProperties) { this._requestsSubscription = forkJoin(attributesDoingRequests.map((attributeDoingRequests) => { const requestsProperties = setRequestsProperties(attributeDoingRequests, this.selectedEntryIds.id); this._interactionsService.isProcessingRequest(true); return getAttributeValues(requestsProperties, this._dataService, this._errorsHandlerService, attributeDoingRequests, this._store$, attributeDoingRequests.attributeConfig).pipe(filter((dataList) => !(dataList instanceof Error || dataList instanceof HttpErrorResponse)), tap((dataList) => { this._interactionsService.isProcessingRequest(false); const clickableCellsEndpoints = getClickableCellsEndpoints(this.resourcesTypes, this.configs.allIds, this.configs.byId[requestsProperties.resourceTypeId]); const dataResourcesAsFlatArray = getDataSourceForGenericArray(dataList, this.configs.byId[requestsProperties.resourceTypeId], clickableCellsEndpoints); this.allAttributesDisplayedAsArray.push(new DisplayedDetailArray(this._selectedResourceTypeId, this.configs, attributeDoingRequests, [requestsProperties.resourceTypeId], dataResourcesAsFlatArray, editableArrayProperties, this._dataDetail.meta.modifiableAttributes.includes(attributeDoingRequests.idField))); this.evtArrayAttributesProcessed.emit(true); })); })).subscribe(); } selectedData(data, displayedArray) { displayedArray.selectedElementsUiIds = data.map((data) => data?.selectionIds ?? data); } _addMandatoryArrays(config) { const mandatoriesFields = config.attributes .filter((attribute) => attribute.props.isMandatory) .reduce((acc, currAttribute) => ({ ...acc, [currAttribute.modelRef]: currAttribute.value.field.componentName === EComponentName.array ? [] : null, }), {}); this._dataDetail = { ...this._dataDetail, ...mandatoriesFields }; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.4", ngImport: i0, type: DataDetailArrayAttributesComponent, deps: [{ token: i1.Store }, { token: i2.Router }, { token: i3.MatDialog }, { token: i4.DataService }, { token: i4.InteractionsService }, { token: i4.ErrorsHandlerService }], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "17.3.4", type: DataDetailArrayAttributesComponent, selector: "dm-data-detail-array-attributes", inputs: { selectedEntryIds: "selectedEntryIds", selectedEntryParentIds: "selectedEntryParentIds", configs: "configs", currentUser: "currentUser", resourcesTypes: "resourcesTypes", editableAttributesFromBackend: "editableAttributesFromBackend", endpoint: "endpoint", areArrayAttributesProcessed: "areArrayAttributesProcessed", hasLoadedArray: "hasLoadedArray", notLoadedArrayString: "notLoadedArrayString", selectedResourceTypeId: "selectedResourceTypeId", dataDetail: "dataDetail", additionalBtns: "additionalBtns" }, outputs: { evtArrayAttributesProcessed: "evtArrayAttributesProcessed", evtSuccess: "evtSuccess" }, viewQueries: [{ propertyName: "assignBtn", first: true, predicate: ["assignBtn"], 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 @fadeIn *ngIf=\"areArrayAttributesProcessed\" class=\"div-array-attributes\">\n <mat-tab-group\n *ngIf=\"allAttributesDisplayedAsArray?.length > 1; else singleArray\"\n color=\"accent\"\n animationDuration=\"0ms\"\n class=\"array-tab-group\"\n >\n <mat-tab *ngFor=\"let attributeArray of allAttributesDisplayedAsArray\">\n <ng-template mat-tab-label>\n <mat-icon class=\"mat-tab-icon-lable\">{{\n attributeArray.icon\n }}</mat-icon>\n {{ attributeArray.label }}\n </ng-template>\n <ng-template matTabContent>\n <ng-container\n [ngTemplateOutlet]=\"tabContent\"\n [ngTemplateOutletContext]=\"{ attributeArray: attributeArray }\"\n ></ng-container>\n </ng-template>\n </mat-tab>\n </mat-tab-group>\n</div>\n<ng-template #singleArray>\n <div *ngFor=\"let attributeArray of allAttributesDisplayedAsArray\">\n <ng-container\n [ngTemplateOutlet]=\"tabContent\"\n [ngTemplateOutletContext]=\"{ attributeArray: attributeArray }\"\n ></ng-container>\n </div>\n</ng-template>\n\n<ng-template #tabContent let-attributeArray=\"attributeArray\">\n <div\n *ngIf=\"attributeArray?.isEditableArray; else onlyArrayLabel\"\n [fxLayout]=\"\n attributeArray.selectedElementsUiIds.length > 0 ? 'row wrap' : 'row'\n \"\n fxLayoutAlign=\"start center\"\n class=\"div-table-title\"\n #divTableHeader\n >\n <div fxLayout=\"row\" fxLayoutAlign=\"start center\">\n <dm-generic-array-header\n [dataLength]=\"attributeArray.viewProperties.getDataSource().data.length\"\n [dataIcon]=\"attributeArray.icon\"\n [dataName]=\"attributeArray.label\"\n ></dm-generic-array-header>\n <dm-ellipsis-menu\n *ngIf=\"\n attributeArray?.viewProperties?.getDataSource()?.filteredData\n ?.length > 0\n \"\n [isProcessingRequest]=\"isProcessingRequest\"\n [dataViewProperties]=\"attributeArray.viewProperties\"\n [uiComponent]=\"uiComponent\"\n [updateActionsParams]=\"attributeArray.updateParams\"\n [options]=\"{\n canMove: canAssignOrMoveAuthorizedTypes.includes(\n selectedResourceTypeId\n ),\n canDelete: attributeArray.canDeleteEntries\n }\"\n [attributeId]=\"attributeArray.attributeId\"\n [selectedElementsUiIds]=\"attributeArray.selectedElementsUiIds\"\n [actions]=\"attributeArray.actions\"\n (evtMoveSelectedElements)=\"\n onUpdateArray(\n $event,\n [updateOperations.REMOVE, updateOperations.ADD],\n attributeArray,\n $event.dialogRef\n )\n \"\n (evtDeleteSelectedElements)=\"\n onUpdateArray(\n $event.entries,\n [updateOperations.REMOVE],\n attributeArray,\n $event.dialogRef\n )\n \"\n (evtDeleteSelectedElement)=\"\n onUpdateArray(\n [$event.entry],\n [updateOperations.REMOVE],\n attributeArray,\n $event.dialogRef\n )\n \"\n (evtEditSelectedElements)=\"\n onUpdateArray(\n $event.rows,\n [updateOperations.EDIT],\n attributeArray,\n $event.dialogRef\n )\n \"\n ></dm-ellipsis-menu>\n </div>\n <div fxLayout=\"row\" fxLayoutAlign=\"start center\">\n <button\n *ngIf=\"canAssignOrMoveAuthorizedTypes?.includes(selectedResourceTypeId)\"\n #assignBtn\n mat-raised-button\n color=\"accent\"\n [disabled]=\"clickOnAssign\"\n (click)=\"assignNewElements(attributeArray)\"\n class=\"btn-assign-new-elements\"\n >\n {{ attributeArray.actions.assign.btnLabel }}\n </button>\n <ng-container *ngTemplateOutlet=\"additionalBtns\"></ng-container>\n <ng-container\n *ngIf=\"arrayGenericAccessibility$ | async as arrayGenericAccessibility\"\n >\n <span\n *ngIf=\"attributeArray?.selectedElementsUiIds?.length > 0\"\n class=\"span-selected-elements\"\n >\n {{\n attributeArray?.selectedElementsUiIds?.length > 1\n ? arrayGenericAccessibility.multipleSelection\n : arrayGenericAccessibility.singleSelection\n }}{{ attributeArray.selectedElementsUiIds.length }}\n </span>\n </ng-container>\n </div>\n </div>\n <ng-template #onlyArrayLabel>\n <span class=\"display-label-name\">\n {{ attributeArray.label }}\n </span>\n </ng-template>\n\n <div class=\"div-generic-array\">\n <dm-generic-array\n *ngIf=\"attributeArray?.initialSource?.length > 0; else emptyArray\"\n [actionsColumnTemplate]=\"actionsColumn\"\n [updateAffectAnotherResourceType]=\"\n attributeArray.updateAffectAnotherResourceType\n \"\n [fieldForUpdate]=\"attributeArray.fieldForUpdate\"\n [selectionColumnId]=\"attributeArray.updateParams?.selectionColumnId\"\n [isClickableRow]=\"attributeArray.viewProperties.getIsClickableRow()\"\n [canAssignOrMoveEntries]=\"\n canAssignOrMoveAuthorizedTypes.includes(selectedResourceTypeId)\n \"\n [isEditableArray]=\"attributeArray.isEditableArray\"\n [endpoint]=\"attributeArray.endpoint\"\n [columns]=\"attributeArray.viewProperties.getColumns()\"\n [initialDisplayedColumns]=\"\n attributeArray.viewProperties.getDisplayedColumns()\n \"\n [resourcesTypesProperties]=\"resourcesTypes\"\n [configs]=\"configs\"\n [accessibility]=\"attributeArray.viewProperties.getAccessibility()\"\n [actions]=\"attributeArray.actions ? attributeArray.actions : null\"\n [selectedElementsUiIds]=\"attributeArray.selectedElementsUiIds\"\n [isAttributeArray]=\"true\"\n [dataSource]=\"attributeArray.viewProperties.getDataSource()\"\n (selectedData)=\"selectedData($event, attributeArray)\"\n ></dm-generic-array>\n <ng-template #actionsColumn let-row=\"row\">\n <dm-ellipsis-menu\n *ngIf=\"row.isRowEditable\"\n [uiComponent]=\"uiComponent\"\n [attributeId]=\"attributeArray.attributeId\"\n [updateActionsParams]=\"attributeArray.updateParams\"\n [options]=\"{\n canMove: row.canMoveEntry,\n canDelete: row.canDeleteEntry,\n isForRow: true\n }\"\n [selectedElementsUiIds]=\"[row.selectionIds]\"\n [selectedRow]=\"row\"\n [isProcessingRequest]=\"isProcessingRequest\"\n [actions]=\"attributeArray.actions ? attributeArray.actions : null\"\n (click)=\"$event.stopPropagation()\"\n (evtMoveSelectedElements)=\"\n onUpdateArray(\n $event,\n [updateOperations.REMOVE, updateOperations.ADD],\n attributeArray,\n $event.dialogRef\n )\n \"\n (evtDeleteSelectedElement)=\"\n onUpdateArray(\n [$event.entry],\n [updateOperations.REMOVE],\n attributeArray,\n $event.dialogRef\n )\n \"\n (evtEditSelectedElement)=\"\n onUpdateArray(\n [$event.row],\n [updateOperations.EDIT],\n attributeArray,\n $event.dialogRef\n )\n \"\n ></dm-ellipsis-menu>\n </ng-template>\n <ng-template #emptyArray>\n <div fxLayoutAlign=\"center start\">\n <span class=\"placeholder-text placeholder-detail-page-text\">\n {{\n hasLoadedArray != null && !hasLoadedArray\n ? notLoadedArrayString\n : attributeArray.emptyArrayPlaceholder\n }}\n </span>\n </div>\n </ng-template>\n </div>\n</ng-template>\n", styles: ["@charset \"UTF-8\";.display-label-name{padding:16px 20px 0;font-weight:700}.div-table-title{padding:0 0 0 20px}.div-generic-array{height:fit-content;margin-bottom:20px}.btn-assign-new-elements{margin-left:4px;min-width:fit-content}.span-selected-elements{color:#00000080;margin-left:8px}\n"], dependencies: [{ kind: "directive", type: i5.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i5.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i5.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: i6.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: i6.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: "component", type: i7.MatButton, selector: " button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button] ", exportAs: ["matButton"] }, { kind: "component", type: i8.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "directive", type: i9.MatTabContent, selector: "[matTabContent]" }, { kind: "directive", type: i9.MatTabLabel, selector: "[mat-tab-label], [matTabLabel]" }, { kind: "component", type: i9.MatTab, selector: "mat-tab", inputs: ["disabled", "label", "aria-label", "aria-labelledby", "labelClass", "bodyClass"], exportAs: ["matTab"] }, { kind: "component", type: i9.MatTabGroup, selector: "mat-tab-group", inputs: ["color", "fitInkBarToContent", "mat-stretch-tabs", "dynamicHeight", "selectedIndex", "headerPosition", "animationDuration", "contentTabIndex", "disablePagination", "disableRipple", "preserveContent", "backgroundColor"], outputs: ["selectedIndexChange", "focusChange", "animationDone", "selectedTabChange"], exportAs: ["matTabGroup"] }, { kind: "component", type: i10.GenericArrayHeaderComponent, selector: "dm-generic-array-header", inputs: ["dataLength", "dataIcon", "dataName"] }, { kind: "component", type: i11.EllipsisMenuComponent, selector: "dm-ellipsis-menu", inputs: ["dataViewProperties", "selectedElementsUiIds", "selectedRow", "isProcessingRequest", "uiComponent", "updateActionsParams", "options", "node", "item", "attributeId", "actions"], outputs: ["clickOnAddResourceBtn", "evtDeleteSelectedElements", "evtDeleteSelectedElement", "evtMoveSelectedElements", "evtEditSelectedElements", "evtEditSelectedElement", "evtMoveDataNode"] }, { kind: "component", type: i12.GenericArrayComponent, selector: "dm-generic-array", inputs: ["isClickableRow", "isSelectOnlyModeEnabled", "selectedElementsUiIds", "dataSource", "columns", "initialDisplayedColumns", "processingRequest$", "actions", "resourcesTypesProperties", "configs", "accessibility", "endpoint", "canAssignOrMoveEntries", "selectedResourceRootTypeId", "isAttributeArray", "updateAffectAnotherResourceType", "fieldForUpdate", "isEditableArray", "selectionColumnId", "actionsColumnTemplate"], outputs: ["selectedData"] }, { kind: "pipe", type: i5.AsyncPipe, name: "async" }], animations: [trigger('fadeIn', fadeIn(':enter'))] }); } } __decorate([ Select(UiState.getArrayAccessibility) ], DataDetailArrayAttributesComponent.prototype, "arrayGenericAccessibility$", void 0); i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.4", ngImport: i0, type: DataDetailArrayAttributesComponent, decorators: [{ type: Component, args: [{ selector: 'dm-data-detail-array-attributes', animations: [trigger('fadeIn', fadeIn(':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 @fadeIn *ngIf=\"areArrayAttributesProcessed\" class=\"div-array-attributes\">\n <mat-tab-group\n *ngIf=\"allAttributesDisplayedAsArray?.length > 1; else singleArray\"\n color=\"accent\"\n animationDuration=\"0ms\"\n class=\"array-tab-group\"\n >\n <mat-tab *ngFor=\"let attributeArray of allAttributesDisplayedAsArray\">\n <ng-template mat-tab-label>\n <mat-icon class=\"mat-tab-icon-lable\">{{\n attributeArray.icon\n }}</mat-icon>\n {{ attributeArray.label }}\n </ng-template>\n <ng-template matTabContent>\n <ng-container\n [ngTemplateOutlet]=\"tabContent\"\n [ngTemplateOutletContext]=\"{ attributeArray: attributeArray }\"\n ></ng-container>\n </ng-template>\n </mat-tab>\n </mat-tab-group>\n</div>\n<ng-template #singleArray>\n <div *ngFor=\"let attributeArray of allAttributesDisplayedAsArray\">\n <ng-container\n [ngTemplateOutlet]=\"tabContent\"\n [ngTemplateOutletContext]=\"{ attributeArray: attributeArray }\"\n ></ng-container>\n </div>\n</ng-template>\n\n<ng-template #tabContent let-attributeArray=\"attributeArray\">\n <div\n *ngIf=\"attributeArray?.isEditableArray; else onlyArrayLabel\"\n [fxLayout]=\"\n attributeArray.selectedElementsUiIds.length > 0 ? 'row wrap' : 'row'\n \"\n fxLayoutAlign=\"start center\"\n class=\"div-table-title\"\n #divTableHeader\n >\n <div fxLayout=\"row\" fxLayoutAlign=\"start center\">\n <dm-generic-array-header\n [dataLength]=\"attributeArray.viewProperties.getDataSource().data.length\"\n [dataIcon]=\"attributeArray.icon\"\n [dataName]=\"attributeArray.label\"\n ></dm-generic-array-header>\n <dm-ellipsis-menu\n *ngIf=\"\n attributeArray?.viewProperties?.getDataSource()?.filteredData\n ?.length > 0\n \"\n [isProcessingRequest]=\"isProcessingRequest\"\n [dataViewProperties]=\"attributeArray.viewProperties\"\n [uiComponent]=\"uiComponent\"\n [updateActionsParams]=\"attributeArray.updateParams\"\n [options]=\"{\n canMove: canAssignOrMoveAuthorizedTypes.includes(\n selectedResourceTypeId\n ),\n canDelete: attributeArray.canDeleteEntries\n }\"\n [attributeId]=\"attributeArray.attributeId\"\n [selectedElementsUiIds]=\"attributeArray.selectedElementsUiIds\"\n [actions]=\"attributeArray.actions\"\n (evtMoveSelectedElements)=\"\n onUpdateArray(\n $event,\n [updateOperations.REMOVE, updateOperations.ADD],\n attributeArray,\n $event.dialogRef\n )\n \"\n (evtDeleteSelectedElements)=\"\n onUpdateArray(\n $event.entries,\n [updateOperations.REMOVE],\n attributeArray,\n $event.dialogRef\n )\n \"\n (evtDeleteSelectedElement)=\"\n onUpdateArray(\n [$event.entry],\n [updateOperations.REMOVE],\n attributeArray,\n $event.dialogRef\n )\n \"\n (evtEditSelectedElements)=\"\n onUpdateArray(\n $event.rows,\n [updateOperations.EDIT],\n attributeArray,\n $event.dialogRef\n )\n \"\n ></dm-ellipsis-menu>\n </div>\n <div fxLayout=\"row\" fxLayoutAlign=\"start center\">\n <button\n *ngIf=\"canAssignOrMoveAuthorizedTypes?.includes(selectedResourceTypeId)\"\n #assignBtn\n mat-raised-button\n color=\"accent\"\n [disabled]=\"clickOnAssign\"\n (click)=\"assignNewElements(attributeArray)\"\n class=\"btn-assign-new-elements\"\n >\n {{ attributeArray.actions.assign.btnLabel }}\n </button>\n <ng-container *ngTemplateOutlet=\"additionalBtns\"></ng-container>\n <ng-container\n *ngIf=\"arrayGenericAccessibility$ | async as arrayGenericAccessibility\"\n >\n <span\n *ngIf=\"attributeArray?.selectedElementsUiIds?.length > 0\"\n class=\"span-selected-elements\"\n >\n {{\n attributeArray?.selectedElementsUiIds?.length > 1\n ? arrayGenericAccessibility.multipleSelection\n : arrayGenericAccessibility.singleSelection\n }}{{ attributeArray.selectedElementsUiIds.length }}\n </span>\n </ng-container>\n </div>\n </div>\n <ng-template #onlyArrayLabel>\n <span