[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 ## Basic usage
```ts ```ts
export interface ContentMetadataCustomPanel { export interface ContentMetadataCustomPanel extends ContentMetadataPanel {
panelTitle: string;
component: string; component: string;
} }
``` ```
@ -22,10 +21,10 @@ export interface ContentMetadataCustomPanel {
| Name | Type | Default value | Description | | 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. | | component | `string` | | Id of the registered [Dynamic component](../../extensions/components/dynamic.component.md) to be displayed inside expansion panel. |
## See also ## See also
- [ContentMetadataCardComponent](../components/content-metadata-card.component.md) - [ContentMetadataCardComponent](../components/content-metadata-card.component.md)
- [Dynamic Component](../../extensions/components/dynamic.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 { adf-content-metadata-header {
display: flex; display: flex;
align-items: center; align-items: center;
flex: 1; width: 100%;
} }
.adf-metadata-properties-title { .adf-metadata-properties-title {
display: block;
overflow: hidden;
text-overflow: ellipsis;
font-weight: 700; font-weight: 700;
font-size: 15px; font-size: 15px;
padding-left: 12px; padding-left: 12px;
@ -44,7 +47,7 @@ import { TranslateModule } from '@ngx-translate/core';
template: ` template: `
<ng-container> <ng-container>
<mat-icon>{{ expanded ? 'expand_more' : 'chevron_right' }}</mat-icon> <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-content></ng-content>
</ng-container> </ng-container>
` `

View File

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

View File

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

View File

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

View File

@ -15,8 +15,8 @@
* limitations under the License. * limitations under the License.
*/ */
export interface ContentMetadataCustomPanel { import { ContentMetadataPanel } from './content-metadata-panel.interface';
panelTitle: string;
export interface ContentMetadataCustomPanel extends ContentMetadataPanel {
component: string; 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 './layout-oriented-config.interface';
export * from './preset-config.interface'; export * from './preset-config.interface';
export * from './content-metadata-custom-panel.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', dataType: 'd:text',
defaultValue: 'defaultValue', defaultValue: 'defaultValue',
mandatory: false, mandatory: false,
multiValued: false multiValued: false,
editable: true
}, },
{ {
name: 'FAS:ALOY', name: 'FAS:ALOY',
@ -98,10 +99,11 @@ describe('PropertyGroupTranslatorService', () => {
propertyValues = { 'FAS:PLAGUE': 'The Chariot Line' }; propertyValues = { 'FAS:PLAGUE': 'The Chariot Line' };
const cardViewGroup = service.translateToCardViewGroups(propertyGroups, propertyValues, null); const cardViewGroup = service.translateToCardViewGroups(propertyGroups, propertyValues, null)[0];
expect(cardViewGroup[0].properties.length).toBe(2); expect(cardViewGroup.properties.length).toBe(2);
expect(cardViewGroup[0].properties[0] instanceof CardViewTextItemModel).toBeTruthy('First property should be instance of CardViewTextItemModel'); expect(cardViewGroup.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'); 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', () => { it('should translate EVERY property in EVERY group properly', () => {
@ -113,7 +115,8 @@ describe('PropertyGroupTranslatorService', () => {
dataType: 'd:text', dataType: 'd:text',
defaultValue: 'defaultvalue', defaultValue: 'defaultvalue',
mandatory: false, mandatory: false,
multiValued: false multiValued: false,
editable: false
}] }]
}), }),
Object.assign({}, propertyGroup, { Object.assign({}, propertyGroup, {
@ -123,17 +126,20 @@ describe('PropertyGroupTranslatorService', () => {
dataType: 'd:text', dataType: 'd:text',
defaultValue: 'defaultvalue', defaultValue: 'defaultvalue',
mandatory: false, mandatory: false,
multiValued: false multiValued: false,
editable: false
}] }]
}) })
); );
propertyValues = { 'FAS:PLAGUE': 'The Chariot Line' }; propertyValues = { 'FAS:PLAGUE': 'The Chariot Line' };
const cardViewGroup = service.translateToCardViewGroups(propertyGroups, propertyValues, null); const cardViewGroups = service.translateToCardViewGroups(propertyGroups, propertyValues, null);
expect(cardViewGroup.length).toBe(2); expect(cardViewGroups.length).toBe(2);
expect(cardViewGroup[0].properties[0] instanceof CardViewTextItemModel).toBeTruthy('First group\'s property should be instance of CardViewTextItemModel'); const firstCardViewGroup = cardViewGroups[0];
expect(cardViewGroup[1].properties[0] instanceof CardViewTextItemModel).toBeTruthy('Second group\'s property should be instance of CardViewTextItemModel'); 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', () => { it('should log an error if unrecognised type is found', () => {

View File

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