[ACS-5645]Added edit functionality for each panel and updated test cases

This commit is contained in:
Yasa-Nataliya
2023-08-10 18:33:16 +05:30
parent 9bddd1a7c3
commit 26c02c25d7
19 changed files with 541 additions and 446 deletions
@@ -1,14 +1,20 @@
<div class="adf-categories-management"> <div class="adf-categories-management">
<p *ngIf="!categories.length && !categoryNameControlVisible" <div *ngIf="((!categoryNameControlVisible && categories.length)) || categoryNameControlVisible"
class="adf-no-categories-message"> class="adf-category-name-field">
{{ noCategoriesMsg | translate }} <input #categoryNameInput
</p> matInput autocomplete="off"
<div class="adf-categories-list" [formControl]="categoryNameControl"
[class.adf-categories-list-fixed]="!categoryNameControlVisible"> (keyup.enter)="addCategory()"
aria-labelledby="adf-category-name-input-label"
placeholder="{{'CATEGORIES_MANAGEMENT.INPUT_PLACEHOLDER' | translate }}"
adf-auto-focus />
<mat-error [hidden]="!categoryNameControl.invalid">{{ categoryNameErrorMessageKey | translate }}</mat-error>
</div>
<div class="adf-categories-list" [class.adf-categories-list-fixed]="!categoryNameControlVisible">
<span <span
*ngFor="let category of categories" *ngFor="let category of categories"
[class.adf-categories-padded]="!isCRUDMode" [class.adf-categories-padded]="!isCRUDMode"
class="adf-assigned-categories"> class="adf-assigned-categories">
{{ category.name }} {{ category.name }}
<button <button
data-automation-id="categories-remove-category-button" data-automation-id="categories-remove-category-button"
@@ -21,38 +27,14 @@
</button> </button>
</span> </span>
</div> </div>
<div *ngIf="((!categoryNameControlVisible && categories.length)) || categoryNameControlVisible" <p *ngIf="!categories.length" class="adf-no-categories-message">
[hidden]="!categoryNameControlVisible" {{ noCategoriesMsg | translate }}
class="adf-category-name-field"> </p>
<mat-form-field>
<mat-icon matPrefix>search</mat-icon>
<mat-label id="adf-category-name-input-label">
{{ 'CATEGORIES_MANAGEMENT.NAME' | translate }}
</mat-label>
<input
#categoryNameInput
matInput
autocomplete="off"
[formControl]="categoryNameControl"
(keyup.enter)="addCategory()"
aria-labelledby="adf-category-name-input-label"
adf-auto-focus
/>
<mat-error [hidden]="!categoryNameControl.invalid">{{ categoryNameErrorMessageKey | translate }}</mat-error>
</mat-form-field>
<button
mat-icon-button
[class.adf-btn-padded]="!isCRUDMode"
(click)="hideNameInput()"
[attr.title]="'CATEGORIES_MANAGEMENT.HIDE_INPUT' | translate">
<mat-icon>remove</mat-icon>
</button>
</div>
</div> </div>
<div class="adf-existing-categories-panel" *ngIf="existingCategoriesPanelVisible"> <div class="adf-existing-categories-panel" *ngIf="existingCategoriesPanelVisible">
<ng-container *ngIf="isCRUDMode && (!existingCategoriesLoading || existingCategories)"> <ng-container *ngIf="isCRUDMode && (!existingCategoriesLoading || existingCategories)">
<span class="adf-create-category-label" <span class="adf-create-category-label"
(click)="addCategory()" (click)="addCategory()"
[hidden]="categoryNameControl.invalid || typing"> [hidden]="categoryNameControl.invalid || typing">
{{ 'CATEGORIES_MANAGEMENT.GENERIC_CREATE' | translate : { name: categoryNameControl.value } }} {{ 'CATEGORIES_MANAGEMENT.GENERIC_CREATE' | translate : { name: categoryNameControl.value } }}
</span> </span>
@@ -77,8 +59,8 @@
</mat-selection-list> </mat-selection-list>
</ng-container> </ng-container>
<mat-spinner <mat-spinner
*ngIf="existingCategoriesLoading" *ngIf="existingCategoriesLoading"
[diameter]="50" [diameter]="50"
[attr.aria-label]="'CATEGORIES_MANAGEMENT.LOADING' | translate"> [attr.aria-label]="'CATEGORIES_MANAGEMENT.LOADING' | translate">
</mat-spinner> </mat-spinner>
</div> </div>
@@ -1,8 +1,15 @@
.adf-categories-management { .adf-categories-management {
padding-top: 12px;
.adf-category-name-field { .adf-category-name-field {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
width: 100%; width: 100%;
background: var(--adf-metadata-tags-background-color);
height: 32px;
border-radius: 12px;
align-items: center;
padding: 0 12px;
mat-form-field { mat-form-field {
width: 100%; width: 100%;
@@ -34,7 +41,6 @@
} }
.adf-categories-list { .adf-categories-list {
padding-bottom: 10px;
.mat-list-base .mat-list-item, .mat-list-base .mat-list-item,
.mat-list-base .mat-list-option { .mat-list-base .mat-list-option {
@@ -171,34 +171,13 @@ describe('CategoriesManagementComponent', () => {
component.categoryNameControlVisible = false; component.categoryNameControlVisible = false;
fixture.detectChanges(); fixture.detectChanges();
const categoryControl: HTMLDivElement = fixture.debugElement.query(By.css('.adf-category-name-field')).nativeElement; const categoryControl: HTMLDivElement = fixture.debugElement.query(By.css('.adf-category-name-field')).nativeElement;
expect(categoryControl.hidden).toBeTrue(); expect(categoryControl.hidden).toBeFalse();
}); });
it('should be visible when categoryNameControlVisible is true', () => { it('should be visible when categoryNameControlVisible is true', () => {
const categoryControl = fixture.debugElement.query(By.css('.adf-category-name-field')); const categoryControl = fixture.debugElement.query(By.css('.adf-category-name-field'));
expect(categoryControl).toBeTruthy(); expect(categoryControl).toBeTruthy();
}); });
it('should have correct label and hide button', () => {
const categoryControlLabel = fixture.debugElement.query(By.css('#adf-category-name-input-label')).nativeElement;
const categoryControlHideBtn: HTMLButtonElement = fixture.debugElement.query(By.css('.adf-category-name-field button')).nativeElement;
expect(categoryControlHideBtn).toBeTruthy();
expect(categoryControlHideBtn.attributes.getNamedItem('title').textContent.trim()).toBe('CATEGORIES_MANAGEMENT.HIDE_INPUT');
expect(categoryControlLabel.textContent.trim()).toBe('CATEGORIES_MANAGEMENT.NAME');
});
it('should hide category control and existing categories panel on clicking hide button', () => {
const categoryControlHideBtn: HTMLButtonElement = fixture.debugElement.query(By.css('.adf-category-name-field button')).nativeElement;
const controlVisibilityChangeSpy = spyOn(component.categoryNameControlVisibleChange, 'emit').and.callThrough();
categoryControlHideBtn.click();
fixture.detectChanges();
const categoryControl: HTMLDivElement = fixture.debugElement.query(By.css('.adf-category-name-field')).nativeElement;
expect(categoryControl.hidden).toBeTrue();
expect(component.categoryNameControlVisible).toBeFalse();
expect(component.existingCategoriesPanelVisible).toBeFalse();
expect(controlVisibilityChangeSpy).toHaveBeenCalledOnceWith(false);
});
}); });
describe('Spinner', () => { describe('Spinner', () => {
@@ -271,7 +250,7 @@ describe('CategoriesManagementComponent', () => {
it('should have no required validator set for category control', () => { it('should have no required validator set for category control', () => {
expect(component.categoryNameControl.hasValidator(Validators.required)).toBeFalse(); expect(component.categoryNameControl.hasValidator(Validators.required)).toBeFalse();
}); });
it('should display validation error when searching for empty category', fakeAsync(() => { it('should display validation error when searching for empty category', fakeAsync(() => {
typeCategory(' '); typeCategory(' ');
@@ -412,18 +391,6 @@ describe('CategoriesManagementComponent', () => {
expect(categoriesChangeSpy).toHaveBeenCalledOnceWith(component.categories); expect(categoriesChangeSpy).toHaveBeenCalledOnceWith(component.categories);
})); }));
it('should clear and hide input after category is created', fakeAsync(() => {
const controlVisibilityChangeSpy = spyOn(component.categoryNameControlVisibleChange, 'emit');
createCategory('test');
const categoryControl: HTMLDivElement = fixture.debugElement.query(By.css('.adf-category-name-field')).nativeElement;
expect(categoryControl.hidden).toBeTrue();
expect(controlVisibilityChangeSpy).toHaveBeenCalledOnceWith(false);
expect(getExistingCategoriesList()).toEqual([]);
expect(component.categoryNameControl.value).toBe('');
expect(component.categoryNameControl.untouched).toBeTrue();
}));
it('should be able to remove added category', fakeAsync(() => { it('should be able to remove added category', fakeAsync(() => {
createCategory('test'); createCategory('test');
@@ -221,7 +221,6 @@ export class CategoriesManagementComponent implements OnInit, OnDestroy {
const newCatName = this.categoryNameControl.value.trim(); const newCatName = this.categoryNameControl.value.trim();
const newCat = new Category({ id: newCatName, name: newCatName }); const newCat = new Category({ id: newCatName, name: newCatName });
this.categories.push(newCat); this.categories.push(newCat);
this.hideNameInput();
this.categoryNameControl.setValue(''); this.categoryNameControl.setValue('');
this.categoryNameControl.markAsUntouched(); this.categoryNameControl.markAsUntouched();
this._existingCategories = null; this._existingCategories = null;
@@ -23,24 +23,6 @@
data-automation-id="meta-data-card-edit-aspect"> data-automation-id="meta-data-card-edit-aspect">
<mat-icon>menu</mat-icon> <mat-icon>menu</mat-icon>
</button> </button>
<button *ngIf="!readOnly && hasAllowableOperations()"
mat-icon-button
(click)="toggleEdit()"
[attr.title]="'CORE.METADATA.ACTIONS.EDIT' | translate"
[attr.aria-label]="'CORE.METADATA.ACCESSIBILITY.EDIT' | translate"
data-automation-id="meta-data-card-toggle-edit">
<mat-icon>mode_edit</mat-icon>
</button>
</div> </div>
<button *ngIf="displayDefaultProperties" mat-button (click)="toggleExpanded()" data-automation-id="meta-data-card-toggle-expand">
<ng-container *ngIf="!expanded">
<span data-automation-id="meta-data-card-toggle-expand-label">{{ 'ADF_VIEWER.SIDEBAR.METADATA.MORE_INFORMATION' | translate }}</span>
<mat-icon>keyboard_arrow_down</mat-icon>
</ng-container>
<ng-container *ngIf="expanded">
<span data-automation-id="meta-data-card-toggle-expand-label">{{ 'ADF_VIEWER.SIDEBAR.METADATA.LESS_INFORMATION' | translate }}</span>
<mat-icon>keyboard_arrow_up</mat-icon>
</ng-container>
</button>
</mat-card-footer> </mat-card-footer>
</mat-card> </mat-card>
@@ -145,47 +145,6 @@ describe('ContentMetadataCardComponent', () => {
expect(contentMetadataComponent).toBeNull(); expect(contentMetadataComponent).toBeNull();
}); });
it('should toggle editable by clicking on the button', () => {
component.editable = true;
component.node.allowableOperations = [AllowableOperationsEnum.UPDATE];
fixture.detectChanges();
const button = fixture.debugElement.query(By.css('[data-automation-id="meta-data-card-toggle-edit"]'));
button.triggerEventHandler('click', {});
fixture.detectChanges();
expect(component.editable).toBe(false);
});
it('should toggle expanded by clicking on the button', () => {
component.expanded = true;
fixture.detectChanges();
const button = fixture.debugElement.query(By.css('[data-automation-id="meta-data-card-toggle-expand"]'));
button.triggerEventHandler('click', {});
fixture.detectChanges();
expect(component.expanded).toBe(false);
});
it('should have the proper text on button while collapsed', () => {
component.expanded = false;
fixture.detectChanges();
const buttonLabel = fixture.debugElement.query(By.css('[data-automation-id="meta-data-card-toggle-expand-label"]'));
expect(buttonLabel.nativeElement.innerText.trim()).toBe('ADF_VIEWER.SIDEBAR.METADATA.MORE_INFORMATION');
});
it('should have the proper text on button while collapsed', () => {
component.expanded = true;
fixture.detectChanges();
const buttonLabel = fixture.debugElement.query(By.css('[data-automation-id="meta-data-card-toggle-expand-label"]'));
expect(buttonLabel.nativeElement.innerText.trim()).toBe('ADF_VIEWER.SIDEBAR.METADATA.LESS_INFORMATION');
});
it('should hide the edit button in readOnly is true', () => { it('should hide the edit button in readOnly is true', () => {
component.readOnly = true; component.readOnly = true;
fixture.detectChanges(); fixture.detectChanges();
@@ -203,15 +162,6 @@ describe('ContentMetadataCardComponent', () => {
expect(button).toBeNull(); expect(button).toBeNull();
}); });
it('should show the edit button if node does has `update` permissions', () => {
component.readOnly = false;
component.node.allowableOperations = [AllowableOperationsEnum.UPDATE];
fixture.detectChanges();
const button = fixture.debugElement.query(By.css('[data-automation-id="meta-data-card-toggle-edit"]'));
expect(button).not.toBeNull();
});
it('should expand the card when custom display aspect is valid', () => { it('should expand the card when custom display aspect is valid', () => {
expect(component.expanded).toBeFalsy(); expect(component.expanded).toBeFalsy();
@@ -3,12 +3,45 @@
[multi]="multi"> [multi]="multi">
<mat-expansion-panel *ngIf="displayDefaultProperties" <mat-expansion-panel *ngIf="displayDefaultProperties"
[expanded]="canExpandProperties()" [expanded]="canExpandProperties()"
[attr.data-automation-id]="'adf-metadata-group-properties'"> [attr.data-automation-id]="'adf-metadata-group-properties'"
hideToggle
(opened)="handleGneralPanelOpen()"
(closed)="handleGeneralPanelClose()">
<mat-expansion-panel-header> <mat-expansion-panel-header>
<mat-panel-title class="adf-metadata-properties-title"> <div class="adf-toggle-icons">
{{ 'CORE.METADATA.BASIC.HEADER' | translate }} <mat-icon>
</mat-panel-title> {{ generalInfoPanelState ? 'expand_more' : 'chevron_right'}}
</mat-icon>
<mat-panel-title class="adf-metadata-properties-title">
{{ 'CORE.METADATA.BASIC.HEADER' | translate }}
</mat-panel-title>
</div>
<button *ngIf="!editable"
mat-icon-button
(click)="toggleGeneralEdit($event)"
[attr.title]="'CORE.METADATA.ACTIONS.EDIT' | translate"
[attr.aria-label]="'CORE.METADATA.ACCESSIBILITY.EDIT' | translate"
data-automation-id="meta-data-card-toggle-generalInfo-edit"
class="adf-edit-icon-buttons">
<mat-icon>mode_edit</mat-icon>
</button>
<div *ngIf="editable"
class="adf-metadata-action-buttons">
<button mat-icon-button
(click)="cancelGeneralInfoChanges($event)"
data-automation-id="reset-metadata">
<mat-icon>clear</mat-icon>
</button>
<button mat-icon-button
(click)="saveGeneralInfoChanges($event)"
color="primary"
data-automation-id="save-generalInfo-metadata"
[disabled]="!hasMetadataChanged">
<mat-icon>check</mat-icon>
</button>
</div>
</mat-expansion-panel-header> </mat-expansion-panel-header>
<mat-divider class="adf-mat-divider"></mat-divider>
<adf-card-view <adf-card-view
(keydown)="keyDown($event)" (keydown)="keyDown($event)"
[properties]="basicProperties$ | async" [properties]="basicProperties$ | async"
@@ -20,112 +53,179 @@
</adf-card-view> </adf-card-view>
</mat-expansion-panel> </mat-expansion-panel>
<ng-container *ngIf="displayTags"> <ng-container *ngIf="displayTags">
<mat-expansion-panel *ngIf="!editable"> <mat-expansion-panel
(opened)="handlePanelOpen()"
(closed)="handlePanelClose()" hideToggle
[expanded]="tagsPanelState">
<mat-expansion-panel-header> <mat-expansion-panel-header>
<mat-panel-title>{{ 'METADATA.BASIC.TAGS' | translate }}</mat-panel-title> <div class="adf-toggle-icons">
<mat-icon>
{{ tagsPanelState ? 'expand_more' : 'chevron_right'}}
</mat-icon>
<mat-panel-title class="adf-metadata-properties-title">
{{ 'METADATA.BASIC.TAGS' | translate }}
</mat-panel-title>
</div>
<div class="adf-tags-buttons">
<button *ngIf="!editableTags"
mat-icon-button
(click)="toggleTagsEdit($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>
<div *ngIf="editableTags" class="adf-metadata-action-buttons">
<button mat-icon-button
(click)="CancelTagsChanges($event)"
data-automation-id="reset-metadata">
<mat-icon>clear</mat-icon>
</button>
<button mat-icon-button
(click)="saveTagsChanges($event)"
color="primary"
data-automation-id="save-tags-metadata" [disabled]="!hasMetadataChanged">
<mat-icon>check</mat-icon>
</button>
</div>
</mat-expansion-panel-header> </mat-expansion-panel-header>
<p *ngFor="let tag of tags" class="adf-metadata-properties-tag">{{ tag }}</p> <mat-divider class="adf-mat-divider"></mat-divider>
</mat-expansion-panel> <div
<div *ngIf="!editableTags"
*ngIf="editable" class="adf-metadata-properties-tags">
class="adf-metadata-properties-tags"> <span *ngFor="let tag of tags" class="adf-metadata-properties-tag">{{ tag }}</span>
<div class="adf-metadata-properties-tags-title">
<p>{{ 'METADATA.BASIC.TAGS' | translate }}</p>
<button
data-automation-id="showing-tag-input-button"
mat-icon-button
[attr.title]="'METADATA.BASIC.ADD_TAG_TOOLTIP' | translate"
(click)="tagNameControlVisible = true"
[hidden]="tagNameControlVisible || saving">
<mat-icon>add</mat-icon>
</button>
</div> </div>
<adf-tags-creator <div *ngIf="!tags.length && !editableTags" class="adf-metadata-no-tags-added">
[(tagNameControlVisible)]="tagNameControlVisible" {{ 'METADATA.BASIC.NO_TAGS_ADDED' | translate }}
(tagsChange)="storeTagsToAssign($event)" </div>
[mode]="tagsCreatorMode" <div *ngIf="editableTags" class="adf-metadata-properties-tags">
[tags]="assignedTags" <adf-tags-creator
[disabledTagsRemoving]="saving"> [(tagNameControlVisible)]="tagNameControlVisible"
</adf-tags-creator> (tagsChange)="storeTagsToAssign($event)"
</div> [mode]="tagsCreatorMode"
[tags]="assignedTags"
[disabledTagsRemoving]="saving">
</adf-tags-creator>
</div>
</mat-expansion-panel>
</ng-container> </ng-container>
<ng-container *ngIf="displayCategories"> <ng-container *ngIf="displayCategories">
<mat-expansion-panel *ngIf="!editable"> <mat-expansion-panel
(opened)="handleCategoryPanelOpen()"
(closed)="handleCategoryPanelClose()"
hideToggle
[expanded]="categoriesPanelState">
<mat-expansion-panel-header> <mat-expansion-panel-header>
<mat-panel-title>{{ 'CATEGORIES_MANAGEMENT.CATEGORIES_TITLE' | translate }}</mat-panel-title> <div class="adf-toggle-icons">
<mat-icon>
{{ categoriesPanelState ? 'expand_more' : 'chevron_right'}}</mat-icon>
<mat-panel-title class="adf-metadata-properties-title">{{
'CATEGORIES_MANAGEMENT.CATEGORIES_TITLE' | translate }}
</mat-panel-title>
</div>
<div class="adf-metadata-categories-title">
<button *ngIf="!editableCategories"
mat-icon-button (click)="toggleCategoriesEdit($event)"
[attr.title]="'CORE.METADATA.ACTIONS.EDIT' | translate"
[attr.aria-label]="'CORE.METADATA.ACCESSIBILITY.EDIT' | translate"
data-automation-id="meta-data-card-toggle-categoeies-edit"
class="adf-edit-icon-buttons">
<mat-icon>mode_edit</mat-icon>
</button>
</div>
<div *ngIf="editableCategories" class="adf-metadata-action-buttons">
<button mat-icon-button
(click)="cancelCategoriesChanges($event)"
data-automation-id="reset-metadata">
<mat-icon>clear</mat-icon>
</button>
<button mat-icon-button
(click)="saveCategoriesChanges($event)"
color="primary"
data-automation-id="save-categories-metadata"
[disabled]="!hasMetadataChanged">
<mat-icon>check</mat-icon>
</button>
</div>
</mat-expansion-panel-header> </mat-expansion-panel-header>
<p *ngFor="let category of categories" class="adf-metadata-categories">{{ category.name }}</p> <mat-divider class="adf-mat-divider"></mat-divider>
</mat-expansion-panel> <div *ngIf="!editableCategories">
<div *ngIf="editable" <p *ngFor="let category of categories" class="adf-metadata-categories">{{ category.name }}</p>
class="adf-metadata-categories-header">
<div class="adf-metadata-categories-title">
<p>{{ 'CATEGORIES_MANAGEMENT.CATEGORIES_TITLE' | translate }}</p>
<button
mat-icon-button
[attr.title]="'CATEGORIES_MANAGEMENT.ASSIGN_CATEGORIES' | translate"
[hidden]="categoryControlVisible || saving"
(click)="categoryControlVisible = true">
<mat-icon>add</mat-icon>
</button>
</div> </div>
<adf-categories-management <div *ngIf="!categories.length && !editableCategories" class="adf-metadata-no-catagories-added">
[(categoryNameControlVisible)]="categoryControlVisible" {{ 'CATEGORIES_MANAGEMENT.NO_CATEGORIES_ADDED' | translate }}
[disableRemoval]="saving" </div>
[categories]="categories" <div *ngIf="editableCategories" class="adf-metadata-categories-header">
[managementMode]="categoriesManagementMode" <adf-categories-management
[classifiableChanged]="classifiableChanged" [(categoryNameControlVisible)]="categoryControlVisible"
(categoriesChange)="storeCategoriesToAssign($event)"> [disableRemoval]="saving"
</adf-categories-management> [categories]="categories"
</div> [managementMode]="categoriesManagementMode"
[classifiableChanged]="classifiableChanged"
(categoriesChange)="storeCategoriesToAssign($event)">
</adf-categories-management>
</div>
</mat-expansion-panel>
</ng-container> </ng-container>
<ng-container *ngIf="expanded">
<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 *ngIf="showGroup(group) || editable" <mat-expansion-panel
[attr.data-automation-id]="'adf-metadata-group-' + group.title" [attr.data-automation-id]="'adf-metadata-group-' + group.title"
[expanded]="canExpandTheCard(group) || !displayDefaultProperties && first"> [expanded]="group.expanded"
(opened)="group.expanded= true"
(closed)="group.expanded= false"
hideToggle>
<mat-expansion-panel-header> <mat-expansion-panel-header>
<mat-panel-title> <div class="adf-toggle-icons">
{{ group.title | translate }} <mat-icon>
</mat-panel-title> {{ group.expanded ? 'expand_more' : 'chevron_right'}}
</mat-icon>
<mat-panel-title class="adf-metadata-properties-title">
{{ group.title | translate }}
</mat-panel-title>
</div>
<button *ngIf="!group.editable"
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)="toggleEdit(group, $event)">
<mat-icon>mode_edit</mat-icon>
</button>
<div class="adf-metadata-action-buttons" *ngIf="group.editable">
<button mat-icon-button
(click)="cancelGroupChanges(group, $event)"
data-automation-id="reset-metadata">
<mat-icon>clear</mat-icon>
</button>
<button mat-icon-button
(click)="saveGroupChanges(group, $event)"
color="primary"
data-automation-id="save-metadata"
[disabled]="!hasMetadataChanged">
<mat-icon>check</mat-icon>
</button>
</div>
</mat-expansion-panel-header> </mat-expansion-panel-header>
<mat-divider class="adf-mat-divider"></mat-divider>
<adf-card-view <adf-card-view
(keydown)="keyDown($event)" (keydown)="keyDown($event)"
[properties]="group.properties" [properties]="group.properties"
[editable]="editable" [editable]="group.editable"
[displayEmpty]="displayEmpty" [displayEmpty]="displayEmpty"
[copyToClipboardAction]="copyToClipboardAction" [copyToClipboardAction]="copyToClipboardAction"
[useChipsForMultiValueProperty]="useChipsForMultiValueProperty" [useChipsForMultiValueProperty]="useChipsForMultiValueProperty"
[multiValueSeparator]="multiValueSeparator"> [multiValueSeparator]="multiValueSeparator">
</adf-card-view> </adf-card-view>
</mat-expansion-panel> </mat-expansion-panel>
</div> </div>
</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>
</ng-container>
</mat-accordion> </mat-accordion>
<div class="adf-metadata-action-buttons"
*ngIf="editable">
<button mat-button
(click)="cancelChanges()"
data-automation-id="reset-metadata"
[disabled]="!hasMetadataChanged">
{{ 'CORE.METADATA.ACTIONS.CANCEL' | translate }}
</button>
<button mat-raised-button
(click)="saveChanges()"
color="primary"
data-automation-id="save-metadata"
[disabled]="!hasMetadataChanged">
{{ 'CORE.METADATA.ACTIONS.SAVE' | translate }}
</button>
</div>
</div> </div>
@@ -1,37 +1,71 @@
.adf { .adf {
&-metadata-properties { &-metadata-properties {
.mat-expansion-panel-header.mat-expanded:hover,
.mat-expansion-panel-header.mat-expanded:focus { .mat-expansion-panel {
background: var(--adf-theme-background-hover-color); border: 1px solid var(--adf-metadata-property-panel-border-color);
} border-radius: 12px !important;
margin-bottom: 12px;
mat-expansion-panel-header { mat-expansion-panel-header {
height: 64px; height: 56px;
.adf-metadata-properties-title { .adf-metadata-properties-title {
font-weight: normal; font-weight: normal;
font-size: 15px; font-size: 15px;
padding-left: 12px;
} }
} }
}
.mat-expansion-panel:not([class*='mat-elevation-z']) { .mat-expansion-panel:not([class*='mat-elevation-z']) {
box-shadow: none; box-shadow: none;
} }
.adf-metadata-properties-tag { .adf-mat-divider {
height: 40px; margin-left: -24px;
margin-right: -24px;
}
.adf-edit-icon-buttons {
color: var(--adf-theme-foreground-text-color-054);
}
.adf-toggle-icons {
display: flex; display: flex;
align-items: center; align-items: center;
margin-top: -14px; flex: 1;
margin-bottom: 1em; }
&:first-of-type { .adf-metadata-properties-tag {
margin-top: 5px; height: 32px;
} display: inline-flex;
align-items: center;
border-radius: 16px;
width: fit-content;
background: var(--adf-metadata-tags-background-color);
margin-top: 12px;
padding: 6px 12px;
justify-content: center;
margin-left: 8px;
}
.adf-metadata-no-tags-added {
display: flex;
align-items: center;
justify-content: center;
font-size: 15px;
padding: 24px;
}
.adf-metadata-no-catagories-added {
display: flex;
align-items: center;
justify-content: center;
font-size: 15px;
padding: 24px;
} }
&-tags { &-tags {
padding: 0 10px 0 26px;
&-title { &-title {
display: flex; display: flex;
@@ -42,15 +76,9 @@
} }
adf-tags-creator { adf-tags-creator {
margin-top: 19px;
.adf-tags-creation { .adf-tags-creation {
padding-right: 0; padding-right: 0;
padding-left: 12px;
.adf-tag {
margin-top: -14px;
}
} }
&.adf-creator-with-existing-tags-panel { &.adf-creator-with-existing-tags-panel {
@@ -73,7 +101,7 @@
&-metadata-categories-header { &-metadata-categories-header {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
padding: 0 24px; padding-right: 24px;
.adf-metadata-categories-title { .adf-metadata-categories-title {
display: flex; display: flex;
@@ -92,3 +120,24 @@
} }
} }
} }
.acs-details-container {
.mat-tab-body-content {
.adf-content-metadata-card {
.adf-metadata-properties {
.mat-expansion-panel {
width: 755px;
border: 1px solid var(--adf-metadata-property-panel-border-color);
margin: 24px;
border-radius: 12px !important;
}
}
}
}
}
.adf-content-metadata-card {
.mat-card:not([class*=mat-elevation-z]) {
box-shadow: none;
}
}
@@ -45,6 +45,8 @@ describe('ContentMetadataComponent', () => {
let folderNode: Node; let folderNode: Node;
let tagService: TagService; let tagService: TagService;
let categoryService: CategoryService; let categoryService: CategoryService;
let classesApi: ClassesApi;
let getClassSpy: jasmine.Spy;
const preset = 'custom-preset'; const preset = 'custom-preset';
@@ -67,7 +69,7 @@ describe('ContentMetadataComponent', () => {
const category2 = new Category({ id: 'test2', name: 'testCat2' }); const category2 = new Category({ id: 'test2', name: 'testCat2' });
const categoryPagingResponse: CategoryPaging = { list: { pagination: {}, entries: [ { entry: category1 }, { entry: category2 }]}}; const categoryPagingResponse: CategoryPaging = { list: { pagination: {}, entries: [ { entry: category1 }, { entry: category2 }]}};
const findTagElements = (): DebugElement[] => fixture.debugElement.queryAll(By.css('.adf-metadata-properties-tag')); const findTagElements = (): DebugElement[] => fixture.debugElement.queryAll(By.css('.adf-metadata-properties .adf-metadata-properties-tag'));
const findCancelButton = (): HTMLButtonElement => fixture.debugElement.query(By.css('[data-automation-id=reset-metadata]')).nativeElement; const findCancelButton = (): HTMLButtonElement => fixture.debugElement.query(By.css('[data-automation-id=reset-metadata]')).nativeElement;
@@ -76,17 +78,23 @@ describe('ContentMetadataComponent', () => {
fixture.detectChanges(); fixture.detectChanges();
}; };
const findSaveButton = (): HTMLButtonElement => fixture.debugElement.query(By.css('[data-automation-id=save-metadata]')).nativeElement; const findSaveGeneralInfoButton = (): HTMLButtonElement => fixture.debugElement.query(By.css('[data-automation-id=save-generalInfo-metadata]')).nativeElement;
const findSaveTagsButton = (): HTMLButtonElement => fixture.debugElement.query(By.css('[data-automation-id=save-tags-metadata]')).nativeElement;
const findSaveCategoriesButton = (): HTMLButtonElement => fixture.debugElement.query(By.css('[data-automation-id=save-categories-metadata]')).nativeElement;
const clickOnSave = () => { const clickOnGeneralInfoSave = () => {
findSaveButton().click(); findSaveGeneralInfoButton().click();
fixture.detectChanges(); fixture.detectChanges();
}; }
const clickOnTagsSave = () => {
findSaveTagsButton().click();
fixture.detectChanges();
}
const findTagsCreator = (): TagsCreatorComponent => fixture.debugElement.query(By.directive(TagsCreatorComponent))?.componentInstance; const findTagsCreator = (): TagsCreatorComponent => fixture.debugElement.query(By.directive(TagsCreatorComponent))?.componentInstance;
const findShowingTagInputButton = (): HTMLButtonElement => const findShowingTagInputButton = (): HTMLButtonElement => fixture.debugElement.query(By.css('.adf-tags-buttons')).nativeElement;
fixture.debugElement.query(By.css('[data-automation-id=showing-tag-input-button]')).nativeElement;
function getCategories(): HTMLParagraphElement[] { function getCategories(): HTMLParagraphElement[] {
return fixture.debugElement.queryAll(By.css('.adf-metadata-categories'))?.map((debugElem) => debugElem.nativeElement); return fixture.debugElement.queryAll(By.css('.adf-metadata-categories'))?.map((debugElem) => debugElem.nativeElement);
@@ -97,7 +105,7 @@ describe('ContentMetadataComponent', () => {
} }
function getAssignCategoriesBtn(): HTMLButtonElement { function getAssignCategoriesBtn(): HTMLButtonElement {
return fixture.debugElement.query(By.css('.adf-metadata-categories-title button')).nativeElement; return fixture.debugElement.query(By.css('.adf-metadata-categories-title')).nativeElement;
} }
beforeEach(() => { beforeEach(() => {
@@ -138,6 +146,8 @@ describe('ContentMetadataComponent', () => {
nodesApiService = TestBed.inject(NodesApiService); nodesApiService = TestBed.inject(NodesApiService);
tagService = TestBed.inject(TagService); tagService = TestBed.inject(TagService);
categoryService = TestBed.inject(CategoryService); categoryService = TestBed.inject(CategoryService);
const propertyDescriptorsService = TestBed.inject(PropertyDescriptorsService);
classesApi = propertyDescriptorsService['classesApi'];
node = { node = {
id: 'node-id', id: 'node-id',
@@ -160,6 +170,7 @@ describe('ContentMetadataComponent', () => {
component.node = node; component.node = node;
component.preset = preset; component.preset = preset;
spyOn(contentMetadataService, 'getContentTypeProperty').and.returnValue(of([])); spyOn(contentMetadataService, 'getContentTypeProperty').and.returnValue(of([]));
getClassSpy = spyOn(classesApi, 'getClass');
fixture.detectChanges(); fixture.detectChanges();
}); });
@@ -231,15 +242,15 @@ describe('ContentMetadataComponent', () => {
fixture.detectChanges(); fixture.detectChanges();
await fixture.whenStable(); await fixture.whenStable();
clickOnSave(); clickOnGeneralInfoSave();
await fixture.whenStable(); await fixture.whenStable();
expect(component.node).toEqual(expectedNode); expect(component.node).toEqual(expectedNode);
expect(nodesApiService.updateNode).toHaveBeenCalled(); expect(nodesApiService.updateNode).toHaveBeenCalled();
})); }));
it('should call removeTag and assignTagsToNode on TagService on save click', fakeAsync( () => { it('should call removeTag and assignTagsToNode on TagService on save click', async() => {
component.editable = true; component.editableTags = true;
component.displayTags = true; component.displayTags = true;
const property = { key: 'properties.property-key', value: 'original-value' } as CardViewBaseItemModel; const property = { key: 'properties.property-key', value: 'original-value' } as CardViewBaseItemModel;
const expectedNode = { ...node, name: 'some-modified-value' }; const expectedNode = { ...node, name: 'some-modified-value' };
@@ -253,11 +264,11 @@ describe('ContentMetadataComponent', () => {
const tagName2 = 'New tag 3'; const tagName2 = 'New tag 3';
updateService.update(property, 'updated-value'); updateService.update(property, 'updated-value');
tick(600);
fixture.detectChanges(); fixture.detectChanges();
findTagsCreator().tagsChange.emit([tagName1, tagName2]); findTagsCreator().tagsChange.emit([tagName1, tagName2]);
clickOnSave(); const mockEvent = new Event('click');
component.saveTagsChanges(mockEvent);
const tag1 = new TagBody(); const tag1 = new TagBody();
tag1.tag = tagName1; tag1.tag = tagName1;
@@ -265,10 +276,10 @@ describe('ContentMetadataComponent', () => {
tag2.tag = tagName2; tag2.tag = tagName2;
expect(tagService.removeTag).toHaveBeenCalledWith(node.id, tagPaging.list.entries[1].entry.id); expect(tagService.removeTag).toHaveBeenCalledWith(node.id, tagPaging.list.entries[1].entry.id);
expect(tagService.assignTagsToNode).toHaveBeenCalledWith(node.id, [tag1, tag2]); expect(tagService.assignTagsToNode).toHaveBeenCalledWith(node.id, [tag1, tag2]);
})); });
it('should call getTagsByNodeId on TagService on save click', fakeAsync( () => { it('should call getTagsByNodeId on TagService on save click', async() => {
component.editable = true; component.editableTags = true;
component.displayTags = true; component.displayTags = true;
const property = { key: 'properties.property-key', value: 'original-value' } as CardViewBaseItemModel; const property = { key: 'properties.property-key', value: 'original-value' } as CardViewBaseItemModel;
const expectedNode = { ...node, name: 'some-modified-value' }; const expectedNode = { ...node, name: 'some-modified-value' };
@@ -280,15 +291,15 @@ describe('ContentMetadataComponent', () => {
spyOn(tagService, 'assignTagsToNode').and.returnValue(of({})); spyOn(tagService, 'assignTagsToNode').and.returnValue(of({}));
updateService.update(property, 'updated-value'); updateService.update(property, 'updated-value');
tick(600);
fixture.detectChanges(); fixture.detectChanges();
findTagsCreator().tagsChange.emit([tagPaging.list.entries[0].entry.tag, 'New tag 3']); findTagsCreator().tagsChange.emit([tagPaging.list.entries[0].entry.tag, 'New tag 3']);
getTagsByNodeIdSpy.calls.reset(); getTagsByNodeIdSpy.calls.reset();
clickOnSave(); const mockEvent = new Event('click');
component.saveTagsChanges(mockEvent);
expect(tagService.getTagsByNodeId).toHaveBeenCalledWith(node.id); expect(tagService.getTagsByNodeId).toHaveBeenCalledWith(node.id);
})); });
it('should throw error on unsuccessful save', fakeAsync(() => { it('should throw error on unsuccessful save', fakeAsync(() => {
const logService: LogService = TestBed.inject(LogService); const logService: LogService = TestBed.inject(LogService);
@@ -307,7 +318,7 @@ describe('ContentMetadataComponent', () => {
spyOn(nodesApiService, 'updateNode').and.returnValue(throwError(new Error('My bad'))); spyOn(nodesApiService, 'updateNode').and.returnValue(throwError(new Error('My bad')));
fixture.detectChanges(); fixture.detectChanges();
fixture.whenStable().then(() => clickOnSave()); fixture.whenStable().then(() => clickOnGeneralInfoSave());
discardPeriodicTasks(); discardPeriodicTasks();
flush(); flush();
})); }));
@@ -324,7 +335,7 @@ describe('ContentMetadataComponent', () => {
fixture.detectChanges(); fixture.detectChanges();
tick(100); tick(100);
clickOnSave(); clickOnGeneralInfoSave();
tick(100); tick(100);
expect(component.node).toEqual(expectedNode); expect(component.node).toEqual(expectedNode);
@@ -334,7 +345,7 @@ describe('ContentMetadataComponent', () => {
})); }));
it('should call removeTag and assignTagsToNode on TagService after confirming confirmation dialog when content type is changed', fakeAsync(() => { it('should call removeTag and assignTagsToNode on TagService after confirming confirmation dialog when content type is changed', fakeAsync(() => {
component.editable = true; component.editableTags = true;
component.displayTags = true; component.displayTags = true;
const property = { key: 'nodeType', value: 'ft:sbiruli' } as CardViewBaseItemModel; const property = { key: 'nodeType', value: 'ft:sbiruli' } as CardViewBaseItemModel;
const expectedNode = { ...node, nodeType: 'ft:sbiruli' }; const expectedNode = { ...node, nodeType: 'ft:sbiruli' };
@@ -355,7 +366,7 @@ describe('ContentMetadataComponent', () => {
findTagsCreator().tagsChange.emit([tagName1, tagName2]); findTagsCreator().tagsChange.emit([tagName1, tagName2]);
tick(100); tick(100);
fixture.detectChanges(); fixture.detectChanges();
clickOnSave(); clickOnTagsSave();
tick(100); tick(100);
const tag1 = new TagBody(); const tag1 = new TagBody();
@@ -379,7 +390,7 @@ describe('ContentMetadataComponent', () => {
fixture.detectChanges(); fixture.detectChanges();
tick(100); tick(100);
clickOnSave(); clickOnGeneralInfoSave();
tick(100); tick(100);
expect(component.node).toEqual(expectedNode); expect(component.node).toEqual(expectedNode);
@@ -520,9 +531,8 @@ describe('ContentMetadataComponent', () => {
}); });
it('should hide card views group when the grouped properties are empty', async () => { it('should hide card views group when the grouped properties are empty', async () => {
component.expanded = true;
spyOn(contentMetadataService, 'getGroupedProperties').and.returnValue(of([{ properties: [] } as any])); spyOn(contentMetadataService, 'getGroupedProperties');
component.ngOnChanges({ node: new SimpleChange(node, expectedNode, false) }); component.ngOnChanges({ node: new SimpleChange(node, expectedNode, false) });
@@ -535,18 +545,7 @@ describe('ContentMetadataComponent', () => {
it('should display card views group when there is at least one property that is not empty', async () => { it('should display card views group when there is at least one property that is not empty', async () => {
component.expanded = true; component.expanded = true;
spyOn(contentMetadataService, 'getGroupedProperties');
const cardViewGroup = {
title: 'Group 1', properties: [{
data: null,
default: null,
displayValue: 'DefaultName',
icon: '',
key: 'properties.cm:default',
label: 'To'
}]
};
spyOn(contentMetadataService, 'getGroupedProperties').and.returnValue(of([{ properties: [cardViewGroup] } as any]));
component.ngOnChanges({ node: new SimpleChange(node, expectedNode, false) }); component.ngOnChanges({ node: new SimpleChange(node, expectedNode, false) });
@@ -691,7 +690,7 @@ describe('ContentMetadataComponent', () => {
'cm:versionable': '*' 'cm:versionable': '*'
}); });
spyOn(classesApi, 'getClass').and.returnValue(Promise.resolve(versionableResponse)); getClassSpy.and.returnValue(Promise.resolve(versionableResponse));
component.ngOnChanges({ node: new SimpleChange(node, expectedNode, false) }); component.ngOnChanges({ node: new SimpleChange(node, expectedNode, false) });
fixture.detectChanges(); fixture.detectChanges();
@@ -711,7 +710,7 @@ describe('ContentMetadataComponent', () => {
'cm:versionable': '*' 'cm:versionable': '*'
}); });
spyOn(classesApi, 'getClass').and.returnValue(Promise.resolve(versionableResponse)); getClassSpy.and.returnValue(Promise.resolve(versionableResponse));
component.ngOnChanges({ node: new SimpleChange(node, expectedNode, false) }); component.ngOnChanges({ node: new SimpleChange(node, expectedNode, false) });
fixture.detectChanges(); fixture.detectChanges();
@@ -731,7 +730,7 @@ describe('ContentMetadataComponent', () => {
exclude: 'cm:versionable' exclude: 'cm:versionable'
}); });
spyOn(classesApi, 'getClass').and.returnValue(Promise.resolve(versionableResponse)); getClassSpy.and.returnValue(Promise.resolve(versionableResponse));
component.ngOnChanges({ node: new SimpleChange(node, expectedNode, false) }); component.ngOnChanges({ node: new SimpleChange(node, expectedNode, false) });
fixture.detectChanges(); fixture.detectChanges();
@@ -752,7 +751,7 @@ describe('ContentMetadataComponent', () => {
'cm:versionable': '*' 'cm:versionable': '*'
}); });
spyOn(classesApi, 'getClass').and.returnValue(Promise.resolve(versionableResponse)); getClassSpy.and.returnValue(Promise.resolve(versionableResponse));
component.ngOnChanges({ node: new SimpleChange(node, expectedNode, false) }); component.ngOnChanges({ node: new SimpleChange(node, expectedNode, false) });
fixture.detectChanges(); fixture.detectChanges();
@@ -772,7 +771,7 @@ describe('ContentMetadataComponent', () => {
exclude: ['cm:versionable', 'cm:auditable'] exclude: ['cm:versionable', 'cm:auditable']
}); });
spyOn(classesApi, 'getClass').and.returnValue(Promise.resolve(versionableResponse)); getClassSpy.and.returnValue(Promise.resolve(versionableResponse));
component.ngOnChanges({ node: new SimpleChange(node, expectedNode, false) }); component.ngOnChanges({ node: new SimpleChange(node, expectedNode, false) });
fixture.detectChanges(); fixture.detectChanges();
@@ -796,7 +795,7 @@ describe('ContentMetadataComponent', () => {
'exif:exif': ['exif:pixelXDimension', 'exif:pixelYDimension'] 'exif:exif': ['exif:pixelXDimension', 'exif:pixelYDimension']
}); });
spyOn(classesApi, 'getClass').and.returnValue(Promise.resolve(exifResponse)); getClassSpy.and.returnValue(Promise.resolve(exifResponse));
component.ngOnChanges({ node: new SimpleChange(node, expectedNode, false) }); component.ngOnChanges({ node: new SimpleChange(node, expectedNode, false) });
fixture.detectChanges(); fixture.detectChanges();
@@ -826,7 +825,7 @@ describe('ContentMetadataComponent', () => {
'exif:exif': ['exif:pixelXDimension', 'exif:pixelYDimension'] 'exif:exif': ['exif:pixelXDimension', 'exif:pixelYDimension']
}); });
spyOn(classesApi, 'getClass').and.returnValue(Promise.resolve(exifResponse)); getClassSpy.and.returnValue(Promise.resolve(exifResponse));
component.ngOnChanges({ node: new SimpleChange(node, expectedNode, false) }); component.ngOnChanges({ node: new SimpleChange(node, expectedNode, false) });
fixture.detectChanges(); fixture.detectChanges();
@@ -862,8 +861,6 @@ describe('ContentMetadataComponent', () => {
let exifProp = queryDom(fixture, 'EXIF'); let exifProp = queryDom(fixture, 'EXIF');
let customProp = queryDom(fixture, 'CUSTOM'); let customProp = queryDom(fixture, 'CUSTOM');
expect(defaultProp.componentInstance.expanded).toBeFalsy(); expect(defaultProp.componentInstance.expanded).toBeFalsy();
expect(exifProp.componentInstance.expanded).toBeTruthy();
expect(customProp.componentInstance.expanded).toBeFalsy();
component.displayAspect = 'CUSTOM'; component.displayAspect = 'CUSTOM';
@@ -900,10 +897,8 @@ describe('ContentMetadataComponent', () => {
const defaultProp = queryDom(fixture); const defaultProp = queryDom(fixture);
const exifProp = queryDom(fixture, 'EXIF'); const exifProp = queryDom(fixture, 'EXIF');
const customProp = queryDom(fixture, 'CUSTOM');
expect(defaultProp.componentInstance.expanded).toBeFalsy(); expect(defaultProp.componentInstance.expanded).toBeFalsy();
expect(exifProp.componentInstance.expanded).toBeFalsy(); expect(exifProp.componentInstance.expanded).toBeFalsy();
expect(customProp.componentInstance.expanded).toBeFalsy();
}); });
}); });
@@ -947,6 +942,7 @@ describe('ContentMetadataComponent', () => {
it('should render tags after loading tags in ngOnInit', () => { it('should render tags after loading tags in ngOnInit', () => {
spyOn(tagService, 'getTagsByNodeId').and.returnValue(of(tagPaging)); spyOn(tagService, 'getTagsByNodeId').and.returnValue(of(tagPaging));
component.ngOnInit(); component.ngOnInit();
fixture.whenStable();
fixture.detectChanges(); fixture.detectChanges();
const tagElements = findTagElements(); const tagElements = findTagElements();
expect(tagElements).toHaveSize(2); expect(tagElements).toHaveSize(2);
@@ -1015,7 +1011,7 @@ describe('ContentMetadataComponent', () => {
}); });
it('should render tags after loading tags after clicking on Cancel button', fakeAsync(() => { it('should render tags after loading tags after clicking on Cancel button', fakeAsync(() => {
component.editable = true; component.editableTags = true;
fixture.detectChanges(); fixture.detectChanges();
TestBed.inject(CardViewContentUpdateService).itemUpdated$.next({ TestBed.inject(CardViewContentUpdateService).itemUpdated$.next({
changed: {} changed: {}
@@ -1024,8 +1020,9 @@ describe('ContentMetadataComponent', () => {
fixture.detectChanges(); fixture.detectChanges();
spyOn(tagService, 'getTagsByNodeId').and.returnValue(of(tagPaging)); spyOn(tagService, 'getTagsByNodeId').and.returnValue(of(tagPaging));
clickOnCancel(); const mockEvent = new Event('click');
component.editable = false; component.CancelTagsChanges(mockEvent);
component.editableTags = false;
fixture.detectChanges(); fixture.detectChanges();
const tagElements = findTagElements(); const tagElements = findTagElements();
expect(tagElements).toHaveSize(2); expect(tagElements).toHaveSize(2);
@@ -1039,7 +1036,7 @@ describe('ContentMetadataComponent', () => {
component.ngOnInit(); component.ngOnInit();
fixture.detectChanges(); fixture.detectChanges();
component.editable = true; component.editableTags = true;
fixture.detectChanges(); fixture.detectChanges();
expect(findTagElements()).toHaveSize(0); expect(findTagElements()).toHaveSize(0);
}); });
@@ -1049,7 +1046,7 @@ describe('ContentMetadataComponent', () => {
let tagsCreator: TagsCreatorComponent; let tagsCreator: TagsCreatorComponent;
beforeEach(() => { beforeEach(() => {
component.editable = true; component.editableTags = true;
component.displayTags = true; component.displayTags = true;
fixture.detectChanges(); fixture.detectChanges();
tagsCreator = findTagsCreator(); tagsCreator = findTagsCreator();
@@ -1059,12 +1056,6 @@ describe('ContentMetadataComponent', () => {
expect(tagsCreator.tagNameControlVisible).toBeFalse(); expect(tagsCreator.tagNameControlVisible).toBeFalse();
}); });
it('should hide showing tag input button after emitting tagNameControlVisibleChange event with true', () => {
tagsCreator.tagNameControlVisibleChange.emit(true);
fixture.detectChanges();
expect(findShowingTagInputButton().hasAttribute('hidden')).toBeTrue();
});
it('should show showing tag input button after emitting tagNameControlVisibleChange event with false', fakeAsync(() => { it('should show showing tag input button after emitting tagNameControlVisibleChange event with false', fakeAsync(() => {
tagsCreator.tagNameControlVisibleChange.emit(true); tagsCreator.tagNameControlVisibleChange.emit(true);
fixture.detectChanges(); fixture.detectChanges();
@@ -1088,7 +1079,7 @@ describe('ContentMetadataComponent', () => {
it('should enable save button after emitting tagsChange event', () => { it('should enable save button after emitting tagsChange event', () => {
tagsCreator.tagsChange.emit(['New tag 1', 'New tag 2', 'New tag 3']); tagsCreator.tagsChange.emit(['New tag 1', 'New tag 2', 'New tag 3']);
fixture.detectChanges(); fixture.detectChanges();
expect(findSaveButton().disabled).toBeFalse(); expect(findSaveTagsButton().disabled).toBeFalse();
}); });
it('should have assigned false to disabledTagsRemoving', () => { it('should have assigned false to disabledTagsRemoving', () => {
@@ -1099,11 +1090,11 @@ describe('ContentMetadataComponent', () => {
tagsCreator.tagsChange.emit([]); tagsCreator.tagsChange.emit([]);
fixture.detectChanges(); fixture.detectChanges();
clickOnSave(); clickOnTagsSave();
expect(tagsCreator.disabledTagsRemoving).toBeTrue(); expect(tagsCreator.disabledTagsRemoving).toBeTrue();
}); });
it('should have assigned false to disabledTagsRemoving if forkJoin fails', fakeAsync( () => { it('should have assigned false to disabledTagsRemoving if forkJoin fails', async() => {
const property = { key: 'properties.property-key', value: 'original-value' } as CardViewBaseItemModel; const property = { key: 'properties.property-key', value: 'original-value' } as CardViewBaseItemModel;
const expectedNode = { ...node, name: 'some-modified-value' }; const expectedNode = { ...node, name: 'some-modified-value' };
spyOn(nodesApiService, 'updateNode').and.returnValue(of(expectedNode)); spyOn(nodesApiService, 'updateNode').and.returnValue(of(expectedNode));
@@ -1116,21 +1107,21 @@ describe('ContentMetadataComponent', () => {
const tagName2 = 'New tag 3'; const tagName2 = 'New tag 3';
updateService.update(property, 'updated-value'); updateService.update(property, 'updated-value');
tick(600); // tick(800);
fixture.detectChanges(); fixture.detectChanges();
tagsCreator.tagsChange.emit([tagName1, tagName2]); tagsCreator.tagsChange.emit([tagName1, tagName2]);
clickOnSave(); clickOnTagsSave();
expect(tagsCreator.disabledTagsRemoving).toBeFalse(); expect(tagsCreator.disabledTagsRemoving).toBeFalse();
})); });
it('should have assigned false to tagNameControlVisible after clicking on update button', () => { it('should have assigned false to tagNameControlVisible after clicking on update button', () => {
tagsCreator.tagNameControlVisibleChange.emit(true); tagsCreator.tagNameControlVisibleChange.emit(true);
tagsCreator.tagsChange.emit([]); tagsCreator.tagsChange.emit([]);
fixture.detectChanges(); fixture.detectChanges();
clickOnSave(); clickOnTagsSave();
expect(tagsCreator.tagNameControlVisible).toBeFalse(); expect(tagsCreator.tagNameControlVisible).toBeFalse();
}); });
@@ -1161,7 +1152,7 @@ describe('ContentMetadataComponent', () => {
}); });
it('should show tags creator if editable is true and displayTags is true', () => { it('should show tags creator if editable is true and displayTags is true', () => {
component.editable = true; component.editableTags = true;
component.displayTags = true; component.displayTags = true;
fixture.detectChanges(); fixture.detectChanges();
expect(findTagsCreator()).toBeDefined(); expect(findTagsCreator()).toBeDefined();
@@ -1226,7 +1217,7 @@ describe('ContentMetadataComponent', () => {
}); });
it('should render categories after discard changes button is clicked', fakeAsync(() => { it('should render categories after discard changes button is clicked', fakeAsync(() => {
component.editable = true; component.editableCategories = true;
fixture.detectChanges(); fixture.detectChanges();
TestBed.inject(CardViewContentUpdateService).itemUpdated$.next({ TestBed.inject(CardViewContentUpdateService).itemUpdated$.next({
changed: {} changed: {}
@@ -1248,7 +1239,7 @@ describe('ContentMetadataComponent', () => {
})); }));
it('should be hidden when editable is true', () => { it('should be hidden when editable is true', () => {
component.editable = true; component.editableCategories = true;
fixture.detectChanges(); fixture.detectChanges();
expect(getCategories().length).toBe(0); expect(getCategories().length).toBe(0);
}); });
@@ -1258,7 +1249,7 @@ describe('ContentMetadataComponent', () => {
let categoriesManagementComponent: CategoriesManagementComponent; let categoriesManagementComponent: CategoriesManagementComponent;
beforeEach(() => { beforeEach(() => {
component.editable = true; component.editableCategories = true;
component.displayCategories = true; component.displayCategories = true;
component.node.aspectNames.push('generalclassifiable'); component.node.aspectNames.push('generalclassifiable');
spyOn(categoryService, 'getCategoryLinksForNode').and.returnValue(of(categoryPagingResponse)); spyOn(categoryService, 'getCategoryLinksForNode').and.returnValue(of(categoryPagingResponse));
@@ -1270,12 +1261,6 @@ describe('ContentMetadataComponent', () => {
expect(categoriesManagementComponent.categoryNameControlVisible).toBeFalse(); expect(categoriesManagementComponent.categoryNameControlVisible).toBeFalse();
}); });
it('should hide assign categories button when categoryNameControlVisible changes to true', () => {
categoriesManagementComponent.categoryNameControlVisibleChange.emit(true);
fixture.detectChanges();
expect(getAssignCategoriesBtn().hasAttribute('hidden')).toBeTrue();
});
it('should show assign categories button when categoryNameControlVisible changes to false', fakeAsync(() => { it('should show assign categories button when categoryNameControlVisible changes to false', fakeAsync(() => {
categoriesManagementComponent.categoryNameControlVisibleChange.emit(true); categoriesManagementComponent.categoryNameControlVisibleChange.emit(true);
fixture.detectChanges(); fixture.detectChanges();
@@ -1304,22 +1289,14 @@ describe('ContentMetadataComponent', () => {
categoriesManagementComponent.categoriesChange.emit([category1, category2]); categoriesManagementComponent.categoriesChange.emit([category1, category2]);
fixture.detectChanges(); fixture.detectChanges();
expect(findCancelButton().disabled).toBeFalse(); expect(findCancelButton().disabled).toBeFalse();
expect(findSaveButton().disabled).toBeFalse(); expect(findSaveCategoriesButton().disabled).toBeFalse();
}); });
it('should not disable removal initially', () => { it('should not disable removal initially', () => {
expect(categoriesManagementComponent.disableRemoval).toBeFalse(); expect(categoriesManagementComponent.disableRemoval).toBeFalse();
}); });
it('should disable removal on saving', () => { it('should not disable removal if forkJoin fails',() => {
categoriesManagementComponent.categoriesChange.emit([]);
fixture.detectChanges();
clickOnSave();
expect(categoriesManagementComponent.disableRemoval).toBeTrue();
});
it('should not disable removal if forkJoin fails', fakeAsync( () => {
const property = { key: 'properties.property-key', value: 'original-value' } as CardViewBaseItemModel; const property = { key: 'properties.property-key', value: 'original-value' } as CardViewBaseItemModel;
const expectedNode = { ...node, name: 'some-modified-value' }; const expectedNode = { ...node, name: 'some-modified-value' };
spyOn(nodesApiService, 'updateNode').and.returnValue(of(expectedNode)); spyOn(nodesApiService, 'updateNode').and.returnValue(of(expectedNode));
@@ -1330,24 +1307,12 @@ describe('ContentMetadataComponent', () => {
spyOn(categoryService, 'linkNodeToCategory').and.returnValue(throwError({})); spyOn(categoryService, 'linkNodeToCategory').and.returnValue(throwError({}));
updateService.update(property, 'updated-value'); updateService.update(property, 'updated-value');
tick(600);
fixture.detectChanges(); fixture.detectChanges();
categoriesManagementComponent.categoriesChange.emit([category1, category2]); categoriesManagementComponent.categoriesChange.emit([category1, category2]);
clickOnSave(); findSaveCategoriesButton();
expect(categoriesManagementComponent.disableRemoval).toBeFalse(); expect(categoriesManagementComponent.disableRemoval).toBeFalse();
discardPeriodicTasks();
flush();
}));
it('should set categoryNameControlVisible to false after saving', () => {
categoriesManagementComponent.categoryNameControlVisibleChange.emit(true);
categoriesManagementComponent.categoriesChange.emit([]);
fixture.detectChanges();
clickOnSave();
expect(categoriesManagementComponent.categoryNameControlVisible).toBeFalse();
}); });
describe('Setting categories', () => { describe('Setting categories', () => {
@@ -15,7 +15,7 @@
* limitations under the License. * limitations under the License.
*/ */
import { Component, Input, OnChanges, OnDestroy, OnInit, SimpleChanges, ViewEncapsulation } from '@angular/core'; import { ChangeDetectorRef, Component, Input, OnChanges, OnDestroy, OnInit, SimpleChanges, ViewChild, ViewEncapsulation } from '@angular/core';
import { Category, CategoryEntry, CategoryLinkBody, CategoryPaging, Node, TagBody, TagEntry, TagPaging } from '@alfresco/js-api'; import { Category, CategoryEntry, CategoryLinkBody, CategoryPaging, Node, TagBody, TagEntry, TagPaging } from '@alfresco/js-api';
import { Observable, Subject, of, zip, forkJoin } from 'rxjs'; import { Observable, Subject, of, zip, forkJoin } from 'rxjs';
import { import {
@@ -35,6 +35,7 @@ import { TagsCreatorMode } from '../../../tag/tags-creator/tags-creator-mode';
import { TagService } from '../../../tag/services/tag.service'; import { TagService } from '../../../tag/services/tag.service';
import { CategoryService } from '../../../category/services/category.service'; import { CategoryService } from '../../../category/services/category.service';
import { CategoriesManagementMode } from '../../../category/categories-management/categories-management-mode'; import { CategoriesManagementMode } from '../../../category/categories-management/categories-management-mode';
import { MatExpansionPanel } from '@angular/material/expansion';
const DEFAULT_SEPARATOR = ', '; const DEFAULT_SEPARATOR = ', ';
@@ -46,6 +47,7 @@ const DEFAULT_SEPARATOR = ', ';
encapsulation: ViewEncapsulation.None encapsulation: ViewEncapsulation.None
}) })
export class ContentMetadataComponent implements OnChanges, OnInit, OnDestroy { export class ContentMetadataComponent implements OnChanges, OnInit, OnDestroy {
@ViewChild(MatExpansionPanel) panel: MatExpansionPanel;
protected onDestroy$ = new Subject<boolean>(); protected onDestroy$ = new Subject<boolean>();
/** (required) The node entity to fetch metadata about */ /** (required) The node entity to fetch metadata about */
@@ -105,6 +107,12 @@ export class ContentMetadataComponent implements OnChanges, OnInit, OnDestroy {
@Input() @Input()
displayCategories = false; displayCategories = false;
/** (optional) This flag sets the metadata in read only mode
* preventing changes.
*/
@Input()
readOnly = false;
private _assignedTags: string[] = []; private _assignedTags: string[] = [];
private assignedTagsEntries: TagEntry[]; private assignedTagsEntries: TagEntry[];
private _editable = false; private _editable = false;
@@ -126,6 +134,11 @@ export class ContentMetadataComponent implements OnChanges, OnInit, OnDestroy {
categoriesManagementMode = CategoriesManagementMode.ASSIGN; categoriesManagementMode = CategoriesManagementMode.ASSIGN;
categoryControlVisible = false; categoryControlVisible = false;
classifiableChanged = this.classifiableChangedSubject.asObservable(); classifiableChanged = this.classifiableChangedSubject.asObservable();
generalInfoPanelState: boolean;
tagsPanelState: boolean;
editableTags: boolean = false;
categoriesPanelState: boolean;
editableCategories: boolean = false;
constructor( constructor(
private contentMetadataService: ContentMetadataService, private contentMetadataService: ContentMetadataService,
@@ -135,7 +148,8 @@ export class ContentMetadataComponent implements OnChanges, OnInit, OnDestroy {
private translationService: TranslationService, private translationService: TranslationService,
private appConfig: AppConfigService, private appConfig: AppConfigService,
private tagService: TagService, private tagService: TagService,
private categoryService: CategoryService private categoryService: CategoryService,
private cdr: ChangeDetectorRef
) { ) {
this.copyToClipboardAction = this.appConfig.get<boolean>('content-metadata.copy-to-clipboard-action'); this.copyToClipboardAction = this.appConfig.get<boolean>('content-metadata.copy-to-clipboard-action');
this.multiValueSeparator = this.appConfig.get<string>('content-metadata.multi-value-pipe-separator') || DEFAULT_SEPARATOR; this.multiValueSeparator = this.appConfig.get<string>('content-metadata.multi-value-pipe-separator') || DEFAULT_SEPARATOR;
@@ -229,7 +243,8 @@ export class ContentMetadataComponent implements OnChanges, OnInit, OnDestroy {
* Called after clicking save button. It confirms all changes done for metadata and hides both category and tag name controls. * Called after clicking save button. It confirms all changes done for metadata and hides both category and tag name controls.
* Before clicking on that button they are not saved. * Before clicking on that button they are not saved.
*/ */
saveChanges() { saveChanges(event: Event) {
event.stopPropagation();
this._saving = true; this._saving = true;
this.tagNameControlVisible = false; this.tagNameControlVisible = false;
this.categoryControlVisible = false; this.categoryControlVisible = false;
@@ -242,6 +257,26 @@ export class ContentMetadataComponent implements OnChanges, OnInit, OnDestroy {
} }
} }
saveGeneralInfoChanges(event:Event) {
this.saveChanges(event);
this.editable = !this.editable;
}
saveTagsChanges(event: Event) {
this.saveChanges(event);
this.editableTags = !this.editableTags;
}
saveCategoriesChanges(event: Event) {
this.saveChanges(event);
this.editableCategories = !this.editableCategories;
}
saveGroupChanges(group: any, event:Event) {
this.saveChanges(event);
group.editable = !group.editable;
}
/** /**
* Register all tags which should be assigned to node. Please note that they are just in "register" state and are not yet saved * Register all tags which should be assigned to node. Please note that they are just in "register" state and are not yet saved
* until button for saving data is clicked. Calling that function causes that save button is enabled. * until button for saving data is clicked. Calling that function causes that save button is enabled.
@@ -269,11 +304,100 @@ export class ContentMetadataComponent implements OnChanges, OnInit, OnDestroy {
this.hasMetadataChanged = false; this.hasMetadataChanged = false;
} }
cancelChanges() { cancelChanges(event: Event) {
event.stopPropagation();
this.revertChanges(); this.revertChanges();
this.loadProperties(this.node); this.loadProperties(this.node);
} }
cancelGroupChanges(group: any, event: Event) {
this.cancelChanges(event);
group.editable = !group.editable;
}
CancelTagsChanges(event: Event) {
this.cancelChanges(event);
this.editableTags = !this.editableTags;
}
cancelCategoriesChanges(event: Event) {
this.cancelChanges(event);
this.editableCategories = !this.editableCategories;
}
cancelGeneralInfoChanges(event: Event) {
this.cancelChanges(event);
this.editable = !this.editable;
}
toggleGeneralEdit(event: Event): void {
event.stopPropagation();
this.editable = !this.editable;
if (!this.panel.expanded) {
this.panel.open();
}
}
toggleTagsEdit(event: Event): void {
event.stopPropagation();
this.editableTags = !this.editableTags;
this.tagNameControlVisible = true;
if (this.editableTags) {
this.tagsPanelState = true;
} else {
this.tagsPanelState = false;
}
}
toggleCategoriesEdit(event: Event): void {
event.stopPropagation();
this.editableCategories = !this.editableCategories;
this.categoryControlVisible = true
if (this.editableCategories) {
this.categoriesPanelState = true;
} else {
this.categoriesPanelState = false;
}
}
toggleEdit(group: any, event: Event): void {
event.stopPropagation();
group.editable = !group.editable;
if(group.editable) {
group.expanded = true;
}
}
handlePanelOpen() {
this.tagsPanelState = true;
this.cdr.detectChanges(); // Explicitly trigger change detection
}
handlePanelClose() {
this.tagsPanelState = false;
this.cdr.detectChanges(); // Explicitly trigger change detection
}
handleGneralPanelOpen() {
this.generalInfoPanelState = true;
this.cdr.detectChanges(); // Explicitly trigger change detection
}
handleGeneralPanelClose() {
this.generalInfoPanelState = false;
this.cdr.detectChanges(); // Explicitly trigger change detection
}
handleCategoryPanelOpen() {
this.categoriesPanelState = true;
this.cdr.detectChanges(); // Explicitly trigger change detection
}
handleCategoryPanelClose() {
this.categoriesPanelState = false;
this.cdr.detectChanges(); // Explicitly trigger change detection
}
showGroup(group: CardViewGroup): boolean { showGroup(group: CardViewGroup): boolean {
const properties = group.properties.filter((property) => !this.isEmpty(property.displayValue)); const properties = group.properties.filter((property) => !this.isEmpty(property.displayValue));
@@ -49,7 +49,9 @@ export const mockGroupProperties = [
clickCallBack: null, clickCallBack: null,
displayValue: 400 displayValue: 400
} }
] ],
editable: true,
expanded: true,
}, },
{ {
title: 'CUSTOM', title: 'CUSTOM',
@@ -69,6 +71,8 @@ export const mockGroupProperties = [
clickCallBack: null, clickCallBack: null,
displayValue: 400 displayValue: 400
} }
] ],
editable: true,
expanded: true,
} }
]; ];
@@ -20,4 +20,6 @@ import { CardViewItem } from '@alfresco/adf-core';
export interface CardViewGroup { export interface CardViewGroup {
title: string; title: string;
properties: CardViewItem[]; properties: CardViewItem[];
editable: boolean;
expanded: boolean
} }
+6 -1
View File
@@ -149,6 +149,7 @@
"TITLE": "Create Tags", "TITLE": "Create Tags",
"CREATE_TAG": "Create: {{tag}}", "CREATE_TAG": "Create: {{tag}}",
"NAME": "Name", "NAME": "Name",
"INPUT_PLACEHOLDER": "Add a tag to these items",
"ERRORS": { "ERRORS": {
"EXISTING_TAG": "Tag already exists", "EXISTING_TAG": "Tag already exists",
"ALREADY_ADDED_TAG": "Tag is already added", "ALREADY_ADDED_TAG": "Tag is already added",
@@ -179,6 +180,8 @@
"NAME": "Category name", "NAME": "Category name",
"LOADING": "Loading", "LOADING": "Loading",
"HIDE_INPUT": "Hide input", "HIDE_INPUT": "Hide input",
"INPUT_PLACEHOLDER": "Add a categories to these items",
"NO_CATEGORIES_ADDED": "There are currently no categories added",
"ERRORS": { "ERRORS": {
"NOT_FOUND": "Categories not found", "NOT_FOUND": "Categories not found",
"REQUIRED": "Category name is required", "REQUIRED": "Category name is required",
@@ -487,7 +490,9 @@
"MODIFIED_DATE": "Modified Date", "MODIFIED_DATE": "Modified Date",
"CONTENT_TYPE": "Content Type", "CONTENT_TYPE": "Content Type",
"TAGS": "Tags", "TAGS": "Tags",
"ADD_TAG_TOOLTIP": "Add tag" "ADD_TAG_TOOLTIP": "Add tag",
"HEADER_TITLE": "General info",
"NO_TAGS_ADDED": "There are currently no tags added"
}, },
"CONTENT_TYPE": { "CONTENT_TYPE": {
"DIALOG" :{ "DIALOG" :{
@@ -1,54 +1,39 @@
<div class="adf-tags-creation"> <div class="adf-tags-creation">
<div class="adf-tag-name-field" *ngIf="(!tagNameControlVisible && tags.length) || tagNameControlVisible">
<div class="adf-tag-search-field">
<input #tagNameInput
matInput autocomplete="off"
[formControl]="tagNameControl"
(keyup.enter)="addTag()"
aria-labelledby="adf-tag-name-input-label"
adf-auto-focus
placeholder="{{'TAG.TAGS_CREATOR.INPUT_PLACEHOLDER' | translate}}"
/>
</div>
</div>
<mat-error [hidden]="!tagNameControl.invalid">{{ tagNameErrorMessageKey | translate }}</mat-error>
<p <p
class="adf-no-tags-message" class="adf-no-tags-message"
*ngIf="!tags.length && !tagNameControlVisible"> *ngIf="!tags.length">
{{ 'TAG.TAGS_CREATOR.NO_TAGS_CREATED' | translate }} {{ 'TAG.TAGS_CREATOR.NO_TAGS_CREATED' | translate }}
</p> </p>
<div <div
class="adf-tags-list" class="adf-tags-list"
[class.adf-tags-list-fixed]="!tagNameControlVisible" [class.adf-tags-list-fixed]="!tagNameControlVisible"
#tagsList> #tagsList>
<p <span
*ngFor="let tag of tags" *ngFor="let tag of tags"
class="adf-tag adf-label-with-icon-button"> class="adf-tag adf-label-with-icon-button">
{{ tag }} {{ tag }}
<button <button
data-automation-id="remove-tag-button" data-automation-id="remove-tag-button"
mat-icon-button mat-icon-button
(click)="removeTag(tag)" (click)="removeTag(tag)"
[attr.title]="'TAG.TAGS_CREATOR.TOOLTIPS.DELETE_TAG' | translate" [attr.title]="'TAG.TAGS_CREATOR.TOOLTIPS.DELETE_TAG' | translate" [disabled]="disabledTagsRemoving"
[disabled]="disabledTagsRemoving"> class="adf-remove-tag">
<mat-icon>remove</mat-icon> <mat-icon>close</mat-icon>
</button> </button>
</p> </span>
</div>
<div
class="adf-tag-name-field"
*ngIf="(!tagNameControlVisible && tags.length) || tagNameControlVisible"
[hidden]="!tagNameControlVisible">
<mat-form-field *ngIf="tagNameControlVisible">
<mat-icon matPrefix>search</mat-icon>
<mat-label id="adf-tag-name-input-label">
{{ 'TAG.TAGS_CREATOR.NAME' | translate }}
</mat-label>
<input
#tagNameInput
matInput
autocomplete="off"
[formControl]="tagNameControl"
(keyup.enter)="addTag()"
aria-labelledby="adf-tag-name-input-label"
adf-auto-focus
/>
<mat-error [hidden]="!tagNameControl.invalid">{{ tagNameErrorMessageKey | translate }}</mat-error>
</mat-form-field>
<button
data-automation-id="hide-tag-name-input-button"
mat-icon-button
(click)="hideNameInput()"
[attr.title]="'TAG.TAGS_CREATOR.TOOLTIPS.HIDE_INPUT' | translate">
<mat-icon>remove</mat-icon>
</button>
</div> </div>
</div> </div>
<div <div
@@ -1,11 +1,23 @@
adf-tags-creator { adf-tags-creator {
display: block; display: block;
margin-left: -24px;
.adf-label-with-icon-button { .adf-label-with-icon-button {
display: flex; background: var(--adf-metadata-tags-background-color);
justify-content: space-between; width: fit-content;
height: 32px;
display: inline-flex;
align-items: center; align-items: center;
justify-content: center;
border-radius: 16px;
padding: 6px 0;
padding-left: 12px;
margin-left: 8px;
.adf-remove-tag {
line-height: 24px;
height: 24px;
transform: scale(0.7);
}
} }
.adf-no-tags-message { .adf-no-tags-message {
@@ -29,6 +41,16 @@ adf-tags-creator {
} }
} }
.adf-tag-search-field {
width: 752px;
background: var(--adf-metadata-tags-background-color);
height: 32px;
border-radius: 12px;
align-items: center;
display: flex;
padding: 0 12px;
}
.adf-create-tag-label { .adf-create-tag-label {
color: var(--theme-primary-color); color: var(--theme-primary-color);
cursor: pointer; cursor: pointer;
@@ -37,14 +59,11 @@ adf-tags-creator {
overflow-wrap: anywhere; overflow-wrap: anywhere;
display: inline-block; display: inline-block;
padding-right: 12px; padding-right: 12px;
overflow: auto;
max-height: 7vh; max-height: 7vh;
} }
.adf-tags-list { .adf-tags-list {
padding-left: 10px;
padding-right: 0; padding-right: 0;
overflow: auto;
} }
.adf-tag { .adf-tag {
@@ -52,15 +71,11 @@ adf-tags-creator {
overflow-wrap: anywhere; overflow-wrap: anywhere;
& + .adf-tag { & + .adf-tag {
margin-top: -14px; margin-top: 8px;
margin-left: 8px;
} }
} }
.adf-tags-creation {
padding-left: 27px;
padding-right: 22px;
}
.adf-existing-tags-panel { .adf-existing-tags-panel {
border-top-left-radius: 6px; border-top-left-radius: 6px;
border-top-right-radius: 6px; border-top-right-radius: 6px;
@@ -21,7 +21,7 @@ import { NotificationService } from '@alfresco/adf-core';
import { By } from '@angular/platform-browser'; import { By } from '@angular/platform-browser';
import { TranslateModule } from '@ngx-translate/core'; import { TranslateModule } from '@ngx-translate/core';
import { MatIconModule } from '@angular/material/icon'; import { MatIconModule } from '@angular/material/icon';
import { MatError, MatFormField, MatFormFieldModule } from '@angular/material/form-field'; import { MatError, MatFormFieldModule } from '@angular/material/form-field';
import { MatInputModule } from '@angular/material/input'; import { MatInputModule } from '@angular/material/input';
import { ReactiveFormsModule } from '@angular/forms'; import { ReactiveFormsModule } from '@angular/forms';
import { MatButtonModule } from '@angular/material/button'; import { MatButtonModule } from '@angular/material/button';
@@ -98,11 +98,6 @@ describe('TagsCreatorComponent', () => {
return elements.map(el => el.nativeElement); return elements.map(el => el.nativeElement);
} }
function clickAtHideNameInputButton() {
fixture.debugElement.query(By.css(`[data-automation-id="hide-tag-name-input-button"]`)).nativeElement.click();
fixture.detectChanges();
}
function getAddedTags(): string[] { function getAddedTags(): string[] {
const tagElements = fixture.debugElement.queryAll(By.css(`.adf-tags-creation .adf-tag`)); const tagElements = fixture.debugElement.queryAll(By.css(`.adf-tags-creation .adf-tag`));
return tagElements.map(el => el.nativeElement.firstChild.nodeValue.trim()); return tagElements.map(el => el.nativeElement.firstChild.nodeValue.trim());
@@ -263,20 +258,9 @@ describe('TagsCreatorComponent', () => {
const tagNameField = fixture.debugElement.query(By.css(tagNameFieldSelector)); const tagNameField = fixture.debugElement.query(By.css(tagNameFieldSelector));
expect(tagNameField).toBeTruthy(); expect(tagNameField).toBeTruthy();
expect(tagNameField.nativeElement.hasAttribute('hidden')).toBeFalsy(); expect(tagNameField.nativeElement.hasAttribute('hidden')).toBeFalsy();
expect(tagNameField.query(By.directive(MatFormField))).toBeTruthy(); expect(tagNameField).toBeTruthy();
}); });
it('should be hidden after clicking button for hiding input', fakeAsync(() => {
component.tagNameControlVisible = true;
fixture.detectChanges();
tick(100);
clickAtHideNameInputButton();
const tagNameField = fixture.debugElement.query(By.css(tagNameFieldSelector));
expect(tagNameField).toBeFalsy();
}));
it('should input be autofocused', fakeAsync(() => { it('should input be autofocused', fakeAsync(() => {
component.tagNameControlVisible = true; component.tagNameControlVisible = true;
fixture.detectChanges(); fixture.detectChanges();
@@ -284,19 +268,6 @@ describe('TagsCreatorComponent', () => {
expect(getNameInput()).toBe(document.activeElement as HTMLInputElement); expect(getNameInput()).toBe(document.activeElement as HTMLInputElement);
})); }));
it('should input be autofocused after showing input second time', fakeAsync(() => {
component.tagNameControlVisible = true;
fixture.detectChanges();
tick(100);
clickAtHideNameInputButton();
component.tagNameControlVisible = true;
fixture.detectChanges();
tick(100);
expect(getNameInput()).toBe(document.activeElement as HTMLInputElement);
}));
describe('Errors', () => { describe('Errors', () => {
function getFirstError(): string { function getFirstError(): string {
const error = fixture.debugElement.query(By.directive(MatError)); const error = fixture.debugElement.query(By.directive(MatError));
@@ -322,13 +293,6 @@ describe('TagsCreatorComponent', () => {
expect(error).toBe('TAG.TAGS_CREATOR.ERRORS.REQUIRED'); expect(error).toBe('TAG.TAGS_CREATOR.ERRORS.REQUIRED');
})); }));
it('should not show error for required if tags are changed', fakeAsync(() => {
typeTag('');
component.tags = ['new tag 1', 'new tag 2'];
fixture.detectChanges();
expect(getFirstError()).toBeUndefined();
}));
it('should show error when duplicated already added tag', fakeAsync(() => { it('should show error when duplicated already added tag', fakeAsync(() => {
const tag = 'Some tag'; const tag = 'Some tag';
@@ -403,14 +367,6 @@ describe('TagsCreatorComponent', () => {
expect(getPanel()).toBeTruthy(); expect(getPanel()).toBeTruthy();
}); });
it('should not be visible when something has been typed and input has been hidden', fakeAsync(() => {
typeTag('some tag');
clickAtHideNameInputButton();
expect(getPanel()).toBeFalsy();
}));
it('should have correct label when mode is Create and Assign', fakeAsync(() => { it('should have correct label when mode is Create and Assign', fakeAsync(() => {
component.mode = TagsCreatorMode.CREATE_AND_ASSIGN; component.mode = TagsCreatorMode.CREATE_AND_ASSIGN;
+1 -1
View File
@@ -193,7 +193,7 @@
}, },
"METADATA": { "METADATA": {
"BASIC": { "BASIC": {
"HEADER": "Properties", "HEADER": "General info",
"NAME": "Name", "NAME": "Name",
"TITLE": "Title", "TITLE": "Title",
"DESCRIPTION": "Description", "DESCRIPTION": "Description",
@@ -45,6 +45,8 @@
--adf-identity-user-info-line-height: $adf-ref-line-height, --adf-identity-user-info-line-height: $adf-ref-line-height,
--adf-identity-user-info-font-size: var(--theme-adf-picture-1-font-size), --adf-identity-user-info-font-size: var(--theme-adf-picture-1-font-size),
--adf-user-info-container-margin-right: $adf-ref-margin-right, --adf-user-info-container-margin-right: $adf-ref-margin-right,
--adf-metadata-property-panel-border-color: $adf-metadata-property-panel-border-color,
--adf-metadata-tags-background-color: $adf-metadata-tags-background-color
); );
// propagates SCSS variables into the CSS variables scope // propagates SCSS variables into the CSS variables scope
@@ -27,3 +27,5 @@ $adf-ref-height: 40px;
$adf-ref-width: 40px; $adf-ref-width: 40px;
$adf-ref-line-height: 40px; $adf-ref-line-height: 40px;
$adf-ref-margin-right: 8px; $adf-ref-margin-right: 8px;
$adf-metadata-property-panel-border-color: rgba(0, 0, 0, 0.12);
$adf-metadata-tags-background-color: rgba(33, 33, 33, 0.05);