[ACS-6831] Property panel refactoring (#9372)

* [ACS-6831] Refactor property panel feature to mitigate bugs and increase code quality

* [ACS-6831] Introduce new interace for metadata panel

* ACS-6831 Corrected tests

* ACS-6831 Fixed unit tests

* ACS-6831 Fixed unit tests

* ACS-6831 Cleaning code

* ACS-6831 Added tests

* ACS-6831 Replaced fdescribe with describe

* [ACS-6831] Use default properties enum, fix editing state

* [ACS-6831] Expand correct section based on displayAspect property

* [ACS-6831] Lint fix

---------

Co-authored-by: Aleksander Sklorz <Aleksander.Sklorz@hyland.com>
This commit is contained in:
MichalKinas 2024-02-26 08:47:56 +01:00 committed by GitHub
parent 872abc3dc0
commit f7dcab7adb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 588 additions and 604 deletions

View File

@ -12,8 +12,7 @@ Specifies required properties for custom metadata panel to be displayed in [Cont
## Basic usage
```ts
export interface ContentMetadataCustomPanel {
panelTitle: string;
export interface ContentMetadataCustomPanel extends ContentMetadataPanel {
component: string;
}
```
@ -22,10 +21,10 @@ export interface ContentMetadataCustomPanel {
| Name | Type | Default value | Description |
| ---- | ---- | ------------- | ----------- |
| panelTitle | `string` | | Title for the panel the will be displayed in expansion panel header. |
| component | `string` | | Id of the registered [Dynamic component](../../extensions/components/dynamic.component.md) to be displayed inside expansion panel. |
## See also
- [ContentMetadataCardComponent](../components/content-metadata-card.component.md)
- [Dynamic Component](../../extensions/components/dynamic.component.md)
- [Content Metadata Panel intergace](./content-metadata-panel.interface.md)

View File

@ -0,0 +1,32 @@
---
Title: Content Metadata Panel interface
Added: v6.7.0
Status: Active
Last reviewed: 2024-02-22
---
# [Content Metadata Panel interface](../../../lib/content-services/src/lib/content-metadata/interfaces/content-metadata-panel.interface.ts "Defined in content-metadata-panel.interface.ts")
Specifies required properties for metadata panel to be displayed in [ContentMetadataCardComponent](../components/content-metadata-card.component.md).
## Basic usage
```ts
export interface ContentMetadataPanel {
panelTitle: string;
expanded?: boolean;
}
```
### Properties
| Name | Type | Default value | Description |
| ---- | ---- | ------------- | ----------- |
| panelTitle | `string` | | Title for the panel the will be displayed in expansion panel header. |
| expanded | `boolean` | | Specifies if given panel is expanded. |
## See also
- [ContentMetadataCardComponent](../components/content-metadata-card.component.md)
- [Dynamic Component](../../extensions/components/dynamic.component.md)
- [Content Metadata Custom panel interface](./content-metadata-custom-panel.interface.md)

View File

@ -31,10 +31,13 @@ import { TranslateModule } from '@ngx-translate/core';
adf-content-metadata-header {
display: flex;
align-items: center;
flex: 1;
width: 100%;
}
.adf-metadata-properties-title {
display: block;
overflow: hidden;
text-overflow: ellipsis;
font-weight: 700;
font-size: 15px;
padding-left: 12px;
@ -44,7 +47,7 @@ import { TranslateModule } from '@ngx-translate/core';
template: `
<ng-container>
<mat-icon>{{ expanded ? 'expand_more' : 'chevron_right' }}</mat-icon>
<mat-panel-title *ngIf="title" class="adf-metadata-properties-title">{{ title | translate }}</mat-panel-title>
<mat-panel-title *ngIf="title" class="adf-metadata-properties-title" [title]="title | translate">{{ title | translate }}</mat-panel-title>
<ng-content></ng-content>
</ng-container>
`

View File

@ -2,35 +2,43 @@
<mat-expansion-panel
*ngIf="displayDefaultProperties"
class="adf-content-metadata-panel"
[(expanded)]="isGeneralPanelExpanded"
[expanded]="currentPanel.panelTitle === DefaultPanels.PROPERTIES && currentPanel.expanded"
(opened)="expandPanel(DefaultPanels.PROPERTIES)"
(closed)="closePanel(DefaultPanels.PROPERTIES)"
[attr.data-automation-id]="'adf-metadata-group-properties'"
hideToggle>
<mat-expansion-panel-header class="adf-metadata-properties-header"
[class.adf-metadata-properties-header-expanded]='isGeneralPanelExpanded'>
<adf-content-metadata-header [title]="'CORE.METADATA.BASIC.HEADER'" [expanded]="isGeneralPanelExpanded">
<button *ngIf="canEditGeneralInfo"
mat-icon-button
(click)="onToggleGeneralInfoEdit($event)"
[attr.title]="'CORE.METADATA.ACTIONS.EDIT' | translate"
[attr.aria-label]="'CORE.METADATA.ACCESSIBILITY.EDIT' | translate"
data-automation-id="meta-data-general-info-edit"
class="adf-edit-icon-buttons">
<mat-expansion-panel-header
class="adf-metadata-properties-header"
[class.adf-metadata-properties-header-expanded]="currentPanel.panelTitle === DefaultPanels.PROPERTIES && currentPanel.expanded">
<adf-content-metadata-header
[title]="'CORE.METADATA.BASIC.HEADER'"
[expanded]="currentPanel.panelTitle === DefaultPanels.PROPERTIES && currentPanel.expanded">
<button
*ngIf="!readOnly && !isPanelEditing(DefaultPanels.PROPERTIES)"
mat-icon-button
(click)="toggleGroupEditing(DefaultPanels.PROPERTIES, $event)"
[attr.title]="'CORE.METADATA.ACTIONS.EDIT' | translate"
[attr.aria-label]="'CORE.METADATA.ACCESSIBILITY.EDIT' | translate"
data-automation-id="meta-data-general-info-edit"
class="adf-edit-icon-buttons">
<mat-icon>mode_edit</mat-icon>
</button>
<div *ngIf="isEditingGeneralInfo" class="adf-metadata-action-buttons">
<button mat-icon-button
[attr.title]="'CORE.METADATA.ACTIONS.CANCEL' | translate"
(click)="onCancelGeneralInfoEdit($event)"
data-automation-id="reset-metadata"
class="adf-metadata-action-buttons-clear">
<div *ngIf="isPanelEditing(DefaultPanels.PROPERTIES)" class="adf-metadata-action-buttons">
<button
mat-icon-button
[attr.title]="'CORE.METADATA.ACTIONS.CANCEL' | translate"
(click)="cancelGroupEditing(DefaultPanels.PROPERTIES, $event)"
data-automation-id="reset-metadata"
class="adf-metadata-action-buttons-clear">
<mat-icon>clear</mat-icon>
</button>
<button mat-icon-button
[attr.title]="'CORE.METADATA.ACTIONS.SAVE' | translate"
(click)="onSaveGeneralInfoChanges($event)"
color="primary"
data-automation-id="save-general-info-metadata"
[disabled]="!hasMetadataChanged">
<button
mat-icon-button
[attr.title]="'CORE.METADATA.ACTIONS.SAVE' | translate"
(click)="saveChanges($event)"
color="primary"
data-automation-id="save-general-info-metadata"
[disabled]="!hasMetadataChanged">
<mat-icon>check</mat-icon>
</button>
</div>
@ -40,7 +48,7 @@
class="adf-metadata-properties-expansion-panel"
(keydown)="keyDown($event)"
[properties]="basicProperties$ | async"
[editable]="isEditingModeGeneralInfo"
[editable]="!readOnly && isPanelEditing(DefaultPanels.PROPERTIES)"
[displayEmpty]="displayEmpty"
[copyToClipboardAction]="copyToClipboardAction"
[useChipsForMultiValueProperty]="useChipsForMultiValueProperty"
@ -49,48 +57,58 @@
</mat-expansion-panel>
<ng-container *ngIf="displayTags">
<mat-expansion-panel hideToggle [(expanded)]="isTagPanelExpanded" class="adf-content-metadata-panel">
<mat-expansion-panel-header class="adf-metadata-properties-header"
[class.adf-metadata-properties-header-expanded]='isTagPanelExpanded'>
<adf-content-metadata-header [title]="'METADATA.BASIC.TAGS'" [expanded]="isTagPanelExpanded">
<button *ngIf="canEditTags"
mat-icon-button
(click)="onToggleTagsEdit($event)"
[attr.title]="'CORE.METADATA.ACTIONS.EDIT' | translate"
[attr.aria-label]="'CORE.METADATA.ACCESSIBILITY.EDIT' | translate"
data-automation-id="showing-tag-input-button"
class="adf-edit-icon-buttons">
<mat-expansion-panel
hideToggle
[expanded]="currentPanel.panelTitle === DefaultPanels.TAGS && currentPanel.expanded"
(opened)="expandPanel(DefaultPanels.TAGS)"
(closed)="closePanel(DefaultPanels.TAGS)"
class="adf-content-metadata-panel"
data-automation-id="adf-content-metadata-tags-panel">
<mat-expansion-panel-header
class="adf-metadata-properties-header"
[class.adf-metadata-properties-header-expanded]="currentPanel.panelTitle === DefaultPanels.TAGS && currentPanel.expanded">
<adf-content-metadata-header [title]="'METADATA.BASIC.TAGS'" [expanded]="currentPanel.panelTitle === DefaultPanels.TAGS && currentPanel.expanded">
<button
*ngIf="!readOnly && !isPanelEditing(DefaultPanels.TAGS)"
mat-icon-button
(click)="toggleGroupEditing(DefaultPanels.TAGS, $event)"
[attr.title]="'CORE.METADATA.ACTIONS.EDIT' | translate"
[attr.aria-label]="'CORE.METADATA.ACCESSIBILITY.EDIT' | translate"
data-automation-id="showing-tag-input-button"
class="adf-edit-icon-buttons">
<mat-icon>mode_edit</mat-icon>
</button>
<div *ngIf="isEditingTags" class="adf-metadata-action-buttons">
<button mat-icon-button
[attr.title]="'CORE.METADATA.ACTIONS.CANCEL' | translate"
(click)="onCancelTagsEdit($event)"
data-automation-id="reset-tags-metadata"
class="adf-metadata-action-buttons-clear">
<div *ngIf="isPanelEditing(DefaultPanels.TAGS)" class="adf-metadata-action-buttons">
<button
mat-icon-button
[attr.title]="'CORE.METADATA.ACTIONS.CANCEL' | translate"
(click)="cancelGroupEditing(DefaultPanels.TAGS, $event)"
data-automation-id="reset-tags-metadata"
class="adf-metadata-action-buttons-clear">
<mat-icon>clear</mat-icon>
</button>
<button mat-icon-button
[attr.title]="'CORE.METADATA.ACTIONS.SAVE' | translate"
(click)="onSaveTagsChanges($event)"
color="primary"
data-automation-id="save-tags-metadata"
[disabled]="!hasMetadataChanged">
<button
mat-icon-button
[attr.title]="'CORE.METADATA.ACTIONS.SAVE' | translate"
(click)="saveChanges($event)"
color="primary"
data-automation-id="save-tags-metadata"
[disabled]="!hasMetadataChanged">
<mat-icon>check</mat-icon>
</button>
</div>
</adf-content-metadata-header>
</adf-content-metadata-header>
</mat-expansion-panel-header>
<div *ngIf="!isEditingModeTags" class="adf-metadata-properties-tags">
<div *ngIf="currentPanel.panelTitle === DefaultPanels.TAGS && !editing" class="adf-metadata-properties-tags">
<span *ngFor="let tag of tags" class="adf-metadata-properties-tag">{{ tag }}</span>
</div>
<div *ngIf="showEmptyTagMessage" class="adf-metadata-no-item-added">
{{ 'METADATA.BASIC.NO_TAGS_ADDED' | translate }}
</div>
<adf-tags-creator
*ngIf="isEditingModeTags"
*ngIf="!readOnly && isPanelEditing(DefaultPanels.TAGS)"
class="adf-metadata-properties-tags"
[tagNameControlVisible]="tagNameControlVisible"
[tagNameControlVisible]="editing"
(tagsChange)="storeTagsToAssign($event)"
[mode]="tagsCreatorMode"
[tags]="assignedTags"
@ -100,48 +118,60 @@
</ng-container>
<ng-container *ngIf="displayCategories">
<mat-expansion-panel hideToggle [(expanded)]="isCategoriesPanelExpanded" class="adf-content-metadata-panel">
<mat-expansion-panel-header class="adf-metadata-properties-header"
[class.adf-metadata-properties-header-expanded]='isCategoriesPanelExpanded'>
<adf-content-metadata-header [title]="'CATEGORIES_MANAGEMENT.CATEGORIES_TITLE'" [expanded]="isCategoriesPanelExpanded">
<button *ngIf="canEditCategories"
mat-icon-button
(click)="onToggleCategoriesEdit($event)"
[attr.title]="'CORE.METADATA.ACTIONS.EDIT' | translate"
[attr.aria-label]="'CORE.METADATA.ACCESSIBILITY.EDIT' | translate"
data-automation-id="meta-data-categories-edit"
class="adf-categories-button adf-edit-icon-buttons">
<mat-expansion-panel
hideToggle
[expanded]="currentPanel.panelTitle === DefaultPanels.CATEGORIES && currentPanel.expanded"
(opened)="expandPanel(DefaultPanels.CATEGORIES)"
(closed)="closePanel(DefaultPanels.CATEGORIES)"
class="adf-content-metadata-panel"
data-automation-id="adf-content-metadata-categories-panel">
<mat-expansion-panel-header
class="adf-metadata-properties-header"
[class.adf-metadata-properties-header-expanded]="currentPanel.panelTitle === DefaultPanels.CATEGORIES && currentPanel.expanded">
<adf-content-metadata-header
[title]="'CATEGORIES_MANAGEMENT.CATEGORIES_TITLE'"
[expanded]="currentPanel.panelTitle === DefaultPanels.CATEGORIES && currentPanel.expanded">
<button
*ngIf="!readOnly && !isPanelEditing(DefaultPanels.CATEGORIES)"
mat-icon-button
(click)="toggleGroupEditing(DefaultPanels.CATEGORIES, $event)"
[attr.title]="'CORE.METADATA.ACTIONS.EDIT' | translate"
[attr.aria-label]="'CORE.METADATA.ACCESSIBILITY.EDIT' | translate"
data-automation-id="meta-data-categories-edit"
class="adf-categories-button adf-edit-icon-buttons">
<mat-icon>mode_edit</mat-icon>
</button>
<div *ngIf="isEditingCategories" class="adf-metadata-action-buttons">
<button mat-icon-button
[attr.title]="'CORE.METADATA.ACTIONS.CANCEL' | translate"
(click)="onCancelCategoriesEdit($event)"
data-automation-id="reset-metadata"
class="adf-metadata-action-buttons-clear">
<div *ngIf="isPanelEditing(DefaultPanels.CATEGORIES)" class="adf-metadata-action-buttons">
<button
mat-icon-button
[attr.title]="'CORE.METADATA.ACTIONS.CANCEL' | translate"
(click)="cancelGroupEditing(DefaultPanels.CATEGORIES, $event)"
data-automation-id="reset-metadata"
class="adf-metadata-action-buttons-clear">
<mat-icon>clear</mat-icon>
</button>
<button mat-icon-button
[attr.title]="'CORE.METADATA.ACTIONS.SAVE' | translate"
(click)="onSaveCategoriesChanges($event)"
color="primary"
data-automation-id="save-categories-metadata"
[disabled]="!hasMetadataChanged">
<button
mat-icon-button
[attr.title]="'CORE.METADATA.ACTIONS.SAVE' | translate"
(click)="saveChanges($event)"
color="primary"
data-automation-id="save-categories-metadata"
[disabled]="!hasMetadataChanged">
<mat-icon>check</mat-icon>
</button>
</div>
</adf-content-metadata-header>
</mat-expansion-panel-header>
<div *ngIf="!isEditingModeCategories">
<div *ngIf="currentPanel.panelTitle === DefaultPanels.CATEGORIES && !editing">
<p *ngFor="let category of categories" class="adf-metadata-categories">{{ category.name }}</p>
</div>
<div *ngIf="showEmptyCategoryMessage" class="adf-metadata-no-item-added">
{{ 'CATEGORIES_MANAGEMENT.NO_CATEGORIES_ADDED' | translate }}
</div>
<adf-categories-management
*ngIf="isEditingModeCategories"
*ngIf="!readOnly && isPanelEditing(DefaultPanels.CATEGORIES)"
class="adf-metadata-categories-header"
[(categoryNameControlVisible)]="categoryControlVisible"
[categoryNameControlVisible]="editing"
[disableRemoval]="saving"
[categories]="categories"
[managementMode]="categoriesManagementMode"
@ -153,67 +183,74 @@
<mat-expansion-panel
*ngFor="let customPanel of customPanels"
[expanded]="canExpandTheCard(customPanel.panelTitle)"
(opened)="customPanel.expanded = true"
(closed)="customPanel.expanded = false"
[expanded]="currentPanel.panelTitle === customPanel.panelTitle && currentPanel.expanded"
(opened)="expandPanel(customPanel.panelTitle)"
(closed)="closePanel(customPanel.panelTitle)"
class="adf-content-metadata-panel"
hideToggle>
<mat-expansion-panel-header class="adf-metadata-properties-header"
[class.adf-metadata-properties-header-expanded]='customPanel.expanded'>
<adf-content-metadata-header class="adf-metadata-custom-panel-title" [title]="customPanel.panelTitle" [expanded]="customPanel.expanded">
<mat-expansion-panel-header
class="adf-metadata-properties-header"
[class.adf-metadata-properties-header-expanded]="currentPanel.panelTitle === customPanel.panelTitle && currentPanel.expanded">
<adf-content-metadata-header
class="adf-metadata-custom-panel-title"
[title]="customPanel.panelTitle"
[expanded]="currentPanel.panelTitle === customPanel.panelTitle && currentPanel.expanded">
</adf-content-metadata-header>
</mat-expansion-panel-header>
<adf-dynamic-component [id]="customPanel.component" [data]="{ node }"></adf-dynamic-component>
</mat-expansion-panel>
<ng-container *ngIf="groupedProperties$ | async; else loading; let groupedProperties">
<div *ngFor="let group of groupedProperties; let first = first;"
class="adf-metadata-grouped-properties-container">
<div *ngFor="let group of groupedProperties; let first = first" class="adf-metadata-grouped-properties-container">
<mat-expansion-panel
[attr.data-automation-id]="'adf-metadata-group-' + group.title"
[expanded]="canExpandTheCard(group.title) || !displayDefaultProperties && first || group.expanded"
(opened)="group.expanded = true"
(closed)="group.expanded = false"
[expanded]="currentPanel.panelTitle === group.title && currentPanel.expanded"
(opened)="expandPanel(group.title)"
(closed)="closePanel(group.title)"
class="adf-content-metadata-panel"
hideToggle>
<mat-expansion-panel-header class="adf-metadata-properties-header"
[class.adf-metadata-properties-header-expanded]='group.expanded'>
<adf-content-metadata-header [title]="group.title" [expanded]="group.expanded">
<button *ngIf="hasGroupToggleEdit(group)"
mat-icon-button
[attr.title]="'CORE.METADATA.ACTIONS.EDIT' | translate"
[attr.aria-label]="'CORE.METADATA.ACCESSIBILITY.EDIT' | translate"
data-automation-id="meta-data-card-toggle-edit"
class="adf-edit-icon-buttons"
(click)="onToggleGroupEdit(group, $event)">
<mat-expansion-panel-header
class="adf-metadata-properties-header"
[class.adf-metadata-properties-header-expanded]="currentPanel.panelTitle === group.title && currentPanel.expanded">
<adf-content-metadata-header [title]="group.title" [expanded]="currentPanel.panelTitle === group.title && currentPanel.expanded">
<button
*ngIf="group.editable && !this.readOnly && !isPanelEditing(group.title)"
mat-icon-button
[attr.title]="'CORE.METADATA.ACTIONS.EDIT' | translate"
[attr.aria-label]="'CORE.METADATA.ACCESSIBILITY.EDIT' | translate"
data-automation-id="meta-data-card-toggle-edit"
class="adf-edit-icon-buttons"
(click)="toggleGroupEditing(group.title, $event)">
<mat-icon>mode_edit</mat-icon>
</button>
<div class="adf-metadata-action-buttons" *ngIf="isGroupToggleEditing(group)">
<button mat-icon-button
[attr.title]="'CORE.METADATA.ACTIONS.CANCEL' | translate"
(click)="onCancelGroupEdit(group, $event)"
data-automation-id="reset-metadata"
class="adf-metadata-action-buttons-clear">
<div class="adf-metadata-action-buttons" *ngIf="group.editable && isPanelEditing(group.title)">
<button
mat-icon-button
[attr.title]="'CORE.METADATA.ACTIONS.CANCEL' | translate"
(click)="cancelGroupEditing(group.title, $event)"
data-automation-id="reset-metadata"
class="adf-metadata-action-buttons-clear">
<mat-icon>clear</mat-icon>
</button>
<button mat-icon-button
[attr.title]="'CORE.METADATA.ACTIONS.SAVE' | translate"
(click)="onSaveGroupChanges(group, $event)"
color="primary"
data-automation-id="save-metadata"
[disabled]="!hasMetadataChanged">
<button
mat-icon-button
[attr.title]="'CORE.METADATA.ACTIONS.SAVE' | translate"
(click)="saveChanges($event)"
color="primary"
data-automation-id="save-metadata"
[disabled]="!hasMetadataChanged">
<mat-icon>check</mat-icon>
</button>
</div>
</adf-content-metadata-header>
</mat-expansion-panel-header>
<div *ngIf="!showGroup(group) && !group.editable" class="adf-metadata-no-item-added">
{{ 'METADATA.BASIC.NO_ITEMS_MESSAGE' | translate: { groupTitle: group.title | translate } }}
{{ 'METADATA.BASIC.NO_ITEMS_MESSAGE' | translate : { groupTitle: group.title | translate } }}
</div>
<adf-card-view
(keydown)="keyDown($event)"
[properties]="group.properties"
[editable]="group.editable"
[editable]="!readOnly && group.editable && isPanelEditing(group.title)"
[displayEmpty]="displayEmpty"
[copyToClipboardAction]="copyToClipboardAction"
[useChipsForMultiValueProperty]="useChipsForMultiValueProperty"
@ -225,7 +262,6 @@
</ng-container>
<ng-template #loading>
<mat-progress-bar mode="indeterminate" [attr.aria-label]="'DATA_LOADING' | translate">
</mat-progress-bar>
<mat-progress-bar mode="indeterminate" [attr.aria-label]="'DATA_LOADING' | translate"> </mat-progress-bar>
</ng-template>
</mat-accordion>

View File

@ -35,7 +35,7 @@ import {
UpdateNotification
} from '@alfresco/adf-core';
import { ContentMetadataService } from '../../services/content-metadata.service';
import { CardViewGroup, PresetConfig, ContentMetadataCustomPanel } from '../../interfaces/content-metadata.interfaces';
import { CardViewGroup, PresetConfig, ContentMetadataCustomPanel, ContentMetadataPanel } from '../../interfaces/content-metadata.interfaces';
import { catchError, debounceTime, map, takeUntil } from 'rxjs/operators';
import { CardViewContentUpdateService } from '../../../common/services/card-view-content-update.service';
import { NodesApiService } from '../../../common/services/nodes-api.service';
@ -48,6 +48,12 @@ import { ContentService } from '../../../common/services/content.service';
const DEFAULT_SEPARATOR = ', ';
enum DefaultPanels {
PROPERTIES = 'Properties',
TAGS = 'Tags',
CATEGORIES = 'Categories'
}
@Component({
selector: 'adf-content-metadata',
templateUrl: './content-metadata.component.html',
@ -124,26 +130,23 @@ export class ContentMetadataComponent implements OnChanges, OnInit, OnDestroy {
private classifiableChangedSubject = new Subject<void>();
private _saving = false;
DefaultPanels = DefaultPanels;
multiValueSeparator: string;
basicProperties$: Observable<CardViewItem[]>;
groupedProperties$: Observable<CardViewGroup[]>;
changedProperties = {};
hasMetadataChanged = false;
tagNameControlVisible = false;
assignedCategories: Category[] = [];
categories: Category[] = [];
categoriesManagementMode = CategoriesManagementMode.ASSIGN;
categoryControlVisible = false;
classifiableChanged = this.classifiableChangedSubject.asObservable();
isGeneralPanelExpanded = true;
isTagPanelExpanded: boolean;
isCategoriesPanelExpanded: boolean;
currentGroup: CardViewGroup;
isEditingModeGeneralInfo = false;
isEditingModeTags = false;
isEditingModeCategories = false;
editing = false;
editedPanelTitle = '';
currentPanel: ContentMetadataPanel = {
expanded: false,
panelTitle: ''
};
constructor(
private contentMetadataService: ContentMetadataService,
@ -180,9 +183,8 @@ export class ContentMetadataComponent implements OnChanges, OnInit, OnDestroy {
this.loadProperties(this.node);
this.verifyAllowableOperations();
if (this.displayAspect === 'Properties') {
this.isGeneralPanelExpanded = true;
}
this.currentPanel.panelTitle = this.displayAspect ?? this.DefaultPanels.PROPERTIES;
this.currentPanel.expanded = true;
}
private verifyAllowableOperations() {
@ -207,6 +209,10 @@ export class ContentMetadataComponent implements OnChanges, OnInit, OnDestroy {
return this._saving;
}
isPanelEditing(panelTitle: string): boolean {
return this.editing && ((this.currentPanel.panelTitle === panelTitle && this.editedPanelTitle === panelTitle) || this.editedPanelTitle === panelTitle);
}
protected handleUpdateError(error: Error) {
let statusCode = 0;
@ -230,10 +236,17 @@ export class ContentMetadataComponent implements OnChanges, OnInit, OnDestroy {
if (changes.node && !changes.node.firstChange) {
this.loadProperties(changes.node.currentValue);
}
if(changes?.readOnly && changes?.readOnly?.currentValue) {
this.cancelEditing();
this.groupedProperties$ = this.contentMetadataService.getGroupedProperties(this.node, this.preset);
if (changes.readOnly?.currentValue) {
this.resetEditing();
this.loadProperties(this.node);
}
if (changes.displayAspect?.currentValue) {
this.currentPanel.panelTitle = changes.displayAspect.currentValue;
this.currentPanel.expanded = true;
}
}
ngOnDestroy() {
@ -254,35 +267,10 @@ export class ContentMetadataComponent implements OnChanges, OnInit, OnDestroy {
});
}
private onSave(event?: MouseEvent) {
saveChanges(event?: MouseEvent) {
event?.stopPropagation();
this.onSaveChanges();
this.cancelEditing();
}
onSaveGeneralInfoChanges(event?: MouseEvent) {
this.onSave(event);
}
onSaveTagsChanges(event?: MouseEvent) {
this.onSave(event);
}
onSaveCategoriesChanges(event?: MouseEvent) {
this.onSave(event);
}
onSaveGroupChanges(group: CardViewGroup, event?: MouseEvent) {
this.onSave(event);
group.editable = false;
}
private onSaveChanges() {
this.resetEditing();
this._saving = true;
this.tagNameControlVisible = false;
this.categoryControlVisible = false;
if (this.hasContentTypeChanged(this.changedProperties)) {
this.contentMetadataService.openConfirmDialog(this.changedProperties).subscribe(() => {
@ -318,172 +306,52 @@ export class ContentMetadataComponent implements OnChanges, OnInit, OnDestroy {
revertChanges() {
this.changedProperties = {};
this.hasMetadataChanged = false;
this.tagNameControlVisible = false;
this.categoryControlVisible = false;
}
// Returns the editing state of the panel
isEditingPanel(): boolean {
return (
(this.isEditingModeGeneralInfo || this.isEditingModeTags || this.isEditingModeCategories || this.currentGroup?.editable) && this.hasMetadataChanged
);
}
onToggleGeneralInfoEdit(event?: MouseEvent) {
event?.stopPropagation();
if (this.isEditingPanel()) {
this.notificationService.showError('METADATA.BASIC.SAVE_OR_DISCARD_CHANGES');
return;
}
const currentMode = this.isEditingModeGeneralInfo;
this.cancelEditing();
this.isEditingModeGeneralInfo = !currentMode;
this.isGeneralPanelExpanded = true;
this.groupedProperties$ = this.contentMetadataService.getGroupedProperties(this.node, this.preset);
}
onToggleTagsEdit(event?: MouseEvent) {
event?.stopPropagation();
if (this.isEditingPanel()) {
this.notificationService.showError('METADATA.BASIC.SAVE_OR_DISCARD_CHANGES');
return;
}
const currentValue = this.isEditingModeTags;
this.cancelEditing();
this.isEditingModeTags = !currentValue;
this.isTagPanelExpanded = this.isEditingModeTags;
this.tagNameControlVisible = true;
this.groupedProperties$ = this.contentMetadataService.getGroupedProperties(this.node, this.preset);
}
private cancelEditing() {
this.isEditingModeGeneralInfo = false;
this.isEditingModeCategories = false;
this.isEditingModeTags = false;
}
onCancelGeneralInfoEdit(event?: MouseEvent) {
event?.stopPropagation();
this.cancelEditing();
this.revertChanges();
this.basicProperties$ = this.getProperties(this.node);
}
onCancelCategoriesEdit(event?: MouseEvent) {
event?.stopPropagation();
this.cancelEditing();
this.revertChanges();
this.loadCategoriesForNode(this.node.id);
const aspectNames = this.node.aspectNames || [];
if (!aspectNames.includes('generalclassifiable')) {
this.categories = [];
this.classifiableChangedSubject.next();
}
}
onCancelTagsEdit(event?: MouseEvent) {
event?.stopPropagation();
this.cancelEditing();
this.revertChanges();
this.basicProperties$ = this.getProperties(this.node);
this.loadTagsForNode(this.node.id);
}
onCancelGroupEdit(group: CardViewGroup, event?: MouseEvent) {
event?.stopPropagation();
this.cancelEditing();
this.revertChanges();
group.editable = false;
this.groupedProperties$ = this.contentMetadataService.getGroupedProperties(this.node, this.preset);
}
onToggleCategoriesEdit(event?: MouseEvent) {
event?.stopPropagation();
if (this.isEditingPanel()) {
this.notificationService.showError('METADATA.BASIC.SAVE_OR_DISCARD_CHANGES');
return;
}
const currentValue = this.isEditingModeCategories;
this.cancelEditing();
this.isEditingModeCategories = !currentValue;
this.isCategoriesPanelExpanded = this.isEditingModeCategories;
this.categoryControlVisible = true;
this.groupedProperties$ = this.contentMetadataService.getGroupedProperties(this.node, this.preset);
}
onToggleGroupEdit(group: CardViewGroup, event?: MouseEvent) {
event?.stopPropagation();
if (this.isEditingPanel()) {
this.notificationService.showError('METADATA.BASIC.SAVE_OR_DISCARD_CHANGES');
return;
}
this.cancelEditing();
if (this.currentGroup && this.currentGroup.title !== group.title) {
this.currentGroup.editable = false;
}
group.editable = !group.editable;
this.currentGroup = group.editable ? group : null;
if (group.editable) {
group.expanded = true;
}
}
get showEmptyTagMessage(): boolean {
return this.tags?.length === 0 && !this.isEditingModeTags;
return this.tags?.length === 0 && this.currentPanel.panelTitle === 'Tags' && !this.editing;
}
get showEmptyCategoryMessage(): boolean {
return this.categories?.length === 0 && !this.isEditingModeCategories;
return this.categories?.length === 0 && this.currentPanel.panelTitle === 'Categories' && !this.editing;
}
get canEditGeneralInfo(): boolean {
return !this.isEditingModeGeneralInfo && !this.readOnly;
toggleGroupEditing(panelTitle: string, event?: MouseEvent) {
event?.stopPropagation();
if (this.editing && this.hasMetadataChanged) {
this.notificationService.showError('METADATA.BASIC.SAVE_OR_DISCARD_CHANGES');
return;
}
this.editing = true;
this.editedPanelTitle = panelTitle;
this.expandPanel(panelTitle);
}
get isEditingGeneralInfo(): boolean {
return this.isEditingModeGeneralInfo && !this.readOnly;
cancelGroupEditing(panelTitle: string, event?: MouseEvent) {
event?.stopPropagation();
this.resetEditing();
this.revertChanges();
const loadBasicProps = panelTitle === this.DefaultPanels.PROPERTIES;
const loadTags = panelTitle === this.DefaultPanels.TAGS;
const loadCategories = panelTitle === this.DefaultPanels.CATEGORIES;
const loadGroupedProps = !loadBasicProps && !loadTags && !loadCategories;
this.loadProperties(this.node, loadBasicProps, loadGroupedProps, loadTags, loadCategories);
}
get canEditTags(): boolean {
return !this.isEditingModeTags && !this.readOnly;
expandPanel(panelTitle: string) {
this.currentPanel.panelTitle = panelTitle;
this.currentPanel.expanded = true;
}
get isEditingTags(): boolean {
return this.isEditingModeTags && !this.readOnly;
closePanel(panelTitle: string) {
if (this.currentPanel.panelTitle === panelTitle) {
this.currentPanel.expanded = false;
}
}
get canEditCategories(): boolean {
return !this.isEditingModeCategories && !this.readOnly;
}
get isEditingCategories(): boolean {
return this.isEditingModeCategories && !this.readOnly;
}
hasGroupToggleEdit(group: CardViewGroup): boolean {
return !group.editable && !this.readOnly;
}
isGroupToggleEditing(group: CardViewGroup): boolean {
return group.editable && !this.readOnly;
resetEditing() {
this.editing = false;
this.editedPanelTitle = '';
}
showGroup(group: CardViewGroup): boolean {
@ -492,10 +360,6 @@ export class ContentMetadataComponent implements OnChanges, OnInit, OnDestroy {
return properties.length > 0;
}
canExpandTheCard(groupTitle: string): boolean {
return groupTitle === this.displayAspect;
}
keyDown(event: KeyboardEvent) {
if (event.keyCode === 37 || event.keyCode === 39) { // ArrowLeft && ArrowRight
event.stopPropagation();
@ -513,6 +377,7 @@ export class ContentMetadataComponent implements OnChanges, OnInit, OnDestroy {
this.cardViewContentUpdateService.updateElement(this.targetProperty);
this.handleUpdateError(err);
this._saving = false;
this.loadProperties(this.node);
return of(null);
})
)
@ -548,16 +413,20 @@ export class ContentMetadataComponent implements OnChanges, OnInit, OnDestroy {
}
}
private loadProperties(node: Node) {
private loadProperties(node: Node, loadBasicProps = true, loadGroupedProps = true, loadTags = true, loadCategories = true) {
if (node) {
this.basicProperties$ = this.getProperties(node);
this.groupedProperties$ = this.contentMetadataService.getGroupedProperties(node, this.preset);
if (loadBasicProps) {
this.basicProperties$ = this.getProperties(node);
}
if (loadGroupedProps) {
this.groupedProperties$ = this.contentMetadataService.getGroupedProperties(node, this.preset);
}
if (this.displayTags) {
if (this.displayTags && loadTags) {
this.loadTagsForNode(node.id);
}
if (this.displayCategories) {
if (this.displayCategories && loadCategories) {
this.loadCategoriesForNode(node.id);
const aspectNames = node.aspectNames || [];

View File

@ -20,6 +20,6 @@ import { CardViewItem } from '@alfresco/adf-core';
export interface CardViewGroup {
title: string;
properties: CardViewItem[];
editable?: boolean;
editable: boolean;
expanded?: boolean;
}

View File

@ -15,8 +15,8 @@
* limitations under the License.
*/
export interface ContentMetadataCustomPanel {
panelTitle: string;
import { ContentMetadataPanel } from './content-metadata-panel.interface';
export interface ContentMetadataCustomPanel extends ContentMetadataPanel {
component: string;
expanded?: boolean;
}

View File

@ -0,0 +1,21 @@
/*!
* @license
* Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export interface ContentMetadataPanel {
panelTitle: string;
expanded?: boolean;
}

View File

@ -25,3 +25,4 @@ export * from './indifferent-config.interface';
export * from './layout-oriented-config.interface';
export * from './preset-config.interface';
export * from './content-metadata-custom-panel.interface';
export * from './content-metadata-panel.interface';

View File

@ -84,7 +84,8 @@ describe('PropertyGroupTranslatorService', () => {
dataType: 'd:text',
defaultValue: 'defaultValue',
mandatory: false,
multiValued: false
multiValued: false,
editable: true
},
{
name: 'FAS:ALOY',
@ -98,10 +99,11 @@ describe('PropertyGroupTranslatorService', () => {
propertyValues = { 'FAS:PLAGUE': 'The Chariot Line' };
const cardViewGroup = service.translateToCardViewGroups(propertyGroups, propertyValues, null);
expect(cardViewGroup[0].properties.length).toBe(2);
expect(cardViewGroup[0].properties[0] instanceof CardViewTextItemModel).toBeTruthy('First property should be instance of CardViewTextItemModel');
expect(cardViewGroup[0].properties[1] instanceof CardViewTextItemModel).toBeTruthy('Second property should be instance of CardViewTextItemModel');
const cardViewGroup = service.translateToCardViewGroups(propertyGroups, propertyValues, null)[0];
expect(cardViewGroup.properties.length).toBe(2);
expect(cardViewGroup.properties[0] instanceof CardViewTextItemModel).toBeTruthy('First property should be instance of CardViewTextItemModel');
expect(cardViewGroup.properties[1] instanceof CardViewTextItemModel).toBeTruthy('Second property should be instance of CardViewTextItemModel');
expect(cardViewGroup.editable).toBeTrue();
});
it('should translate EVERY property in EVERY group properly', () => {
@ -113,7 +115,8 @@ describe('PropertyGroupTranslatorService', () => {
dataType: 'd:text',
defaultValue: 'defaultvalue',
mandatory: false,
multiValued: false
multiValued: false,
editable: false
}]
}),
Object.assign({}, propertyGroup, {
@ -123,17 +126,20 @@ describe('PropertyGroupTranslatorService', () => {
dataType: 'd:text',
defaultValue: 'defaultvalue',
mandatory: false,
multiValued: false
multiValued: false,
editable: false
}]
})
);
propertyValues = { 'FAS:PLAGUE': 'The Chariot Line' };
const cardViewGroup = service.translateToCardViewGroups(propertyGroups, propertyValues, null);
expect(cardViewGroup.length).toBe(2);
expect(cardViewGroup[0].properties[0] instanceof CardViewTextItemModel).toBeTruthy('First group\'s property should be instance of CardViewTextItemModel');
expect(cardViewGroup[1].properties[0] instanceof CardViewTextItemModel).toBeTruthy('Second group\'s property should be instance of CardViewTextItemModel');
const cardViewGroups = service.translateToCardViewGroups(propertyGroups, propertyValues, null);
expect(cardViewGroups.length).toBe(2);
const firstCardViewGroup = cardViewGroups[0];
expect(firstCardViewGroup.properties[0] instanceof CardViewTextItemModel).toBeTruthy('First group\'s property should be instance of CardViewTextItemModel');
expect(cardViewGroups[1].properties[0] instanceof CardViewTextItemModel).toBeTruthy('Second group\'s property should be instance of CardViewTextItemModel');
expect(firstCardViewGroup.editable).toBeFalse();
});
it('should log an error if unrecognised type is found', () => {

View File

@ -64,6 +64,7 @@ export class PropertyGroupTranslatorService {
return propertyGroups.map((propertyGroup) => {
const translatedPropertyGroup: any = Object.assign({}, propertyGroup);
translatedPropertyGroup.properties = this.translateArray(propertyGroup.properties, propertyValues, definition);
translatedPropertyGroup.editable = translatedPropertyGroup.properties.some((property) => property.editable);
return translatedPropertyGroup;
});
}