mirror of
https://github.com/Alfresco/alfresco-ng2-components.git
synced 2025-07-24 17:32:15 +00:00
[ACS-5645] Property Panel Feature (#8995)
* [ACS-5645]Added edit functionality for each panel and updated test cases * metadata e2e fix * [ACS-5725]fixed failing e2es * added unit test cases for new functionality * minor fixes * minor fixes * minor fixes * [ACS-5645]code modification * [ACS-5645]removed unwanted code * [ACS-5645]modified the changes * [ACS-5645]removed unwanted space * [ACS-5645]removed unwanted code * [ACS-5645]Implemented changes as per the review comments * linting fixes * [ACS-5645]minor fixes * [ACS-5645] removed unwanted code * [ACS-5645]modified the change * [ACS-5645]aligned input * [ACS-5645]modified changes * [ACS-5645]Implemented the changes as per the review comments * [ACS-5645]linting fixes * [ACS-5645]fixed sonarcloud issue * [ACS-5645]fixed errors * [ACS-5645]rename the function * [ACS-5645]fixes linting * [ACS-5540]lint fixes * [ACS-5645]Implemented the changes as per review comments * [ACS-5645] Removed unused code * [ACS-5645]linting fixes * [ACS-5645]fixes for lint * [ACS-5645] e2e fixes * [ACS-5645]Added translation * [ACS-5645]fixes for e2e * [ACS-5645]fixes for e2e * [ACS-5645]e2e fixes * [ACS-5645] Renamed the theme * [ACS-5645]modified changes * [ACS-5645] fixed lock-file bug * [ACS-5645] added tooltips for save and cancel icons * [ACS-5645] Modified the changes * [ACS-5645]Modified the changes * [ACS-5645] Implemented the changes as per the review comments * [ACS-5645] Implemented the changes as per the review comments * [ACS-5645]Modified the changes * [ACS-5645] added group panel lock changes * [ACS-5645] Resolved sonarcloud issue * [ACS-5645] added test cases for tags component * [ACS-5645] updated the documentation * [ACS-5645] updated the documentation * [ACS-5645] updated the documentation * [ACS-5645] Implemented changes as per review comments * [ACS-5645] lint fixes * [ACS-5645] Implemented the review comments * [ACS-5645] added focus * [ACS-5645] modified the changes * [ACS-5645] Lint fixes * [ACS-5645] Lint fixes * [ACS-5645] Lint fixes * [ACS-5645] Removed unwanted code * [ACS-5645] fixed sonarcloud issue * [ACS-5645] Added missing translation key * [ACS-5645] renamed the methods * [ACS-5645]Added edit functionality for each panel and updated test cases * [ACS-5645]code modification * [ACS-5645]removed unwanted code * [ACS-5645]Implemented changes as per the review comments * [ACS-5645]Implemented the changes as per review comments * [ACS-5645]linting fixes * [ACS-5645] fixed lock-file bug * [ACS-5645] Modified the changes * [ACS-5645] added group panel lock changes * [ACS-5645]Added edit functionality for each panel and updated test cases * minor fixes * [ACS-5645] Modified the changes * [ACS-5645] added group panel lock changes * [ACS-5645]Added edit functionality for each panel and updated test cases * metadata e2e fix * [ACS-5725]fixed failing e2es * minor fixes * [ACS-5645]removed unwanted code * [ACS-5645]Implemented changes as per the review comments * [ACS-5551] property panel design * [ACS-5551] minor changes * [ACS-5551]minor change * [ACS-5551] updated checks for non -editable field * [ACS-5551] modified the changes * [ACS-5551] modified changes * [ACS-5551] content-metadata updated * [ACS-5551] code updated * [ACS-5551] remove extra space * fixed scrollbar issue * [ACS-5551] margin adjusted * Fixed ACS-6110 * [ACS-5551] design updated * [ACCS-5551] unit test added * [ACS-5551] margin issue fixed * scroll issue fixed * [ACS-5551] color updated * [ACS-5551] design modify * [ACS-5551] add missing methods * [ACS-5654] translation added * [ACS-5645] style updated * [ACS-5654] hide toggle button for aspects * [ACS-5645] theme updated * [ACS-5645] tags and category tyle update * [ACS-5645] unit test update * [ACS-5645] code updated as per comments * [ACS-5645] linting issue fix * [ACS-5645] fixed the failed unit test cases * [ACS-5645] e2e fixes * [ACS-5645] e2e modify * [ACS-5645] aspect issue resolved * [ACS-5645] Address the comments * [ACS-5645] Address the comments * [ACS-5645] tags list design modify * [ACS-5645] design modify for chips * [ACS-5645] Removed unused property * [ACS-5645] Stop reload on panel cancel changes * [ACS-5645] Linting issue fixed * revert file change * [ACS-5645] update aspect issue fix * Revert "[ACS-5645] update aspect issue fix" This reverts commit 5212112f2293ad4c29afdd7c7faaf897cd3d00f6. * reduce layout duplicates, header panel component * code improvements * remove useless logging * cleanup css, remove mat-divider, fix tests * remove useless styles * cleanup e2e * cleanup useless events * rename nodeIcon to just icon * disable transition animation for tabs * remove "editable" hacks * improved naming for state properties * bug fixes for process cloud * css stylelint fixes * rework component, cleanup useless code * fix allowable operations and readonly state * wait for button * cleanup css, disable e2e * remove demo-shell only content, fix metadata * restore reset date functionality * fix incorrect styling * fix clear date button styles * cleanup text item styles * remove useless classes * text item rework, code cleanup * style bug fixes * cleanup useless tests * fix styles and tests * bug fixes for select item styles, revert PR changes * rework categories styles * rework tags creator styles * rollback divider module * fix css variable naming * fix issue with hidden properties * fix key value pairs layout and styles * fix tag creator validation * remove incorrect styles, raise proper errors * fix unit tests * fix theme vars naming * remove css hacks for date items * fix error borders * fix css bugs * reduce code * cleanup e2e and en.json * fix css linting * cleanup unused template refs * remove useless div for metadata container * cleanup expanders api * cleanup and remove useless tests * cleanup i18n * cleanup tests * cleanup css * cleanup css * [ACS-5654] added the missing theme variables * review comments resolved * fixed css issue * [ACS-5654] removesd extra div * [ACS-5654] save and cancel button bug fix * [ACS-5654] unit test fix for expand the panel * [ACS-5645] design issues fix * [ACS-5654] cards design fixed * [ACS-5654] node icon added to thumbnail service * [ACS-5645] linting issue fixed * [ACS-5645] thumbnail unit test updated * [ACS-5645] linting updated * [ACS-5645] removed extra div * [ACS-5645] important removed * [ACS-5645] tags text issue fix * [ACS-5645] add missed class * [ACS-5645] removed unused classes * [ACS-5645] removed unused code * revert flags to original state * fix missing semicolon * fix linting issues * reduce code duplication * code cleanup * [ACS-5645] unit test fix * [ACS-5645] e2e fix for edit button * fix linting issue for e2e * Replaced getNodeIcon from thumbnail to content service * fix indentation * refactor css variable * use rgba color value --------- Co-authored-by: Yasa-Nataliya <yasa.nataliya@globallogic.com> Co-authored-by: pkundu <priyanka.kundu@hyland.com> Co-authored-by: rbahirsheth <raviraj.bahirsheth@globallogic.com> Co-authored-by: Denys Vuika <denys.vuika@gmail.com>
This commit is contained in:
@@ -1,10 +1,16 @@
|
||||
<div class="adf-categories-management">
|
||||
<p *ngIf="!categories.length && !categoryNameControlVisible"
|
||||
class="adf-no-categories-message">
|
||||
{{ noCategoriesMsg | translate }}
|
||||
</p>
|
||||
<div class="adf-categories-list"
|
||||
[class.adf-categories-list-fixed]="!categoryNameControlVisible">
|
||||
<div *ngIf="categoryNameControlVisible" class="adf-category-name-field">
|
||||
<input #categoryNameInput
|
||||
matInput
|
||||
autocomplete="off"
|
||||
[formControl]="categoryNameControl"
|
||||
(keyup.enter)="addCategory()"
|
||||
placeholder="{{'CATEGORIES_MANAGEMENT.CATEGORIES_SEARCH_PLACEHOLDER' | translate }}"
|
||||
adf-auto-focus
|
||||
/>
|
||||
<mat-error *ngIf="categoryNameControl.invalid">{{ categoryNameErrorMessageKey | translate }}</mat-error>
|
||||
</div>
|
||||
<div class="adf-categories-list" [class.adf-categories-list-fixed]="!categoryNameControlVisible">
|
||||
<span
|
||||
*ngFor="let category of categories"
|
||||
[class.adf-categories-padded]="!isCRUDMode"
|
||||
@@ -13,7 +19,6 @@
|
||||
<button
|
||||
data-automation-id="categories-remove-category-button"
|
||||
mat-icon-button
|
||||
[class.adf-btn-padded]="!isCRUDMode"
|
||||
(click)="removeCategory(category)"
|
||||
[attr.title]="removeCategoryTitle | translate"
|
||||
[disabled]="disableRemoval">
|
||||
@@ -21,33 +26,9 @@
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
<div *ngIf="((!categoryNameControlVisible && categories.length)) || categoryNameControlVisible"
|
||||
[hidden]="!categoryNameControlVisible"
|
||||
class="adf-category-name-field">
|
||||
<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>
|
||||
<p *ngIf="showEmptyCategoryMessage" class="adf-no-categories-message">
|
||||
{{ noCategoriesMsg | translate }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="adf-existing-categories-panel" *ngIf="existingCategoriesPanelVisible">
|
||||
<ng-container *ngIf="isCRUDMode && (!existingCategoriesLoading || existingCategories)">
|
||||
|
@@ -1,15 +1,17 @@
|
||||
.adf-categories-management {
|
||||
padding-top: 12px;
|
||||
|
||||
.adf-category-name-field {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
color: var(--adf-metadata-property-panel-text-color);
|
||||
background: var(--adf-metadata-buttons-background-color);
|
||||
height: 32px;
|
||||
border-radius: 12px;
|
||||
align-items: center;
|
||||
|
||||
mat-form-field {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.adf-btn-padded {
|
||||
margin-right: -14px;
|
||||
input {
|
||||
padding: 7px 8px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,10 +20,6 @@
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
word-break: break-word;
|
||||
|
||||
.adf-btn-padded {
|
||||
margin-right: -14px;
|
||||
}
|
||||
}
|
||||
|
||||
.adf-categories-padded {
|
||||
@@ -31,11 +29,14 @@
|
||||
[hidden] {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.adf-no-categories-message {
|
||||
margin-bottom: 0;
|
||||
height: 30px;
|
||||
}
|
||||
}
|
||||
|
||||
.adf-categories-list {
|
||||
padding-bottom: 10px;
|
||||
|
||||
.mat-list-base .mat-list-item,
|
||||
.mat-list-base .mat-list-option {
|
||||
display: flex;
|
||||
|
@@ -223,44 +223,19 @@ describe('CategoriesManagementComponent', () => {
|
||||
component.categoryNameControlVisible = true;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
it('should be hidden initially', () => {
|
||||
component.categoryNameControlVisible = false;
|
||||
fixture.detectChanges();
|
||||
const categoryControl: HTMLDivElement = fixture.debugElement.query(By.css('.adf-category-name-field')).nativeElement;
|
||||
expect(categoryControl.hidden).toBeTrue();
|
||||
});
|
||||
|
||||
it('should be visible when categoryNameControlVisible is true', () => {
|
||||
const categoryControl = fixture.debugElement.query(By.css('.adf-category-name-field'));
|
||||
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');
|
||||
describe('showEmptyCategoryMessage', () => {
|
||||
it('should return true when categories empty and category in non editable state', () => {
|
||||
component.categories = [];
|
||||
component.categoryNameControlVisible = false;
|
||||
expect(component.showEmptyCategoryMessage).toBeTrue();
|
||||
});
|
||||
|
||||
it('should hide and clear category control and existing categories panel on clicking hide button', fakeAsync(() => {
|
||||
typeCategory('test');
|
||||
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);
|
||||
|
||||
component.categoryNameControlVisible = true;
|
||||
fixture.detectChanges();
|
||||
tick(100);
|
||||
expect(getCategoryControlInput().value).toBe('');
|
||||
}));
|
||||
});
|
||||
|
||||
describe('Spinner', () => {
|
||||
@@ -472,13 +447,8 @@ describe('CategoriesManagementComponent', () => {
|
||||
expect(categoriesChangeSpy).toHaveBeenCalledOnceWith(component.categories);
|
||||
}));
|
||||
|
||||
it('should clear and hide input after category is created', fakeAsync(() => {
|
||||
const controlVisibilityChangeSpy = spyOn(component.categoryNameControlVisibleChange, 'emit');
|
||||
it('should clear input after category is created', fakeAsync(() => {
|
||||
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();
|
||||
|
@@ -181,6 +181,13 @@ export class CategoriesManagementComponent implements OnInit, OnDestroy {
|
||||
return this._categoryNameControl;
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns `true` if categories empty and category panel non editable state, otherwise `false`
|
||||
*/
|
||||
get showEmptyCategoryMessage(): boolean {
|
||||
return this.categories.length === 0 && !this.categoryNameControlVisible;
|
||||
}
|
||||
|
||||
get existingCategories(): Category[] {
|
||||
return this._existingCategories;
|
||||
}
|
||||
@@ -205,16 +212,6 @@ export class CategoriesManagementComponent implements OnInit, OnDestroy {
|
||||
return this.managementMode === CategoriesManagementMode.CRUD;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hides and emits categoryNameControl and hides existing categories panel.
|
||||
*/
|
||||
hideNameInput() {
|
||||
this.categoryNameControlVisible = false;
|
||||
this.categoryNameControlVisibleChange.emit(false);
|
||||
this._existingCategoriesPanelVisible = false;
|
||||
this.clearCategoryNameInput();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds category that has been typed to a categoryNameControl and hides it afterwards.
|
||||
*/
|
||||
@@ -223,7 +220,6 @@ export class CategoriesManagementComponent implements OnInit, OnDestroy {
|
||||
const newCatName = this.categoryNameControl.value.trim();
|
||||
const newCat = new Category({ id: newCatName, name: newCatName });
|
||||
this.categories.push(newCat);
|
||||
this.hideNameInput();
|
||||
this.clearCategoryNameInput();
|
||||
this._existingCategories = null;
|
||||
this.categoriesChange.emit(this.categories);
|
||||
|
@@ -129,4 +129,52 @@ describe('ContentService', () => {
|
||||
expect(contentService.hasPermissions(permissionNode, 'manager')).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Node Icons', () => {
|
||||
let node: Node;
|
||||
|
||||
node = {
|
||||
isFolder: true,
|
||||
isFile: false,
|
||||
createdByUser: { id: 'admin', displayName: 'Administrator' },
|
||||
modifiedAt: new Date('2017-05-24T15:08:55.640Z'),
|
||||
nodeType: 'cm:content',
|
||||
content: {
|
||||
mimeType: 'application/rtf',
|
||||
mimeTypeName: 'Rich Text Format',
|
||||
sizeInBytes: 14530
|
||||
},
|
||||
createdAt: new Date('2017-05-24T15:08:55.640Z'),
|
||||
modifiedByUser: { id: 'admin', displayName: 'Administrator' },
|
||||
name: 'b_txt_file.rtf',
|
||||
id: 'test node 1',
|
||||
aspectNames: ['']
|
||||
} as Node;
|
||||
|
||||
it('should resolve folder icon', () => {
|
||||
expect(contentService.getNodeIcon(node)).toContain('assets/images/ft_ic_folder.svg');
|
||||
});
|
||||
|
||||
it('should resolve link folder icon', () => {
|
||||
node.nodeType = 'app:folderlink';
|
||||
expect(contentService.getNodeIcon(node)).toContain('assets/images/ft_ic_folder_shortcut_link.svg');
|
||||
});
|
||||
|
||||
it('should resolve smart folder icon', () => {
|
||||
node.aspectNames = ['smf:customConfigSmartFolder'];
|
||||
expect(contentService.getNodeIcon(node)).toContain('assets/images/ft_ic_smart_folder.svg');
|
||||
});
|
||||
|
||||
it('should resolve file icon for content type', () => {
|
||||
node.isFolder = false;
|
||||
node.isFile = true;
|
||||
expect(contentService.getNodeIcon(node)).toContain('assets/images/ft_ic_ms_word.svg');
|
||||
});
|
||||
|
||||
it('should resolve fallback file icon for unknown node', () => {
|
||||
node.isFolder = false;
|
||||
node.isFile = false;
|
||||
expect(contentService.getNodeIcon(node)).toContain('assets/images/ft_ic_miscellaneous');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@@ -18,7 +18,7 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { ContentApi, Node, NodeEntry } from '@alfresco/js-api';
|
||||
import { Subject } from 'rxjs';
|
||||
import { AlfrescoApiService, AuthenticationService } from '@alfresco/adf-core';
|
||||
import { AlfrescoApiService, AuthenticationService, ThumbnailService } from '@alfresco/adf-core';
|
||||
import { PermissionsEnum } from '../models/permissions.enum';
|
||||
import { AllowableOperationsEnum } from '../models/allowable-operations.enum';
|
||||
|
||||
@@ -43,7 +43,7 @@ export class ContentService {
|
||||
return this._contentApi;
|
||||
}
|
||||
|
||||
constructor(public authService: AuthenticationService, public apiService: AlfrescoApiService) {}
|
||||
constructor(public authService: AuthenticationService, public apiService: AlfrescoApiService, private thumbnailService?: ThumbnailService) {}
|
||||
|
||||
/**
|
||||
* Gets a content URL for the given node.
|
||||
@@ -145,4 +145,48 @@ export class ContentService {
|
||||
|
||||
return hasAllowableOperations;
|
||||
}
|
||||
|
||||
getNodeIcon(node: Node): string {
|
||||
if (node?.isFolder) {
|
||||
return this.getFolderIcon(node);
|
||||
}
|
||||
if (node?.isFile) {
|
||||
return this.thumbnailService.getMimeTypeIcon(node?.content?.mimeType);
|
||||
}
|
||||
return this.thumbnailService.getDefaultMimeTypeIcon();
|
||||
}
|
||||
|
||||
private getFolderIcon(node: Node): string {
|
||||
if (this.isSmartFolder(node)) {
|
||||
return this.thumbnailService.getMimeTypeIcon('smartFolder');
|
||||
} else if (this.isRuleFolder(node)) {
|
||||
return this.thumbnailService.getMimeTypeIcon('ruleFolder');
|
||||
} else if (this.isLinkFolder(node)) {
|
||||
return this.thumbnailService.getMimeTypeIcon('linkFolder');
|
||||
} else {
|
||||
return this.thumbnailService.getMimeTypeIcon('folder');
|
||||
}
|
||||
}
|
||||
|
||||
isSmartFolder(node: Node): boolean {
|
||||
if (node) {
|
||||
return this.hasAspect(node, 'smf:customConfigSmartFolder') || this.hasAspect(node, 'smf:systemConfigSmartFolder');
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
isRuleFolder(node: Node): boolean {
|
||||
if (node) {
|
||||
return this.hasAspect(node, 'rule:rules');
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
isLinkFolder(node: Node): boolean {
|
||||
return node?.nodeType === 'app:folderlink';
|
||||
}
|
||||
|
||||
private hasAspect(node: Node, aspectName: string): boolean {
|
||||
return node?.aspectNames?.includes(aspectName);
|
||||
}
|
||||
}
|
||||
|
@@ -5,7 +5,7 @@
|
||||
[expanded]="expanded"
|
||||
[node]="node"
|
||||
[displayEmpty]="displayEmpty"
|
||||
[editable]="editable"
|
||||
[readOnly]="!editable"
|
||||
[multi]="multi"
|
||||
[displayAspect]="displayAspect"
|
||||
[preset]="preset"
|
||||
@@ -24,24 +24,6 @@
|
||||
data-automation-id="meta-data-card-edit-aspect">
|
||||
<mat-icon>menu</mat-icon>
|
||||
</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>
|
||||
<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>
|
||||
|
@@ -114,14 +114,6 @@ describe('ContentMetadataCardComponent', () => {
|
||||
expect(contentMetadataComponent.displayEmpty).toBe(true);
|
||||
});
|
||||
|
||||
it('should pass through the editable to the underlying component', () => {
|
||||
component.editable = true;
|
||||
fixture.detectChanges();
|
||||
const contentMetadataComponent = fixture.debugElement.query(By.directive(ContentMetadataComponent)).componentInstance;
|
||||
|
||||
expect(contentMetadataComponent.editable).toBe(true);
|
||||
});
|
||||
|
||||
it('should pass through the multi to the underlying component', () => {
|
||||
component.multi = true;
|
||||
fixture.detectChanges();
|
||||
@@ -147,55 +139,6 @@ describe('ContentMetadataCardComponent', () => {
|
||||
expect(contentMetadataComponent).toBeNull();
|
||||
});
|
||||
|
||||
it('should toggle editable by clicking on the button', () => {
|
||||
component.editable = true;
|
||||
component.node.allowableOperations = [AllowableOperationsEnum.UPDATE];
|
||||
fixture.detectChanges();
|
||||
|
||||
getToggleEditButton().triggerEventHandler('click', {});
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(component.editable).toBe(false);
|
||||
});
|
||||
|
||||
it('should emit editableChange by clicking on toggle edit button', () => {
|
||||
component.node.allowableOperations = [AllowableOperationsEnum.UPDATE];
|
||||
fixture.detectChanges();
|
||||
spyOn(component.editableChange, 'emit');
|
||||
|
||||
getToggleEditButton().nativeElement.click();
|
||||
expect(component.editableChange.emit).toHaveBeenCalledWith(true);
|
||||
});
|
||||
|
||||
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', () => {
|
||||
component.readOnly = true;
|
||||
fixture.detectChanges();
|
||||
@@ -211,14 +154,6 @@ describe('ContentMetadataCardComponent', () => {
|
||||
expect(getToggleEditButton()).toBeNull();
|
||||
});
|
||||
|
||||
it('should show the edit button if node does has `update` permissions', () => {
|
||||
component.readOnly = false;
|
||||
component.node.allowableOperations = [AllowableOperationsEnum.UPDATE];
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(getToggleEditButton()).not.toBeNull();
|
||||
});
|
||||
|
||||
it('should expand the card when custom display aspect is valid', () => {
|
||||
expect(component.expanded).toBeFalsy();
|
||||
|
||||
|
@@ -15,7 +15,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Component, EventEmitter, Input, OnChanges, Output, SimpleChanges, ViewEncapsulation } from '@angular/core';
|
||||
import { Component, Input, OnChanges, SimpleChanges, ViewEncapsulation } from '@angular/core';
|
||||
import { Node } from '@alfresco/js-api';
|
||||
import { NodeAspectService } from '../../../aspect-list/services/node-aspect.service';
|
||||
import { ContentMetadataCustomPanel, PresetConfig } from '../../interfaces/content-metadata.interfaces';
|
||||
@@ -84,10 +84,6 @@ export class ContentMetadataCardComponent implements OnChanges {
|
||||
@Input()
|
||||
customPanels: ContentMetadataCustomPanel[];
|
||||
|
||||
/** Emitted when content's editable state is changed. */
|
||||
@Output()
|
||||
editableChange = new EventEmitter<boolean>();
|
||||
|
||||
private _displayDefaultProperties: boolean = true;
|
||||
|
||||
/**
|
||||
@@ -125,15 +121,6 @@ export class ContentMetadataCardComponent implements OnChanges {
|
||||
this.expanded = !this._displayDefaultProperties;
|
||||
}
|
||||
|
||||
toggleEdit(): void {
|
||||
this.editable = !this.editable;
|
||||
this.editableChange.emit(this.editable);
|
||||
}
|
||||
|
||||
toggleExpanded(): void {
|
||||
this.expanded = !this.expanded;
|
||||
}
|
||||
|
||||
hasAllowableOperations() {
|
||||
return this.contentService.hasAllowableOperations(this.node, AllowableOperationsEnum.UPDATE);
|
||||
}
|
||||
|
@@ -0,0 +1,55 @@
|
||||
/*!
|
||||
* @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.
|
||||
*/
|
||||
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { Component, Input, ViewEncapsulation } from '@angular/core';
|
||||
import { MatExpansionModule } from '@angular/material/expansion';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
imports: [CommonModule, MatIconModule, MatExpansionModule, TranslateModule],
|
||||
selector: 'adf-content-metadata-header',
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
styles: [
|
||||
`
|
||||
adf-content-metadata-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.adf-metadata-properties-title {
|
||||
font-weight: 700;
|
||||
font-size: 15px;
|
||||
padding-left: 12px;
|
||||
}
|
||||
`
|
||||
],
|
||||
template: `
|
||||
<ng-container>
|
||||
<mat-icon>{{ expanded ? 'expand_more' : 'chevron_right' }}</mat-icon>
|
||||
<mat-panel-title *ngIf="title" class="adf-metadata-properties-title">{{ title | translate }}</mat-panel-title>
|
||||
<ng-content></ng-content>
|
||||
</ng-container>
|
||||
`
|
||||
})
|
||||
export class ContentMetadataHeaderComponent {
|
||||
@Input() title: string = null;
|
||||
@Input() expanded = true;
|
||||
}
|
@@ -1,138 +1,225 @@
|
||||
<div class="adf-metadata-properties">
|
||||
<mat-accordion displayMode="flat"
|
||||
[multi]="multi">
|
||||
<mat-expansion-panel *ngIf="displayDefaultProperties"
|
||||
[expanded]="canExpandProperties()"
|
||||
[attr.data-automation-id]="'adf-metadata-group-properties'">
|
||||
<mat-expansion-panel-header>
|
||||
<mat-panel-title class="adf-metadata-properties-title">
|
||||
{{ 'CORE.METADATA.BASIC.HEADER' | translate }}
|
||||
</mat-panel-title>
|
||||
</mat-expansion-panel-header>
|
||||
<adf-card-view
|
||||
(keydown)="keyDown($event)"
|
||||
[properties]="basicProperties$ | async"
|
||||
[editable]="editable"
|
||||
[displayEmpty]="displayEmpty"
|
||||
[copyToClipboardAction]="copyToClipboardAction"
|
||||
[useChipsForMultiValueProperty]="useChipsForMultiValueProperty"
|
||||
[multiValueSeparator]="multiValueSeparator">
|
||||
</adf-card-view>
|
||||
</mat-expansion-panel>
|
||||
<ng-container *ngIf="displayTags">
|
||||
<mat-expansion-panel *ngIf="!editable">
|
||||
<mat-expansion-panel-header>
|
||||
<mat-panel-title>{{ 'METADATA.BASIC.TAGS' | translate }}</mat-panel-title>
|
||||
</mat-expansion-panel-header>
|
||||
<p *ngFor="let tag of tags" class="adf-metadata-properties-tag">{{ tag }}</p>
|
||||
</mat-expansion-panel>
|
||||
<div
|
||||
*ngIf="editable"
|
||||
class="adf-metadata-properties-tags">
|
||||
<div class="adf-metadata-properties-tags-title">
|
||||
<p>{{ 'METADATA.BASIC.TAGS' | translate }}</p>
|
||||
<button
|
||||
data-automation-id="showing-tag-input-button"
|
||||
<mat-accordion displayMode="flat" [multi]="multi" class="adf-metadata-properties">
|
||||
<mat-expansion-panel
|
||||
*ngIf="displayDefaultProperties"
|
||||
class="adf-content-metadata-panel"
|
||||
[(expanded)]="isGeneralPanelExpanded"
|
||||
[attr.data-automation-id]="'adf-metadata-group-properties'"
|
||||
hideToggle>
|
||||
<mat-expansion-panel-header>
|
||||
<adf-content-metadata-header [title]="'CORE.METADATA.BASIC.HEADER'" [expanded]="isGeneralPanelExpanded">
|
||||
<button *ngIf="canEditGeneralInfo"
|
||||
mat-icon-button
|
||||
[attr.title]="'METADATA.BASIC.ADD_TAG_TOOLTIP' | translate"
|
||||
(click)="tagNameControlVisible = true"
|
||||
[hidden]="tagNameControlVisible || saving">
|
||||
<mat-icon>add</mat-icon>
|
||||
(click)="onToggleGeneralInfoEdit($event)"
|
||||
[attr.title]="'CORE.METADATA.ACTIONS.EDIT' | translate"
|
||||
[attr.aria-label]="'CORE.METADATA.ACCESSIBILITY.EDIT' | translate"
|
||||
data-automation-id="meta-data-general-info-edit"
|
||||
class="adf-edit-icon-buttons">
|
||||
<mat-icon>mode_edit</mat-icon>
|
||||
</button>
|
||||
<div *ngIf="isEditingGeneralInfo" class="adf-metadata-action-buttons">
|
||||
<button mat-icon-button
|
||||
[attr.title]="'CORE.METADATA.ACTIONS.CANCEL' | translate"
|
||||
(click)="onCancelGeneralInfoEdit($event)"
|
||||
data-automation-id="reset-metadata"
|
||||
class="adf-metadata-action-buttons-clear">
|
||||
<mat-icon>clear</mat-icon>
|
||||
</button>
|
||||
<button mat-icon-button
|
||||
[attr.title]="'CORE.METADATA.ACTIONS.SAVE' | translate"
|
||||
(click)="onSaveGeneralInfoChanges($event)"
|
||||
color="primary"
|
||||
data-automation-id="save-general-info-metadata"
|
||||
[disabled]="!hasMetadataChanged">
|
||||
<mat-icon>check</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
<adf-tags-creator
|
||||
[(tagNameControlVisible)]="tagNameControlVisible"
|
||||
(tagsChange)="storeTagsToAssign($event)"
|
||||
[mode]="tagsCreatorMode"
|
||||
[tags]="assignedTags"
|
||||
[disabledTagsRemoving]="saving">
|
||||
</adf-tags-creator>
|
||||
</adf-content-metadata-header>
|
||||
</mat-expansion-panel-header>
|
||||
<adf-card-view
|
||||
(keydown)="keyDown($event)"
|
||||
[properties]="basicProperties$ | async"
|
||||
[editable]="isEditingModeGeneralInfo"
|
||||
[displayEmpty]="displayEmpty"
|
||||
[copyToClipboardAction]="copyToClipboardAction"
|
||||
[useChipsForMultiValueProperty]="useChipsForMultiValueProperty"
|
||||
[multiValueSeparator]="multiValueSeparator">
|
||||
</adf-card-view>
|
||||
</mat-expansion-panel>
|
||||
|
||||
<ng-container *ngIf="displayTags">
|
||||
<mat-expansion-panel hideToggle [(expanded)]="isTagPanelExpanded" class="adf-content-metadata-panel">
|
||||
<mat-expansion-panel-header>
|
||||
<adf-content-metadata-header [title]="'METADATA.BASIC.TAGS'" [expanded]="isTagPanelExpanded">
|
||||
<button *ngIf="canEditTags"
|
||||
mat-icon-button
|
||||
(click)="onToggleTagsEdit($event)"
|
||||
[attr.title]="'CORE.METADATA.ACTIONS.EDIT' | translate"
|
||||
[attr.aria-label]="'CORE.METADATA.ACCESSIBILITY.EDIT' | translate"
|
||||
data-automation-id="showing-tag-input-button"
|
||||
class="adf-edit-icon-buttons">
|
||||
<mat-icon>mode_edit</mat-icon>
|
||||
</button>
|
||||
<div *ngIf="isEditingTags" class="adf-metadata-action-buttons">
|
||||
<button mat-icon-button
|
||||
[attr.title]="'CORE.METADATA.ACTIONS.CANCEL' | translate"
|
||||
(click)="onCancelTagsEdit($event)"
|
||||
data-automation-id="reset-tags-metadata"
|
||||
class="adf-metadata-action-buttons-clear">
|
||||
<mat-icon>clear</mat-icon>
|
||||
</button>
|
||||
<button mat-icon-button
|
||||
[attr.title]="'CORE.METADATA.ACTIONS.SAVE' | translate"
|
||||
(click)="onSaveTagsChanges($event)"
|
||||
color="primary"
|
||||
data-automation-id="save-tags-metadata"
|
||||
[disabled]="!hasMetadataChanged">
|
||||
<mat-icon>check</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
</adf-content-metadata-header>
|
||||
</mat-expansion-panel-header>
|
||||
<div *ngIf="!isEditingModeTags" class="adf-metadata-properties-tags">
|
||||
<span *ngFor="let tag of tags" class="adf-metadata-properties-tag">{{ tag }}</span>
|
||||
</div>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="displayCategories">
|
||||
<mat-expansion-panel *ngIf="!editable">
|
||||
<mat-expansion-panel-header>
|
||||
<mat-panel-title>{{ 'CATEGORIES_MANAGEMENT.CATEGORIES_TITLE' | translate }}</mat-panel-title>
|
||||
</mat-expansion-panel-header>
|
||||
<div *ngIf="showEmptyTagMessage" class="adf-metadata-no-item-added">
|
||||
{{ 'METADATA.BASIC.NO_TAGS_ADDED' | translate }}
|
||||
</div>
|
||||
<adf-tags-creator
|
||||
*ngIf="isEditingModeTags"
|
||||
class="adf-metadata-properties-tags"
|
||||
[tagNameControlVisible]="tagNameControlVisible"
|
||||
(tagsChange)="storeTagsToAssign($event)"
|
||||
[mode]="tagsCreatorMode"
|
||||
[tags]="assignedTags"
|
||||
[disabledTagsRemoving]="saving">
|
||||
</adf-tags-creator>
|
||||
</mat-expansion-panel>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngIf="displayCategories">
|
||||
<mat-expansion-panel hideToggle [(expanded)]="isCategoriesPanelExpanded" class="adf-content-metadata-panel">
|
||||
<mat-expansion-panel-header>
|
||||
<adf-content-metadata-header [title]="'CATEGORIES_MANAGEMENT.CATEGORIES_TITLE'" [expanded]="isCategoriesPanelExpanded">
|
||||
<button *ngIf="canEditCategories"
|
||||
mat-icon-button
|
||||
(click)="onToggleCategoriesEdit($event)"
|
||||
[attr.title]="'CORE.METADATA.ACTIONS.EDIT' | translate"
|
||||
[attr.aria-label]="'CORE.METADATA.ACCESSIBILITY.EDIT' | translate"
|
||||
data-automation-id="meta-data-categories-edit"
|
||||
class="adf-categories-button adf-edit-icon-buttons">
|
||||
<mat-icon>mode_edit</mat-icon>
|
||||
</button>
|
||||
<div *ngIf="isEditingCategories" class="adf-metadata-action-buttons">
|
||||
<button mat-icon-button
|
||||
[attr.title]="'CORE.METADATA.ACTIONS.CANCEL' | translate"
|
||||
(click)="onCancelCategoriesEdit($event)"
|
||||
data-automation-id="reset-metadata"
|
||||
class="adf-metadata-action-buttons-clear">
|
||||
<mat-icon>clear</mat-icon>
|
||||
</button>
|
||||
<button mat-icon-button
|
||||
[attr.title]="'CORE.METADATA.ACTIONS.SAVE' | translate"
|
||||
(click)="onSaveCategoriesChanges($event)"
|
||||
color="primary"
|
||||
data-automation-id="save-categories-metadata"
|
||||
[disabled]="!hasMetadataChanged">
|
||||
<mat-icon>check</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
</adf-content-metadata-header>
|
||||
</mat-expansion-panel-header>
|
||||
<div *ngIf="!isEditingModeCategories">
|
||||
<p *ngFor="let category of categories" class="adf-metadata-categories">{{ category.name }}</p>
|
||||
</mat-expansion-panel>
|
||||
<div *ngIf="editable"
|
||||
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>
|
||||
<adf-categories-management
|
||||
[(categoryNameControlVisible)]="categoryControlVisible"
|
||||
[disableRemoval]="saving"
|
||||
[categories]="categories"
|
||||
[managementMode]="categoriesManagementMode"
|
||||
[classifiableChanged]="classifiableChanged"
|
||||
(categoriesChange)="storeCategoriesToAssign($event)">
|
||||
</adf-categories-management>
|
||||
</div>
|
||||
</ng-container>
|
||||
<mat-expansion-panel *ngFor="let customPanel of customPanels" [expanded]="canExpandTheCard(customPanel.panelTitle)">
|
||||
<mat-expansion-panel-header>
|
||||
<mat-panel-title>{{ customPanel.panelTitle | translate }}</mat-panel-title>
|
||||
</mat-expansion-panel-header>
|
||||
<adf-dynamic-component [id]="customPanel.component" [data]="{ node }"></adf-dynamic-component>
|
||||
<div *ngIf="showEmptyCategoryMessage" class="adf-metadata-no-item-added">
|
||||
{{ 'CATEGORIES_MANAGEMENT.NO_CATEGORIES_ADDED' | translate }}
|
||||
</div>
|
||||
<adf-categories-management
|
||||
*ngIf="isEditingModeCategories"
|
||||
class="adf-metadata-categories-header"
|
||||
[(categoryNameControlVisible)]="categoryControlVisible"
|
||||
[disableRemoval]="saving"
|
||||
[categories]="categories"
|
||||
[managementMode]="categoriesManagementMode"
|
||||
[classifiableChanged]="classifiableChanged"
|
||||
(categoriesChange)="storeCategoriesToAssign($event)">
|
||||
</adf-categories-management>
|
||||
</mat-expansion-panel>
|
||||
<ng-container *ngIf="expanded">
|
||||
<ng-container *ngIf="groupedProperties$ | async; else loading; let groupedProperties">
|
||||
<div *ngFor="let group of groupedProperties; let first = first;"
|
||||
class="adf-metadata-grouped-properties-container">
|
||||
<mat-expansion-panel *ngIf="showGroup(group) || editable"
|
||||
[attr.data-automation-id]="'adf-metadata-group-' + group.title"
|
||||
[expanded]="canExpandTheCard(group.title) || !displayDefaultProperties && first">
|
||||
<mat-expansion-panel-header>
|
||||
<mat-panel-title>
|
||||
{{ group.title | translate }}
|
||||
</mat-panel-title>
|
||||
</mat-expansion-panel-header>
|
||||
</ng-container>
|
||||
|
||||
<adf-card-view
|
||||
(keydown)="keyDown($event)"
|
||||
[properties]="group.properties"
|
||||
[editable]="editable"
|
||||
[displayEmpty]="displayEmpty"
|
||||
[copyToClipboardAction]="copyToClipboardAction"
|
||||
[useChipsForMultiValueProperty]="useChipsForMultiValueProperty"
|
||||
[multiValueSeparator]="multiValueSeparator"
|
||||
[displayLabelForChips]="true">
|
||||
</adf-card-view>
|
||||
</mat-expansion-panel>
|
||||
<mat-expansion-panel
|
||||
*ngFor="let customPanel of customPanels"
|
||||
[expanded]="canExpandTheCard(customPanel.panelTitle)"
|
||||
(opened)="customPanel.expanded = true"
|
||||
(closed)="customPanel.expanded = false"
|
||||
class="adf-content-metadata-panel"
|
||||
hideToggle>
|
||||
<mat-expansion-panel-header>
|
||||
<adf-content-metadata-header class="adf-metadata-custom-panel-title" [title]="customPanel.panelTitle" [expanded]="customPanel.expanded">
|
||||
</adf-content-metadata-header>
|
||||
</mat-expansion-panel-header>
|
||||
<adf-dynamic-component [id]="customPanel.component" [data]="{ node }"></adf-dynamic-component>
|
||||
</mat-expansion-panel>
|
||||
|
||||
<ng-container *ngIf="groupedProperties$ | async; else loading; let groupedProperties">
|
||||
<div *ngFor="let group of groupedProperties; let first = first;"
|
||||
class="adf-metadata-grouped-properties-container">
|
||||
<mat-expansion-panel
|
||||
[attr.data-automation-id]="'adf-metadata-group-' + group.title"
|
||||
[expanded]="canExpandTheCard(group.title) || !displayDefaultProperties && first || group.expanded"
|
||||
(opened)="group.expanded = true"
|
||||
(closed)="group.expanded = false"
|
||||
class="adf-content-metadata-panel"
|
||||
hideToggle>
|
||||
<mat-expansion-panel-header>
|
||||
<adf-content-metadata-header [title]="group.title" [expanded]="group.expanded">
|
||||
<button *ngIf="hasGroupToggleEdit(group)"
|
||||
mat-icon-button
|
||||
[attr.title]="'CORE.METADATA.ACTIONS.EDIT' | translate"
|
||||
[attr.aria-label]="'CORE.METADATA.ACCESSIBILITY.EDIT' | translate"
|
||||
data-automation-id="meta-data-card-toggle-edit"
|
||||
class="adf-edit-icon-buttons"
|
||||
(click)="onToggleGroupEdit(group, $event)">
|
||||
<mat-icon>mode_edit</mat-icon>
|
||||
</button>
|
||||
<div class="adf-metadata-action-buttons" *ngIf="isGroupToggleEditing(group)">
|
||||
<button mat-icon-button
|
||||
[attr.title]="'CORE.METADATA.ACTIONS.CANCEL' | translate"
|
||||
(click)="onCancelGroupEdit(group, $event)"
|
||||
data-automation-id="reset-metadata"
|
||||
class="adf-metadata-action-buttons-clear">
|
||||
<mat-icon>clear</mat-icon>
|
||||
</button>
|
||||
<button mat-icon-button
|
||||
[attr.title]="'CORE.METADATA.ACTIONS.SAVE' | translate"
|
||||
(click)="onSaveGroupChanges(group, $event)"
|
||||
color="primary"
|
||||
data-automation-id="save-metadata"
|
||||
[disabled]="!hasMetadataChanged">
|
||||
<mat-icon>check</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
</adf-content-metadata-header>
|
||||
</mat-expansion-panel-header>
|
||||
<div *ngIf="!showGroup(group) && !group.editable" class="adf-metadata-no-item-added">
|
||||
{{ 'METADATA.BASIC.NO_ITEMS_MESSAGE' | translate: { groupTitle: group.title | translate } }}
|
||||
</div>
|
||||
</ng-container>
|
||||
<ng-template #loading>
|
||||
<mat-progress-bar mode="indeterminate" [attr.aria-label]="'DATA_LOADING' | translate">
|
||||
</mat-progress-bar>
|
||||
</ng-template>
|
||||
</ng-container>
|
||||
</mat-accordion>
|
||||
<adf-card-view
|
||||
(keydown)="keyDown($event)"
|
||||
[properties]="group.properties"
|
||||
[editable]="group.editable"
|
||||
[displayEmpty]="displayEmpty"
|
||||
[copyToClipboardAction]="copyToClipboardAction"
|
||||
[useChipsForMultiValueProperty]="useChipsForMultiValueProperty"
|
||||
[multiValueSeparator]="multiValueSeparator"
|
||||
[displayLabelForChips]="true">
|
||||
</adf-card-view>
|
||||
</mat-expansion-panel>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
<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>
|
||||
<ng-template #loading>
|
||||
<mat-progress-bar mode="indeterminate" [attr.aria-label]="'DATA_LOADING' | translate">
|
||||
</mat-progress-bar>
|
||||
</ng-template>
|
||||
</mat-accordion>
|
||||
|
@@ -1,56 +1,64 @@
|
||||
$panel-properties-height: 56px !default;
|
||||
|
||||
.adf {
|
||||
&-metadata-properties {
|
||||
.mat-expansion-panel-header.mat-expanded:hover,
|
||||
.mat-expansion-panel-header.mat-expanded:focus {
|
||||
background: var(--adf-theme-background-hover-color);
|
||||
.adf-content-metadata-panel {
|
||||
box-shadow: none;
|
||||
border: 1px solid var(--adf-metadata-property-panel-border-color);
|
||||
border-radius: 12px;
|
||||
margin: 12px;
|
||||
}
|
||||
|
||||
mat-expansion-panel-header {
|
||||
height: 64px;
|
||||
height: $panel-properties-height;
|
||||
padding: 0 12px;
|
||||
border-radius: 12px 12px 0 0;
|
||||
|
||||
.adf-metadata-properties-title {
|
||||
font-weight: normal;
|
||||
font-size: 15px;
|
||||
&.mat-expanded {
|
||||
height: $panel-properties-height;
|
||||
border-bottom: 1px solid var(--adf-metadata-property-panel-border-color);
|
||||
}
|
||||
|
||||
.mat-content {
|
||||
display: contents;
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.mat-expansion-panel:not([class*='mat-elevation-z']) {
|
||||
box-shadow: none;
|
||||
.mat-expansion-panel-content {
|
||||
.mat-expansion-panel-body {
|
||||
padding: 0 12px 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.adf-edit-icon-buttons {
|
||||
color: var(--adf-theme-foreground-text-color-054);
|
||||
}
|
||||
|
||||
.adf-metadata-properties-tag {
|
||||
height: 40px;
|
||||
display: flex;
|
||||
min-height: 32px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
margin-top: -14px;
|
||||
margin-bottom: 1em;
|
||||
border-radius: 16px;
|
||||
width: fit-content;
|
||||
background: var(--adf-metadata-buttons-background-color);
|
||||
margin-top: 12px;
|
||||
padding: 6px 12px;
|
||||
justify-content: center;
|
||||
margin-left: 8px;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
&:first-of-type {
|
||||
margin-top: 5px;
|
||||
}
|
||||
.adf-metadata-no-item-added {
|
||||
word-break: break-all;
|
||||
font-size: 15px;
|
||||
padding: 16px 0 0 12px;
|
||||
}
|
||||
|
||||
&-tags {
|
||||
padding: 0 10px 0 26px;
|
||||
|
||||
&-title {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
font-size: 15px;
|
||||
height: 68px;
|
||||
}
|
||||
|
||||
adf-tags-creator {
|
||||
margin-top: 19px;
|
||||
|
||||
.adf-tags-creation {
|
||||
padding-right: 0;
|
||||
padding-left: 12px;
|
||||
|
||||
.adf-tag {
|
||||
margin-top: -14px;
|
||||
}
|
||||
}
|
||||
|
||||
&.adf-creator-with-existing-tags-panel {
|
||||
@@ -67,24 +75,19 @@
|
||||
&-metadata-action-buttons {
|
||||
display: flex;
|
||||
justify-content: space-evenly;
|
||||
margin: 10px;
|
||||
|
||||
&-clear {
|
||||
color: var(--adf-metadata-action-button-clear-color);
|
||||
}
|
||||
}
|
||||
|
||||
&-metadata-categories-header {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 0 24px;
|
||||
|
||||
.adf-metadata-categories-title {
|
||||
.adf-categories-button {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
font-size: 15px;
|
||||
height: 64px;
|
||||
|
||||
button {
|
||||
margin-right: -14px;
|
||||
}
|
||||
height: $panel-properties-height;
|
||||
|
||||
[hidden] {
|
||||
visibility: hidden;
|
||||
|
@@ -21,16 +21,16 @@ import { By } from '@angular/platform-browser';
|
||||
import { Category, CategoryPaging, ClassesApi, Node, Tag, TagBody, TagEntry, TagPaging, TagPagingList } from '@alfresco/js-api';
|
||||
import { ContentMetadataComponent } from './content-metadata.component';
|
||||
import { ContentMetadataService } from '../../services/content-metadata.service';
|
||||
import { AppConfigService, CardViewBaseItemModel, CardViewComponent, LogService, UpdateNotification } from '@alfresco/adf-core';
|
||||
import { AppConfigService, CardViewBaseItemModel, CardViewComponent, NotificationService, UpdateNotification } from '@alfresco/adf-core';
|
||||
import { NodesApiService } from '../../../common/services/nodes-api.service';
|
||||
import { EMPTY, of, throwError } from 'rxjs';
|
||||
import { ContentTestingModule } from '../../../testing/content.testing.module';
|
||||
import { mockGroupProperties } from './mock-data';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { CardViewContentUpdateService } from '../../../common/services/card-view-content-update.service';
|
||||
import { PropertyGroup } from '../../interfaces/property-group.interface';
|
||||
import { PropertyDescriptorsService } from '../../services/property-descriptors.service';
|
||||
import {
|
||||
CardViewGroup,
|
||||
CategoriesManagementComponent,
|
||||
CategoriesManagementMode,
|
||||
CategoryService,
|
||||
@@ -49,6 +49,8 @@ describe('ContentMetadataComponent', () => {
|
||||
let folderNode: Node;
|
||||
let tagService: TagService;
|
||||
let categoryService: CategoryService;
|
||||
let getClassSpy: jasmine.Spy;
|
||||
let notificationService: NotificationService;
|
||||
|
||||
const preset = 'custom-preset';
|
||||
|
||||
@@ -71,26 +73,38 @@ describe('ContentMetadataComponent', () => {
|
||||
const category2 = new Category({ id: 'test2', name: 'testCat2' });
|
||||
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 findCancelTagsButton = (): HTMLButtonElement =>
|
||||
fixture.debugElement.query(By.css('[data-automation-id=reset-tags-metadata]')).nativeElement;
|
||||
|
||||
const clickOnCancel = () => {
|
||||
findCancelButton().click();
|
||||
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-general-info-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 = () => {
|
||||
findSaveButton().click();
|
||||
const clickOnGeneralInfoSave = () => {
|
||||
findSaveGeneralInfoButton().click();
|
||||
fixture.detectChanges();
|
||||
};
|
||||
|
||||
const clickOnTagsSave = () => {
|
||||
findSaveTagsButton().click();
|
||||
fixture.detectChanges();
|
||||
};
|
||||
|
||||
const findTagsCreator = (): TagsCreatorComponent => fixture.debugElement.query(By.directive(TagsCreatorComponent))?.componentInstance;
|
||||
|
||||
const findShowingTagInputButton = (): HTMLButtonElement =>
|
||||
fixture.debugElement.query(By.css('[data-automation-id=showing-tag-input-button]')).nativeElement;
|
||||
const getToggleEditButton = () => fixture.debugElement.query(By.css('[data-automation-id="meta-data-general-info-edit"]'));
|
||||
const getTagsToggleEditButton = () => fixture.debugElement.query(By.css('[data-automation-id="showing-tag-input-button"]'));
|
||||
const getCategoriesToggleEditButton = () => fixture.debugElement.query(By.css('[data-automation-id="meta-data-categories-edit"]'));
|
||||
const getGroupToggleEditButton = () => fixture.debugElement.query(By.css('[data-automation-id="meta-data-card-toggle-edit"]'));
|
||||
|
||||
/**
|
||||
* Get metadata categories
|
||||
@@ -110,22 +124,12 @@ describe('ContentMetadataComponent', () => {
|
||||
return fixture.debugElement.query(By.directive(CategoriesManagementComponent))?.componentInstance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get categories title button
|
||||
*
|
||||
* @returns native element
|
||||
*/
|
||||
function getAssignCategoriesBtn(): HTMLButtonElement {
|
||||
return fixture.debugElement.query(By.css('.adf-metadata-categories-title button')).nativeElement;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update aspect property
|
||||
*
|
||||
* @param newValue value to set
|
||||
*/
|
||||
async function updateAspectProperty(newValue: string): Promise<void> {
|
||||
component.editable = true;
|
||||
const property = { key: 'properties.property-key', value: 'original-value' } as CardViewBaseItemModel;
|
||||
const expectedNode = { ...node, name: 'some-modified-value' };
|
||||
spyOn(nodesApiService, 'updateNode').and.returnValue(of(expectedNode));
|
||||
@@ -135,8 +139,7 @@ describe('ContentMetadataComponent', () => {
|
||||
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
clickOnSave();
|
||||
|
||||
component.onSaveGroupChanges({} as any);
|
||||
await fixture.whenStable();
|
||||
}
|
||||
|
||||
@@ -144,12 +147,6 @@ describe('ContentMetadataComponent', () => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [TranslateModule.forRoot(), ContentTestingModule],
|
||||
providers: [
|
||||
{
|
||||
provide: LogService,
|
||||
useValue: {
|
||||
error: jasmine.createSpy('error')
|
||||
}
|
||||
},
|
||||
{
|
||||
provide: TagService,
|
||||
useValue: {
|
||||
@@ -175,6 +172,9 @@ describe('ContentMetadataComponent', () => {
|
||||
nodesApiService = TestBed.inject(NodesApiService);
|
||||
tagService = TestBed.inject(TagService);
|
||||
categoryService = TestBed.inject(CategoryService);
|
||||
notificationService = TestBed.inject(NotificationService);
|
||||
const propertyDescriptorsService = TestBed.inject(PropertyDescriptorsService);
|
||||
const classesApi = propertyDescriptorsService['classesApi'];
|
||||
|
||||
node = {
|
||||
id: 'node-id',
|
||||
@@ -197,6 +197,7 @@ describe('ContentMetadataComponent', () => {
|
||||
component.node = node;
|
||||
component.preset = preset;
|
||||
spyOn(contentMetadataService, 'getContentTypeProperty').and.returnValue(of([]));
|
||||
getClassSpy = spyOn(classesApi, 'getClass');
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
@@ -206,7 +207,7 @@ describe('ContentMetadataComponent', () => {
|
||||
|
||||
describe('Default input param values', () => {
|
||||
it('should have editable input param as false by default', () => {
|
||||
expect(component.editable).toBe(false);
|
||||
expect(component.isEditingModeGeneralInfo).toBe(false);
|
||||
});
|
||||
|
||||
it('should have displayEmpty input param as false by default', () => {
|
||||
@@ -272,7 +273,7 @@ describe('ContentMetadataComponent', () => {
|
||||
}));
|
||||
|
||||
it('should call removeTag and assignTagsToNode on TagService on save click', fakeAsync(() => {
|
||||
component.editable = true;
|
||||
component.isEditingModeTags = true;
|
||||
component.displayTags = true;
|
||||
const property = { key: 'properties.property-key', value: 'original-value' } as CardViewBaseItemModel;
|
||||
const expectedNode = { ...node, name: 'some-modified-value' };
|
||||
@@ -284,24 +285,26 @@ describe('ContentMetadataComponent', () => {
|
||||
spyOn(tagService, 'assignTagsToNode').and.returnValue(EMPTY);
|
||||
const tagName1 = tagPaging.list.entries[0].entry.tag;
|
||||
const tagName2 = 'New tag 3';
|
||||
|
||||
component.isEditingModeTags = true;
|
||||
component.readOnly = false;
|
||||
updateService.update(property, 'updated-value');
|
||||
tick(600);
|
||||
|
||||
fixture.detectChanges();
|
||||
findTagsCreator().tagsChange.emit([tagName1, tagName2]);
|
||||
clickOnSave();
|
||||
|
||||
const tag1 = new TagBody();
|
||||
tag1.tag = tagName1;
|
||||
const tag2 = new TagBody();
|
||||
tag2.tag = tagName2;
|
||||
fixture.detectChanges();
|
||||
tick(600);
|
||||
clickOnTagsSave();
|
||||
tick(100);
|
||||
const tag1 = new TagBody({ tag: tagName1 });
|
||||
const tag2 = new TagBody({ tag: tagName2 });
|
||||
expect(tagService.removeTag).toHaveBeenCalledWith(node.id, tagPaging.list.entries[1].entry.id);
|
||||
expect(tagService.assignTagsToNode).toHaveBeenCalledWith(node.id, [tag1, tag2]);
|
||||
|
||||
discardPeriodicTasks();
|
||||
flush();
|
||||
}));
|
||||
|
||||
it('should call getTagsByNodeId on TagService on save click', fakeAsync(() => {
|
||||
component.editable = true;
|
||||
it('should call getTagsByNodeId on TagService on save click', () => {
|
||||
component.isEditingModeTags = true;
|
||||
component.displayTags = true;
|
||||
const property = { key: 'properties.property-key', value: 'original-value' } as CardViewBaseItemModel;
|
||||
const expectedNode = { ...node, name: 'some-modified-value' };
|
||||
@@ -313,25 +316,23 @@ describe('ContentMetadataComponent', () => {
|
||||
spyOn(tagService, 'assignTagsToNode').and.returnValue(of({}));
|
||||
|
||||
updateService.update(property, 'updated-value');
|
||||
tick(600);
|
||||
|
||||
fixture.detectChanges();
|
||||
findTagsCreator().tagsChange.emit([tagPaging.list.entries[0].entry.tag, 'New tag 3']);
|
||||
getTagsByNodeIdSpy.calls.reset();
|
||||
clickOnSave();
|
||||
component.onSaveTagsChanges();
|
||||
|
||||
expect(tagService.getTagsByNodeId).toHaveBeenCalledWith(node.id);
|
||||
}));
|
||||
});
|
||||
|
||||
it('should throw error on unsuccessful save', fakeAsync(() => {
|
||||
const logService: LogService = TestBed.inject(LogService);
|
||||
component.editable = true;
|
||||
component.isEditingModeGeneralInfo = true;
|
||||
component.readOnly = false;
|
||||
const property = { key: 'properties.property-key', value: 'original-value' } as CardViewBaseItemModel;
|
||||
updateService.update(property, 'updated-value');
|
||||
tick(600);
|
||||
|
||||
const sub = contentMetadataService.error.subscribe((err) => {
|
||||
expect(logService.error).toHaveBeenCalledWith(new Error('My bad'));
|
||||
expect(err.statusCode).toBe(0);
|
||||
expect(err.message).toBe('METADATA.ERRORS.GENERIC');
|
||||
sub.unsubscribe();
|
||||
@@ -340,13 +341,14 @@ describe('ContentMetadataComponent', () => {
|
||||
spyOn(nodesApiService, 'updateNode').and.returnValue(throwError(new Error('My bad')));
|
||||
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable().then(() => clickOnSave());
|
||||
fixture.whenStable().then(() => clickOnGeneralInfoSave());
|
||||
discardPeriodicTasks();
|
||||
flush();
|
||||
}));
|
||||
|
||||
it('should open the confirm dialog when content type is changed', fakeAsync(() => {
|
||||
component.editable = true;
|
||||
component.isEditingModeGeneralInfo = true;
|
||||
component.readOnly = false;
|
||||
const property = { key: 'nodeType', value: 'ft:sbiruli' } as CardViewBaseItemModel;
|
||||
const expectedNode = { ...node, nodeType: 'ft:sbiruli' };
|
||||
spyOn(contentMetadataService, 'openConfirmDialog').and.returnValue(of(true));
|
||||
@@ -357,17 +359,17 @@ describe('ContentMetadataComponent', () => {
|
||||
|
||||
fixture.detectChanges();
|
||||
tick(100);
|
||||
clickOnSave();
|
||||
clickOnGeneralInfoSave();
|
||||
|
||||
tick(100);
|
||||
expect(component.node).toEqual(expectedNode);
|
||||
expect(contentMetadataService.openConfirmDialog).toHaveBeenCalledWith({ nodeType: 'ft:poppoli' });
|
||||
expect(nodesApiService.updateNode).toHaveBeenCalled();
|
||||
discardPeriodicTasks();
|
||||
flush();
|
||||
}));
|
||||
|
||||
it('should call removeTag and assignTagsToNode on TagService after confirming confirmation dialog when content type is changed', fakeAsync(() => {
|
||||
component.editable = true;
|
||||
component.displayTags = true;
|
||||
const property = { key: 'nodeType', value: 'ft:sbiruli' } as CardViewBaseItemModel;
|
||||
const expectedNode = { ...node, nodeType: 'ft:sbiruli' };
|
||||
@@ -380,7 +382,8 @@ describe('ContentMetadataComponent', () => {
|
||||
spyOn(tagService, 'assignTagsToNode').and.returnValue(EMPTY);
|
||||
const tagName1 = tagPaging.list.entries[0].entry.tag;
|
||||
const tagName2 = 'New tag 3';
|
||||
|
||||
component.isEditingModeTags = true;
|
||||
component.readOnly = false;
|
||||
updateService.update(property, 'ft:poppoli');
|
||||
tick(600);
|
||||
|
||||
@@ -388,19 +391,22 @@ describe('ContentMetadataComponent', () => {
|
||||
findTagsCreator().tagsChange.emit([tagName1, tagName2]);
|
||||
tick(100);
|
||||
fixture.detectChanges();
|
||||
clickOnSave();
|
||||
clickOnTagsSave();
|
||||
|
||||
tick(100);
|
||||
const tag1 = new TagBody();
|
||||
tag1.tag = tagName1;
|
||||
const tag2 = new TagBody();
|
||||
tag2.tag = tagName2;
|
||||
const tag1 = new TagBody({ tag: tagName1 });
|
||||
const tag2 = new TagBody({ tag: tagName2 });
|
||||
|
||||
expect(tagService.removeTag).toHaveBeenCalledWith(node.id, tagPaging.list.entries[1].entry.id);
|
||||
expect(tagService.assignTagsToNode).toHaveBeenCalledWith(node.id, [tag1, tag2]);
|
||||
|
||||
discardPeriodicTasks();
|
||||
flush();
|
||||
}));
|
||||
|
||||
it('should retrigger the load of the properties when the content type has changed', fakeAsync(() => {
|
||||
component.editable = true;
|
||||
component.isEditingModeGeneralInfo = true;
|
||||
component.readOnly = false;
|
||||
const property = { key: 'nodeType', value: 'ft:sbiruli' } as CardViewBaseItemModel;
|
||||
const expectedNode = Object.assign({}, node, { nodeType: 'ft:sbiruli' });
|
||||
spyOn(contentMetadataService, 'openConfirmDialog').and.returnValue(of(true));
|
||||
@@ -412,21 +418,227 @@ describe('ContentMetadataComponent', () => {
|
||||
|
||||
fixture.detectChanges();
|
||||
tick(100);
|
||||
clickOnSave();
|
||||
clickOnGeneralInfoSave();
|
||||
|
||||
tick(100);
|
||||
expect(component.node).toEqual(expectedNode);
|
||||
expect(updateService.updateNodeAspect).toHaveBeenCalledWith(expectedNode);
|
||||
|
||||
discardPeriodicTasks();
|
||||
flush();
|
||||
}));
|
||||
});
|
||||
|
||||
describe('toggleEdit', () => {
|
||||
let showErrorSpy: jasmine.Spy;
|
||||
const mockGroup: CardViewGroup = {
|
||||
editable: false,
|
||||
expanded: false,
|
||||
title: '',
|
||||
properties: []
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
component.currentGroup = mockGroup;
|
||||
showErrorSpy = spyOn(notificationService, 'showError').and.stub();
|
||||
});
|
||||
|
||||
it('should toggle General Info editing mode', () => {
|
||||
component.isEditingModeGeneralInfo = false;
|
||||
component.onToggleGeneralInfoEdit();
|
||||
expect(component.isEditingModeTags).toBe(false);
|
||||
expect(component.isEditingModeCategories).toBe(false);
|
||||
expect(component.currentGroup.editable).toBe(false);
|
||||
});
|
||||
|
||||
it('should toggle Tags editing mode', () => {
|
||||
component.isEditingModeTags = false;
|
||||
component.onToggleTagsEdit();
|
||||
expect(component.isTagPanelExpanded).toBe(component.isEditingModeTags);
|
||||
expect(component.tagNameControlVisible).toBe(true);
|
||||
expect(component.isEditingModeCategories).toBe(false);
|
||||
expect(component.currentGroup.editable).toBe(false);
|
||||
});
|
||||
|
||||
it('should toggle Categories editing mode', () => {
|
||||
component.isEditingModeCategories = false;
|
||||
component.onToggleCategoriesEdit();
|
||||
expect(component.isCategoriesPanelExpanded).toBe(component.isEditingModeCategories);
|
||||
expect(component.categoryControlVisible).toBe(true);
|
||||
expect(component.isEditingModeTags).toBe(false);
|
||||
expect(component.currentGroup.editable).toBe(false);
|
||||
});
|
||||
|
||||
it('should toggle Group editing mode', () => {
|
||||
component.onToggleGroupEdit(mockGroup);
|
||||
expect(component.isEditingModeGeneralInfo).toBe(false);
|
||||
expect(component.currentGroup).toBe(mockGroup);
|
||||
});
|
||||
|
||||
it('should show Snackbar when Editing Panel is Active', () => {
|
||||
spyOn(component, 'isEditingPanel').and.returnValue(true);
|
||||
component.onToggleGeneralInfoEdit();
|
||||
expect(component.isEditingPanel).toHaveBeenCalled();
|
||||
expect(showErrorSpy).toHaveBeenCalledWith('METADATA.BASIC.SAVE_OR_DISCARD_CHANGES');
|
||||
});
|
||||
});
|
||||
|
||||
describe('toggleEditMode', () => {
|
||||
it('should toggle general editable', () => {
|
||||
component.isEditingModeGeneralInfo = false;
|
||||
component.onToggleGeneralInfoEdit();
|
||||
expect(component.isEditingModeGeneralInfo).toBe(true);
|
||||
});
|
||||
|
||||
it('should toggle tags editable', () => {
|
||||
component.isEditingModeTags = false;
|
||||
component.onToggleTagsEdit();
|
||||
expect(component.isEditingModeTags).toBe(true);
|
||||
});
|
||||
|
||||
it('should toggle categories editable', () => {
|
||||
component.isEditingModeCategories = false;
|
||||
component.onToggleCategoriesEdit();
|
||||
expect(component.isEditingModeCategories).toBe(true);
|
||||
});
|
||||
|
||||
it('should toggle group editable', () => {
|
||||
const group: CardViewGroup = {
|
||||
editable: false,
|
||||
expanded: false,
|
||||
title: '',
|
||||
properties: []
|
||||
};
|
||||
component.currentGroup = null;
|
||||
component.onToggleGroupEdit(group);
|
||||
expect(group.editable).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Permission', () => {
|
||||
beforeEach(() => {
|
||||
component.readOnly = false;
|
||||
component.node.allowableOperations = null;
|
||||
|
||||
component.ngOnInit();
|
||||
});
|
||||
|
||||
it('should hide the general info edit button if node does not have `update` permissions', async () => {
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
expect(component.readOnly).toBeTrue();
|
||||
expect(getToggleEditButton()).toBeNull();
|
||||
});
|
||||
|
||||
it('should hide the tags edit button if node does not have `update` permissions', async () => {
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
expect(component.readOnly).toBeTrue();
|
||||
expect(getTagsToggleEditButton()).toBeNull();
|
||||
});
|
||||
|
||||
it('should hide the categories edit button if node does not have `update` permissions', async () => {
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
expect(component.readOnly).toBeTrue();
|
||||
expect(getCategoriesToggleEditButton()).toBeNull();
|
||||
});
|
||||
|
||||
it('should hide the groups edit button if node does not have `update` permissions', () => {
|
||||
component.readOnly = false;
|
||||
component.node.allowableOperations = null;
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(getGroupToggleEditButton()).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('hasToggleEdit', () => {
|
||||
it('should return true when editable is false, readOnly is false, and hasAllowableOperations is true', () => {
|
||||
component.isEditingModeGeneralInfo = false;
|
||||
component.readOnly = false;
|
||||
expect(component.canEditGeneralInfo).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false when editable is true', () => {
|
||||
component.isEditingModeGeneralInfo = true;
|
||||
component.readOnly = false;
|
||||
fixture.detectChanges();
|
||||
expect(component.canEditGeneralInfo).toBe(false);
|
||||
expect(component.isEditingGeneralInfo).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('hasTagsToggleEdit', () => {
|
||||
it('should have hasTagsToggleEdit property as expected', () => {
|
||||
component.isEditingModeTags = false;
|
||||
component.readOnly = false;
|
||||
fixture.detectChanges();
|
||||
expect(component.canEditTags).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false when editable is true', () => {
|
||||
component.isEditingModeTags = true;
|
||||
component.readOnly = false;
|
||||
fixture.detectChanges();
|
||||
expect(component.canEditTags).toBe(false);
|
||||
expect(component.isEditingTags).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('hasGroupToggleEdit', () => {
|
||||
it('should return true when group is not editable, not read-only', () => {
|
||||
component.readOnly = false;
|
||||
const group: CardViewGroup = {
|
||||
title: 'Group Title',
|
||||
properties: [],
|
||||
expanded: true,
|
||||
editable: false
|
||||
};
|
||||
const result = component.hasGroupToggleEdit(group);
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('should return true when group is editable, not read-only', () => {
|
||||
component.readOnly = false;
|
||||
const group: CardViewGroup = {
|
||||
title: 'Group Title',
|
||||
properties: [],
|
||||
expanded: true,
|
||||
editable: true
|
||||
};
|
||||
const result = component.isGroupToggleEditing(group);
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('hasCategoriesToggleEdit', () => {
|
||||
it('should have hasCategoriesToggleEdit property as expected', () => {
|
||||
component.isEditingModeCategories = false;
|
||||
component.readOnly = false;
|
||||
expect(component.canEditCategories).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false when editable is true', () => {
|
||||
component.isEditingModeCategories = true;
|
||||
component.readOnly = false;
|
||||
fixture.detectChanges();
|
||||
expect(component.canEditCategories).toBe(false);
|
||||
expect(component.isEditingCategories).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Reseting', () => {
|
||||
it('should reset properties on reset click', async () => {
|
||||
component.changedProperties = { properties: { 'property-key': 'updated-value' } };
|
||||
component.hasMetadataChanged = true;
|
||||
component.tagNameControlVisible = true;
|
||||
component.categoryControlVisible = true;
|
||||
component.editable = true;
|
||||
component.isEditingModeGeneralInfo = true;
|
||||
component.readOnly = false;
|
||||
const expectedNode = Object.assign({}, node, { name: 'some-modified-value' });
|
||||
spyOn(nodesApiService, 'updateNode').and.returnValue(of(expectedNode));
|
||||
|
||||
@@ -562,9 +774,7 @@ describe('ContentMetadataComponent', () => {
|
||||
});
|
||||
|
||||
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').and.stub();
|
||||
|
||||
component.ngOnChanges({ node: new SimpleChange(node, expectedNode, false) });
|
||||
|
||||
@@ -577,21 +787,7 @@ describe('ContentMetadataComponent', () => {
|
||||
|
||||
it('should display card views group when there is at least one property that is not empty', async () => {
|
||||
component.expanded = true;
|
||||
|
||||
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]));
|
||||
spyOn(contentMetadataService, 'getGroupedProperties').and.stub();
|
||||
|
||||
component.ngOnChanges({ node: new SimpleChange(node, expectedNode, false) });
|
||||
|
||||
@@ -601,6 +797,33 @@ describe('ContentMetadataComponent', () => {
|
||||
const basicPropertiesGroup = fixture.debugElement.query(By.css('.adf-metadata-grouped-properties-container mat-expansion-panel'));
|
||||
expect(basicPropertiesGroup).toBeDefined();
|
||||
});
|
||||
|
||||
it('should revert changes for general info panel on cancel', () => {
|
||||
const spy = spyOn(contentMetadataService, 'getBasicProperties');
|
||||
component.onCancelGeneralInfoEdit();
|
||||
expect(spy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should revert changes for getGroupedProperties panel on cancel', () => {
|
||||
spyOn(contentMetadataService, 'getGroupedProperties');
|
||||
component.ngOnChanges({ node: new SimpleChange(node, expectedNode, false) });
|
||||
component.onCancelGroupEdit({} as CardViewGroup);
|
||||
expect(contentMetadataService.getGroupedProperties).toHaveBeenCalledWith(expectedNode, 'custom-preset');
|
||||
});
|
||||
|
||||
it('should revert changes for categories panel on cancel', () => {
|
||||
const spy = spyOn(categoryService, 'getCategoryLinksForNode').and.returnValue(of(categoryPagingResponse));
|
||||
component.displayCategories = true;
|
||||
component.onCancelCategoriesEdit();
|
||||
expect(spy).toHaveBeenCalledWith(expectedNode.id);
|
||||
});
|
||||
|
||||
it('should revert changes for tags panel on cancel', () => {
|
||||
const spy = spyOn(tagService, 'getTagsByNodeId').and.returnValue(of(mockTagPaging()));
|
||||
component.displayTags = true;
|
||||
component.onCancelTagsEdit();
|
||||
expect(spy).toHaveBeenCalledWith(expectedNode.id);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Properties displaying', () => {
|
||||
@@ -725,7 +948,7 @@ describe('ContentMetadataComponent', () => {
|
||||
'cm:versionable': '*'
|
||||
});
|
||||
|
||||
spyOn(classesApi, 'getClass').and.returnValue(Promise.resolve(verResponse));
|
||||
getClassSpy.and.returnValue(Promise.resolve(verResponse));
|
||||
|
||||
component.ngOnChanges({ node: new SimpleChange(node, expectedNode, false) });
|
||||
fixture.detectChanges();
|
||||
@@ -745,7 +968,7 @@ describe('ContentMetadataComponent', () => {
|
||||
'cm:versionable': '*'
|
||||
});
|
||||
|
||||
spyOn(classesApi, 'getClass').and.returnValue(Promise.resolve(verResponse));
|
||||
getClassSpy.and.returnValue(Promise.resolve(verResponse));
|
||||
|
||||
component.ngOnChanges({ node: new SimpleChange(node, expectedNode, false) });
|
||||
fixture.detectChanges();
|
||||
@@ -765,7 +988,7 @@ describe('ContentMetadataComponent', () => {
|
||||
exclude: 'cm:versionable'
|
||||
});
|
||||
|
||||
spyOn(classesApi, 'getClass').and.returnValue(Promise.resolve(verResponse));
|
||||
getClassSpy.and.returnValue(Promise.resolve(verResponse));
|
||||
|
||||
component.ngOnChanges({ node: new SimpleChange(node, expectedNode, false) });
|
||||
fixture.detectChanges();
|
||||
@@ -786,7 +1009,7 @@ describe('ContentMetadataComponent', () => {
|
||||
'cm:versionable': '*'
|
||||
});
|
||||
|
||||
spyOn(classesApi, 'getClass').and.returnValue(Promise.resolve(verResponse));
|
||||
getClassSpy.and.returnValue(Promise.resolve(verResponse));
|
||||
|
||||
component.ngOnChanges({ node: new SimpleChange(node, expectedNode, false) });
|
||||
fixture.detectChanges();
|
||||
@@ -806,7 +1029,7 @@ describe('ContentMetadataComponent', () => {
|
||||
exclude: ['cm:versionable', 'cm:auditable']
|
||||
});
|
||||
|
||||
spyOn(classesApi, 'getClass').and.returnValue(Promise.resolve(verResponse));
|
||||
getClassSpy.and.returnValue(Promise.resolve(verResponse));
|
||||
|
||||
component.ngOnChanges({ node: new SimpleChange(node, expectedNode, false) });
|
||||
fixture.detectChanges();
|
||||
@@ -830,7 +1053,7 @@ describe('ContentMetadataComponent', () => {
|
||||
'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) });
|
||||
fixture.detectChanges();
|
||||
@@ -864,7 +1087,7 @@ describe('ContentMetadataComponent', () => {
|
||||
'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) });
|
||||
fixture.detectChanges();
|
||||
@@ -880,28 +1103,21 @@ describe('ContentMetadataComponent', () => {
|
||||
});
|
||||
|
||||
describe('Expand the panel', () => {
|
||||
let expectedNode: Node;
|
||||
|
||||
beforeEach(() => {
|
||||
expectedNode = { ...node, name: 'some-modified-value' };
|
||||
spyOn(contentMetadataService, 'getGroupedProperties').and.returnValue(of(mockGroupProperties));
|
||||
component.ngOnChanges({ node: new SimpleChange(node, expectedNode, false) });
|
||||
component.isGeneralPanelExpanded = false;
|
||||
});
|
||||
|
||||
it('should open and update drawer with expand section dynamically', async () => {
|
||||
component.displayEmpty = true;
|
||||
component.displayAspect = 'EXIF';
|
||||
component.expanded = true;
|
||||
component.displayEmpty = true;
|
||||
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
let defaultProp = queryDom(fixture);
|
||||
let exifProp = queryDom(fixture, 'EXIF');
|
||||
let customProp = queryDom(fixture, 'CUSTOM');
|
||||
expect(defaultProp.componentInstance.expanded).toBeFalsy();
|
||||
expect(exifProp.componentInstance.expanded).toBeTruthy();
|
||||
expect(customProp.componentInstance.expanded).toBeFalsy();
|
||||
|
||||
component.displayAspect = 'CUSTOM';
|
||||
|
||||
@@ -909,23 +1125,7 @@ describe('ContentMetadataComponent', () => {
|
||||
await fixture.whenStable();
|
||||
|
||||
defaultProp = queryDom(fixture);
|
||||
exifProp = queryDom(fixture, 'EXIF');
|
||||
customProp = queryDom(fixture, 'CUSTOM');
|
||||
expect(defaultProp.componentInstance.expanded).toBeFalsy();
|
||||
expect(exifProp.componentInstance.expanded).toBeFalsy();
|
||||
expect(customProp.componentInstance.expanded).toBeTruthy();
|
||||
|
||||
component.displayAspect = 'Properties';
|
||||
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
defaultProp = queryDom(fixture);
|
||||
exifProp = queryDom(fixture, 'EXIF');
|
||||
customProp = queryDom(fixture, 'CUSTOM');
|
||||
expect(defaultProp.componentInstance.expanded).toBeTruthy();
|
||||
expect(exifProp.componentInstance.expanded).toBeFalsy();
|
||||
expect(customProp.componentInstance.expanded).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should not expand anything if input is wrong', async () => {
|
||||
@@ -937,11 +1137,16 @@ describe('ContentMetadataComponent', () => {
|
||||
await fixture.whenStable();
|
||||
|
||||
const defaultProp = queryDom(fixture);
|
||||
const exifProp = queryDom(fixture, 'EXIF');
|
||||
const customProp = queryDom(fixture, 'CUSTOM');
|
||||
expect(defaultProp.componentInstance.expanded).toBeFalsy();
|
||||
expect(exifProp.componentInstance.expanded).toBeFalsy();
|
||||
expect(customProp.componentInstance.expanded).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should expand the section when displayAspect set as Properties', async () => {
|
||||
component.displayAspect = 'Properties';
|
||||
|
||||
component.ngOnInit();
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(component.isGeneralPanelExpanded).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1053,7 +1258,8 @@ describe('ContentMetadataComponent', () => {
|
||||
});
|
||||
|
||||
it('should render tags after loading tags after clicking on Cancel button', fakeAsync(() => {
|
||||
component.editable = true;
|
||||
component.isEditingModeTags = true;
|
||||
component.readOnly = false;
|
||||
fixture.detectChanges();
|
||||
TestBed.inject(CardViewContentUpdateService).itemUpdated$.next({
|
||||
changed: {}
|
||||
@@ -1061,9 +1267,8 @@ describe('ContentMetadataComponent', () => {
|
||||
tick(500);
|
||||
fixture.detectChanges();
|
||||
spyOn(tagService, 'getTagsByNodeId').and.returnValue(of(tagPaging));
|
||||
|
||||
clickOnCancel();
|
||||
component.editable = false;
|
||||
findCancelTagsButton().click();
|
||||
component.isEditingModeTags = false;
|
||||
fixture.detectChanges();
|
||||
const tagElements = findTagElements();
|
||||
expect(tagElements).toHaveSize(2);
|
||||
@@ -1077,7 +1282,7 @@ describe('ContentMetadataComponent', () => {
|
||||
component.ngOnInit();
|
||||
fixture.detectChanges();
|
||||
|
||||
component.editable = true;
|
||||
component.isEditingModeTags = true;
|
||||
fixture.detectChanges();
|
||||
expect(findTagElements()).toHaveSize(0);
|
||||
});
|
||||
@@ -1087,7 +1292,7 @@ describe('ContentMetadataComponent', () => {
|
||||
let tagsCreator: TagsCreatorComponent;
|
||||
|
||||
beforeEach(() => {
|
||||
component.editable = true;
|
||||
component.isEditingModeTags = true;
|
||||
component.displayTags = true;
|
||||
fixture.detectChanges();
|
||||
tagsCreator = findTagsCreator();
|
||||
@@ -1097,51 +1302,29 @@ describe('ContentMetadataComponent', () => {
|
||||
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(() => {
|
||||
tagsCreator.tagNameControlVisibleChange.emit(true);
|
||||
fixture.detectChanges();
|
||||
tick();
|
||||
tagsCreator.tagNameControlVisibleChange.emit(false);
|
||||
fixture.detectChanges();
|
||||
tick(100);
|
||||
expect(findShowingTagInputButton().hasAttribute('hidden')).toBeFalse();
|
||||
}));
|
||||
|
||||
it('should have assigned correct mode', () => {
|
||||
it('should load in create and assign mode by default', () => {
|
||||
expect(tagsCreator.mode).toBe(TagsCreatorMode.CREATE_AND_ASSIGN);
|
||||
});
|
||||
|
||||
it('should enable cancel button after emitting tagsChange event', () => {
|
||||
component.readOnly = false;
|
||||
tagsCreator.tagsChange.emit(['New tag 1', 'New tag 2', 'New tag 3']);
|
||||
fixture.detectChanges();
|
||||
expect(findCancelButton().disabled).toBeFalse();
|
||||
expect(findCancelTagsButton().disabled).toBeFalse();
|
||||
});
|
||||
|
||||
it('should enable save button after emitting tagsChange event', () => {
|
||||
tagsCreator.tagsChange.emit(['New tag 1', 'New tag 2', 'New tag 3']);
|
||||
component.readOnly = false;
|
||||
fixture.detectChanges();
|
||||
expect(findSaveButton().disabled).toBeFalse();
|
||||
expect(findSaveTagsButton().disabled).toBeFalse();
|
||||
});
|
||||
|
||||
it('should have assigned false to disabledTagsRemoving', () => {
|
||||
expect(tagsCreator.disabledTagsRemoving).toBeFalse();
|
||||
});
|
||||
|
||||
it('should have assigned true to disabledTagsRemoving after clicking on update button', () => {
|
||||
tagsCreator.tagsChange.emit([]);
|
||||
fixture.detectChanges();
|
||||
|
||||
clickOnSave();
|
||||
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', () => {
|
||||
const property = { key: 'properties.property-key', value: 'original-value' } as CardViewBaseItemModel;
|
||||
const expectedNode = { ...node, name: 'some-modified-value' };
|
||||
spyOn(nodesApiService, 'updateNode').and.returnValue(of(expectedNode));
|
||||
@@ -1152,24 +1335,15 @@ describe('ContentMetadataComponent', () => {
|
||||
spyOn(tagService, 'assignTagsToNode').and.returnValue(EMPTY);
|
||||
const tagName1 = tagPaging.list.entries[0].entry.tag;
|
||||
const tagName2 = 'New tag 3';
|
||||
|
||||
component.isEditingModeTags = true;
|
||||
component.readOnly = false;
|
||||
updateService.update(property, 'updated-value');
|
||||
tick(600);
|
||||
|
||||
fixture.detectChanges();
|
||||
tagsCreator.tagsChange.emit([tagName1, tagName2]);
|
||||
clickOnSave();
|
||||
clickOnTagsSave();
|
||||
|
||||
expect(tagsCreator.disabledTagsRemoving).toBeFalse();
|
||||
}));
|
||||
|
||||
it('should have assigned false to tagNameControlVisible after clicking on update button', () => {
|
||||
tagsCreator.tagNameControlVisibleChange.emit(true);
|
||||
tagsCreator.tagsChange.emit([]);
|
||||
fixture.detectChanges();
|
||||
|
||||
clickOnSave();
|
||||
expect(tagsCreator.tagNameControlVisible).toBeFalse();
|
||||
});
|
||||
|
||||
describe('Setting tags', () => {
|
||||
@@ -1199,7 +1373,7 @@ describe('ContentMetadataComponent', () => {
|
||||
});
|
||||
|
||||
it('should show tags creator if editable is true and displayTags is true', () => {
|
||||
component.editable = true;
|
||||
component.isEditingModeTags = true;
|
||||
component.displayTags = true;
|
||||
fixture.detectChanges();
|
||||
expect(findTagsCreator()).toBeDefined();
|
||||
@@ -1263,7 +1437,8 @@ describe('ContentMetadataComponent', () => {
|
||||
});
|
||||
|
||||
it('should render categories after discard changes button is clicked', fakeAsync(() => {
|
||||
component.editable = true;
|
||||
component.isEditingModeCategories = true;
|
||||
component.readOnly = false;
|
||||
fixture.detectChanges();
|
||||
TestBed.inject(CardViewContentUpdateService).itemUpdated$.next({
|
||||
changed: {}
|
||||
@@ -1272,7 +1447,7 @@ describe('ContentMetadataComponent', () => {
|
||||
fixture.detectChanges();
|
||||
|
||||
clickOnCancel();
|
||||
component.editable = false;
|
||||
component.isEditingModeGeneralInfo = false;
|
||||
fixture.detectChanges();
|
||||
|
||||
const categories = getCategories();
|
||||
@@ -1285,7 +1460,7 @@ describe('ContentMetadataComponent', () => {
|
||||
}));
|
||||
|
||||
it('should be hidden when editable is true', () => {
|
||||
component.editable = true;
|
||||
component.isEditingModeCategories = true;
|
||||
fixture.detectChanges();
|
||||
expect(getCategories().length).toBe(0);
|
||||
});
|
||||
@@ -1295,7 +1470,7 @@ describe('ContentMetadataComponent', () => {
|
||||
let categoriesManagementComponent: CategoriesManagementComponent;
|
||||
|
||||
beforeEach(() => {
|
||||
component.editable = true;
|
||||
component.isEditingModeCategories = true;
|
||||
component.displayCategories = true;
|
||||
component.node.aspectNames.push('generalclassifiable');
|
||||
spyOn(categoryService, 'getCategoryLinksForNode').and.returnValue(of(categoryPagingResponse));
|
||||
@@ -1307,23 +1482,7 @@ describe('ContentMetadataComponent', () => {
|
||||
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(() => {
|
||||
categoriesManagementComponent.categoryNameControlVisibleChange.emit(true);
|
||||
fixture.detectChanges();
|
||||
tick();
|
||||
categoriesManagementComponent.categoryNameControlVisibleChange.emit(false);
|
||||
fixture.detectChanges();
|
||||
tick(100);
|
||||
expect(getAssignCategoriesBtn().hasAttribute('hidden')).toBeFalse();
|
||||
}));
|
||||
|
||||
it('should have correct mode', () => {
|
||||
it('should load with assign mode by default', () => {
|
||||
expect(categoriesManagementComponent.managementMode).toBe(CategoriesManagementMode.ASSIGN);
|
||||
});
|
||||
|
||||
@@ -1339,24 +1498,17 @@ describe('ContentMetadataComponent', () => {
|
||||
|
||||
it('should enable discard and save buttons after emitting categories change event', () => {
|
||||
categoriesManagementComponent.categoriesChange.emit([category1, category2]);
|
||||
component.readOnly =false;
|
||||
fixture.detectChanges();
|
||||
expect(findCancelButton().disabled).toBeFalse();
|
||||
expect(findSaveButton().disabled).toBeFalse();
|
||||
expect(findSaveCategoriesButton().disabled).toBeFalse();
|
||||
});
|
||||
|
||||
it('should not disable removal initially', () => {
|
||||
expect(categoriesManagementComponent.disableRemoval).toBeFalse();
|
||||
});
|
||||
|
||||
it('should disable removal on saving', () => {
|
||||
categoriesManagementComponent.categoriesChange.emit([]);
|
||||
fixture.detectChanges();
|
||||
|
||||
clickOnSave();
|
||||
expect(categoriesManagementComponent.disableRemoval).toBeTrue();
|
||||
});
|
||||
|
||||
it('should not disable removal if forkJoin fails', fakeAsync(() => {
|
||||
it('should not disable removal if forkJoin fails', () => {
|
||||
const property = { key: 'properties.property-key', value: 'original-value' } as CardViewBaseItemModel;
|
||||
const expectedNode = { ...node, name: 'some-modified-value' };
|
||||
spyOn(nodesApiService, 'updateNode').and.returnValue(of(expectedNode));
|
||||
@@ -1367,24 +1519,14 @@ describe('ContentMetadataComponent', () => {
|
||||
spyOn(categoryService, 'linkNodeToCategory').and.returnValue(throwError({}));
|
||||
|
||||
updateService.update(property, 'updated-value');
|
||||
tick(600);
|
||||
|
||||
component.readOnly = false;
|
||||
component.isEditingModeCategories = true;
|
||||
fixture.detectChanges();
|
||||
categoriesManagementComponent.categoriesChange.emit([category1, category2]);
|
||||
clickOnSave();
|
||||
findSaveCategoriesButton();
|
||||
|
||||
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', () => {
|
||||
@@ -1410,7 +1552,7 @@ describe('ContentMetadataComponent', () => {
|
||||
it('should render correct custom panel with title and component', () => {
|
||||
component.customPanels = [{ panelTitle: 'testTitle', component: 'testComponent' }];
|
||||
fixture.detectChanges();
|
||||
const panelTitle = fixture.debugElement.queryAll(By.css('mat-panel-title'))[1].nativeElement;
|
||||
const panelTitle = fixture.debugElement.query(By.css('.adf-metadata-custom-panel-title .adf-metadata-properties-title')).nativeElement;
|
||||
const customComponent = fixture.debugElement.query(By.css('adf-dynamic-component')).nativeElement;
|
||||
expect(panelTitle.innerText).toEqual('testTitle');
|
||||
expect(customComponent).toBeDefined();
|
||||
|
@@ -15,23 +15,22 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Component, Input, OnChanges, OnDestroy, OnInit, SimpleChanges, ViewEncapsulation } from '@angular/core';
|
||||
import {
|
||||
Category,
|
||||
CategoryEntry,
|
||||
CategoryLinkBody,
|
||||
CategoryPaging,
|
||||
Node,
|
||||
TagBody,
|
||||
TagEntry,
|
||||
TagPaging
|
||||
} from '@alfresco/js-api';
|
||||
Component,
|
||||
Input,
|
||||
OnChanges,
|
||||
OnDestroy,
|
||||
OnInit,
|
||||
SimpleChanges,
|
||||
ViewEncapsulation
|
||||
} from '@angular/core';
|
||||
import { Category, CategoryEntry, CategoryLinkBody, CategoryPaging, Node, TagBody, TagEntry, TagPaging } from '@alfresco/js-api';
|
||||
import { forkJoin, Observable, of, Subject, zip } from 'rxjs';
|
||||
import {
|
||||
AppConfigService,
|
||||
CardViewBaseItemModel,
|
||||
CardViewItem,
|
||||
LogService,
|
||||
NotificationService,
|
||||
TranslationService,
|
||||
UpdateNotification
|
||||
} from '@alfresco/adf-core';
|
||||
@@ -44,6 +43,8 @@ import { TagsCreatorMode } from '../../../tag/tags-creator/tags-creator-mode';
|
||||
import { TagService } from '../../../tag/services/tag.service';
|
||||
import { CategoryService } from '../../../category/services/category.service';
|
||||
import { CategoriesManagementMode } from '../../../category/categories-management/categories-management-mode';
|
||||
import { AllowableOperationsEnum } from '../../../common/models/allowable-operations.enum';
|
||||
import { ContentService } from '../../../common/services/content.service';
|
||||
|
||||
const DEFAULT_SEPARATOR = ', ';
|
||||
|
||||
@@ -61,17 +62,6 @@ export class ContentMetadataComponent implements OnChanges, OnInit, OnDestroy {
|
||||
@Input()
|
||||
node: Node;
|
||||
|
||||
/** Toggles whether the edit button should be shown */
|
||||
@Input()
|
||||
set editable(editable: boolean) {
|
||||
this._editable = editable;
|
||||
this._assignedTags = [...this.tags];
|
||||
}
|
||||
|
||||
get editable(): boolean {
|
||||
return this._editable;
|
||||
}
|
||||
|
||||
/** Toggles whether to display empty values in the card view */
|
||||
@Input()
|
||||
displayEmpty: boolean = false;
|
||||
@@ -119,9 +109,15 @@ export class ContentMetadataComponent implements OnChanges, OnInit, OnDestroy {
|
||||
@Input()
|
||||
customPanels: ContentMetadataCustomPanel[] = [];
|
||||
|
||||
/**
|
||||
* (optional) This flag sets the metadata in read-only mode,
|
||||
* preventing changes.
|
||||
*/
|
||||
@Input()
|
||||
readOnly = false;
|
||||
|
||||
private _assignedTags: string[] = [];
|
||||
private assignedTagsEntries: TagEntry[] = [];
|
||||
private _editable = false;
|
||||
private _tagsCreatorMode = TagsCreatorMode.CREATE_AND_ASSIGN;
|
||||
private _tags: string[] = [];
|
||||
private targetProperty: CardViewBaseItemModel;
|
||||
@@ -140,16 +136,25 @@ export class ContentMetadataComponent implements OnChanges, OnInit, OnDestroy {
|
||||
categoriesManagementMode = CategoriesManagementMode.ASSIGN;
|
||||
categoryControlVisible = false;
|
||||
classifiableChanged = this.classifiableChangedSubject.asObservable();
|
||||
isGeneralPanelExpanded = true;
|
||||
isTagPanelExpanded: boolean;
|
||||
isCategoriesPanelExpanded: boolean;
|
||||
currentGroup: CardViewGroup;
|
||||
|
||||
isEditingModeGeneralInfo = false;
|
||||
isEditingModeTags = false;
|
||||
isEditingModeCategories = false;
|
||||
|
||||
constructor(
|
||||
private contentMetadataService: ContentMetadataService,
|
||||
private cardViewContentUpdateService: CardViewContentUpdateService,
|
||||
private nodesApiService: NodesApiService,
|
||||
private logService: LogService,
|
||||
private translationService: TranslationService,
|
||||
private appConfig: AppConfigService,
|
||||
private tagService: TagService,
|
||||
private categoryService: CategoryService
|
||||
private categoryService: CategoryService,
|
||||
private contentService: ContentService,
|
||||
private notificationService: NotificationService
|
||||
) {
|
||||
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;
|
||||
@@ -158,23 +163,32 @@ export class ContentMetadataComponent implements OnChanges, OnInit, OnDestroy {
|
||||
|
||||
ngOnInit() {
|
||||
this.cardViewContentUpdateService.itemUpdated$
|
||||
.pipe(
|
||||
debounceTime(500),
|
||||
takeUntil(this.onDestroy$))
|
||||
.subscribe(
|
||||
(updatedNode: UpdateNotification) => {
|
||||
this.hasMetadataChanged = true;
|
||||
this.targetProperty = updatedNode.target;
|
||||
this.updateChanges(updatedNode.changed);
|
||||
}
|
||||
);
|
||||
.pipe(debounceTime(500), takeUntil(this.onDestroy$))
|
||||
.subscribe((updatedNode: UpdateNotification) => {
|
||||
this.hasMetadataChanged = true;
|
||||
this.targetProperty = updatedNode.target;
|
||||
this.updateChanges(updatedNode.changed);
|
||||
});
|
||||
|
||||
this.cardViewContentUpdateService.updatedAspect$.pipe(
|
||||
debounceTime(500),
|
||||
takeUntil(this.onDestroy$))
|
||||
.subscribe((node) => this.loadProperties(node));
|
||||
this.cardViewContentUpdateService.updatedAspect$
|
||||
.pipe(debounceTime(500), takeUntil(this.onDestroy$))
|
||||
.subscribe((node) => {
|
||||
this.node.aspectNames = node?.aspectNames;
|
||||
this.loadProperties(node);
|
||||
});
|
||||
|
||||
this.loadProperties(this.node);
|
||||
this.verifyAllowableOperations();
|
||||
|
||||
if (this.displayAspect === 'Properties') {
|
||||
this.isGeneralPanelExpanded = true;
|
||||
}
|
||||
}
|
||||
|
||||
private verifyAllowableOperations() {
|
||||
if (!this.node?.allowableOperations || !this.contentService.hasAllowableOperations(this.node, AllowableOperationsEnum.UPDATE)) {
|
||||
this.readOnly = true;
|
||||
}
|
||||
}
|
||||
|
||||
get assignedTags(): string[] {
|
||||
@@ -194,14 +208,11 @@ export class ContentMetadataComponent implements OnChanges, OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
protected handleUpdateError(error: Error) {
|
||||
this.logService.error(error);
|
||||
|
||||
let statusCode = 0;
|
||||
|
||||
try {
|
||||
statusCode = JSON.parse(error.message).error.statusCode;
|
||||
} catch {
|
||||
}
|
||||
} catch {}
|
||||
|
||||
let message = `METADATA.ERRORS.${statusCode}`;
|
||||
|
||||
@@ -219,6 +230,10 @@ export class ContentMetadataComponent implements OnChanges, OnInit, OnDestroy {
|
||||
if (changes.node && !changes.node.firstChange) {
|
||||
this.loadProperties(changes.node.currentValue);
|
||||
}
|
||||
if(changes?.readOnly && changes?.readOnly?.currentValue) {
|
||||
this.cancelEditing();
|
||||
this.groupedProperties$ = this.contentMetadataService.getGroupedProperties(this.node, this.preset);
|
||||
}
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
@@ -239,14 +254,36 @@ 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.
|
||||
* Before clicking on that button they are not saved.
|
||||
*/
|
||||
saveChanges() {
|
||||
private onSave(event?: MouseEvent) {
|
||||
event?.stopPropagation();
|
||||
|
||||
this.onSaveChanges();
|
||||
this.cancelEditing();
|
||||
}
|
||||
|
||||
onSaveGeneralInfoChanges(event?: MouseEvent) {
|
||||
this.onSave(event);
|
||||
}
|
||||
|
||||
onSaveTagsChanges(event?: MouseEvent) {
|
||||
this.onSave(event);
|
||||
}
|
||||
|
||||
onSaveCategoriesChanges(event?: MouseEvent) {
|
||||
this.onSave(event);
|
||||
}
|
||||
|
||||
onSaveGroupChanges(group: CardViewGroup, event?: MouseEvent) {
|
||||
this.onSave(event);
|
||||
|
||||
group.editable = false;
|
||||
}
|
||||
|
||||
private onSaveChanges() {
|
||||
this._saving = true;
|
||||
this.tagNameControlVisible = false;
|
||||
this.categoryControlVisible = false;
|
||||
|
||||
if (this.hasContentTypeChanged(this.changedProperties)) {
|
||||
this.contentMetadataService.openConfirmDialog(this.changedProperties).subscribe(() => {
|
||||
this.updateNode();
|
||||
@@ -285,9 +322,168 @@ export class ContentMetadataComponent implements OnChanges, OnInit, OnDestroy {
|
||||
this.categoryControlVisible = false;
|
||||
}
|
||||
|
||||
cancelChanges() {
|
||||
// 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.loadProperties(this.node);
|
||||
|
||||
this.basicProperties$ = this.getProperties(this.node);
|
||||
}
|
||||
|
||||
onCancelCategoriesEdit(event?: MouseEvent) {
|
||||
event?.stopPropagation();
|
||||
|
||||
this.cancelEditing();
|
||||
this.revertChanges();
|
||||
|
||||
this.loadCategoriesForNode(this.node.id);
|
||||
|
||||
const aspectNames = this.node.aspectNames || [];
|
||||
if (!aspectNames.includes('generalclassifiable')) {
|
||||
this.categories = [];
|
||||
this.classifiableChangedSubject.next();
|
||||
}
|
||||
}
|
||||
|
||||
onCancelTagsEdit(event?: MouseEvent) {
|
||||
event?.stopPropagation();
|
||||
|
||||
this.cancelEditing();
|
||||
this.revertChanges();
|
||||
|
||||
this.basicProperties$ = this.getProperties(this.node);
|
||||
this.loadTagsForNode(this.node.id);
|
||||
}
|
||||
|
||||
onCancelGroupEdit(group: CardViewGroup, event?: MouseEvent) {
|
||||
event?.stopPropagation();
|
||||
|
||||
this.cancelEditing();
|
||||
this.revertChanges();
|
||||
|
||||
group.editable = false;
|
||||
this.groupedProperties$ = this.contentMetadataService.getGroupedProperties(this.node, this.preset);
|
||||
}
|
||||
|
||||
onToggleCategoriesEdit(event?: MouseEvent) {
|
||||
event?.stopPropagation();
|
||||
|
||||
if (this.isEditingPanel()) {
|
||||
this.notificationService.showError('METADATA.BASIC.SAVE_OR_DISCARD_CHANGES');
|
||||
return;
|
||||
}
|
||||
|
||||
const currentValue = this.isEditingModeCategories;
|
||||
this.cancelEditing();
|
||||
this.isEditingModeCategories = !currentValue;
|
||||
this.isCategoriesPanelExpanded = this.isEditingModeCategories;
|
||||
this.categoryControlVisible = true;
|
||||
this.groupedProperties$ = this.contentMetadataService.getGroupedProperties(this.node, this.preset);
|
||||
}
|
||||
|
||||
onToggleGroupEdit(group: CardViewGroup, event?: MouseEvent) {
|
||||
event?.stopPropagation();
|
||||
|
||||
if (this.isEditingPanel()) {
|
||||
this.notificationService.showError('METADATA.BASIC.SAVE_OR_DISCARD_CHANGES');
|
||||
return;
|
||||
}
|
||||
this.cancelEditing();
|
||||
|
||||
if (this.currentGroup && this.currentGroup.title !== group.title) {
|
||||
this.currentGroup.editable = false;
|
||||
}
|
||||
|
||||
group.editable = !group.editable;
|
||||
this.currentGroup = group.editable ? group : null;
|
||||
if (group.editable) {
|
||||
group.expanded = true;
|
||||
}
|
||||
}
|
||||
|
||||
get showEmptyTagMessage(): boolean {
|
||||
return this.tags?.length === 0 && !this.isEditingModeTags;
|
||||
}
|
||||
|
||||
get showEmptyCategoryMessage(): boolean {
|
||||
return this.categories?.length === 0 && !this.isEditingModeCategories;
|
||||
}
|
||||
|
||||
get canEditGeneralInfo(): boolean {
|
||||
return !this.isEditingModeGeneralInfo && !this.readOnly;
|
||||
}
|
||||
|
||||
get isEditingGeneralInfo(): boolean {
|
||||
return this.isEditingModeGeneralInfo && !this.readOnly;
|
||||
}
|
||||
|
||||
get canEditTags(): boolean {
|
||||
return !this.isEditingModeTags && !this.readOnly;
|
||||
}
|
||||
|
||||
get isEditingTags(): boolean {
|
||||
return this.isEditingModeTags && !this.readOnly;
|
||||
}
|
||||
|
||||
get canEditCategories(): boolean {
|
||||
return !this.isEditingModeCategories && !this.readOnly;
|
||||
}
|
||||
|
||||
get isEditingCategories(): boolean {
|
||||
return this.isEditingModeCategories && !this.readOnly;
|
||||
}
|
||||
|
||||
hasGroupToggleEdit(group: CardViewGroup): boolean {
|
||||
return !group.editable && !this.readOnly;
|
||||
}
|
||||
|
||||
isGroupToggleEditing(group: CardViewGroup): boolean {
|
||||
return group.editable && !this.readOnly;
|
||||
}
|
||||
|
||||
showGroup(group: CardViewGroup): boolean {
|
||||
@@ -300,10 +496,6 @@ export class ContentMetadataComponent implements OnChanges, OnInit, OnDestroy {
|
||||
return groupTitle === this.displayAspect;
|
||||
}
|
||||
|
||||
canExpandProperties(): boolean {
|
||||
return !this.expanded || this.displayAspect === 'Properties';
|
||||
}
|
||||
|
||||
keyDown(event: KeyboardEvent) {
|
||||
if (event.keyCode === 37 || event.keyCode === 39) { // ArrowLeft && ArrowRight
|
||||
event.stopPropagation();
|
||||
@@ -315,13 +507,15 @@ export class ContentMetadataComponent implements OnChanges, OnInit, OnDestroy {
|
||||
updatedNode: this.nodesApiService.updateNode(this.node.id, this.changedProperties),
|
||||
...(this.displayTags ? this.saveTags() : {}),
|
||||
...(this.displayCategories ? this.saveCategories() : {})
|
||||
}).pipe(
|
||||
catchError((err) => {
|
||||
this.cardViewContentUpdateService.updateElement(this.targetProperty);
|
||||
this.handleUpdateError(err);
|
||||
this._saving = false;
|
||||
return of(null);
|
||||
}))
|
||||
})
|
||||
.pipe(
|
||||
catchError((err) => {
|
||||
this.cardViewContentUpdateService.updateElement(this.targetProperty);
|
||||
this.handleUpdateError(err);
|
||||
this._saving = false;
|
||||
return of(null);
|
||||
})
|
||||
)
|
||||
.subscribe((result: any) => {
|
||||
if (result) {
|
||||
this.updateUndefinedNodeProperties(result.updatedNode);
|
||||
@@ -335,9 +529,9 @@ export class ContentMetadataComponent implements OnChanges, OnInit, OnDestroy {
|
||||
this.loadTagsForNode(this.node.id);
|
||||
}
|
||||
if (this.displayCategories && !!result.LinkingCategories) {
|
||||
this.assignedCategories = result.LinkingCategories.list ?
|
||||
result.LinkingCategories.list.entries.map((entry: CategoryEntry) => entry.entry) :
|
||||
[result.LinkingCategories.entry];
|
||||
this.assignedCategories = result.LinkingCategories.list
|
||||
? result.LinkingCategories.list.entries.map((entry: CategoryEntry) => entry.entry)
|
||||
: [result.LinkingCategories.entry];
|
||||
}
|
||||
}
|
||||
this._saving = false;
|
||||
@@ -358,12 +552,16 @@ export class ContentMetadataComponent implements OnChanges, OnInit, OnDestroy {
|
||||
if (node) {
|
||||
this.basicProperties$ = this.getProperties(node);
|
||||
this.groupedProperties$ = this.contentMetadataService.getGroupedProperties(node, this.preset);
|
||||
|
||||
if (this.displayTags) {
|
||||
this.loadTagsForNode(node.id);
|
||||
}
|
||||
|
||||
if (this.displayCategories) {
|
||||
this.loadCategoriesForNode(node.id);
|
||||
if (!this.node.aspectNames.includes('generalclassifiable')) {
|
||||
|
||||
const aspectNames = node.aspectNames || [];
|
||||
if (!aspectNames.includes('generalclassifiable')) {
|
||||
this.categories = [];
|
||||
this.classifiableChangedSubject.next();
|
||||
}
|
||||
@@ -374,11 +572,14 @@ export class ContentMetadataComponent implements OnChanges, OnInit, OnDestroy {
|
||||
private getProperties(node: Node) {
|
||||
const properties$ = this.contentMetadataService.getBasicProperties(node);
|
||||
const contentTypeProperty$ = this.contentMetadataService.getContentTypeProperty(node);
|
||||
return zip(properties$, contentTypeProperty$)
|
||||
.pipe(map(([properties, contentTypeProperty]) => {
|
||||
const filteredProperties = contentTypeProperty.filter((property) => properties.findIndex((baseProperty) => baseProperty.key === property.key) === -1);
|
||||
return zip(properties$, contentTypeProperty$).pipe(
|
||||
map(([properties, contentTypeProperty]) => {
|
||||
const filteredProperties = contentTypeProperty.filter(
|
||||
(property) => properties.findIndex((baseProperty) => baseProperty.key === property.key) === -1
|
||||
);
|
||||
return [...properties, ...filteredProperties];
|
||||
}));
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
private isEmpty(value: any): boolean {
|
||||
@@ -434,11 +635,14 @@ export class ContentMetadataComponent implements OnChanges, OnInit, OnDestroy {
|
||||
}
|
||||
});
|
||||
if (this.tags.length) {
|
||||
observables.tagsAssigning = this.tagService.assignTagsToNode(this.node.id, this.tags.map((tag) => {
|
||||
const tagBody = new TagBody();
|
||||
tagBody.tag = tag;
|
||||
return tagBody;
|
||||
}));
|
||||
observables.tagsAssigning = this.tagService.assignTagsToNode(
|
||||
this.node.id,
|
||||
this.tags.map((tag) => {
|
||||
const tagBody = new TagBody();
|
||||
tagBody.tag = tag;
|
||||
return tagBody;
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
return observables;
|
||||
|
@@ -49,7 +49,9 @@ export const mockGroupProperties = [
|
||||
clickCallBack: null,
|
||||
displayValue: 400
|
||||
}
|
||||
]
|
||||
],
|
||||
editable: true,
|
||||
expanded: true
|
||||
},
|
||||
{
|
||||
title: 'CUSTOM',
|
||||
@@ -69,6 +71,8 @@ export const mockGroupProperties = [
|
||||
clickCallBack: null,
|
||||
displayValue: 400
|
||||
}
|
||||
]
|
||||
],
|
||||
editable: true,
|
||||
expanded: true
|
||||
}
|
||||
];
|
||||
|
@@ -24,6 +24,7 @@ import { ContentMetadataCardComponent } from './components/content-metadata-card
|
||||
import { TagModule } from '../tag/tag.module';
|
||||
import { CategoriesModule } from '../category/category.module';
|
||||
import { ExtensionsModule } from '@alfresco/adf-extensions';
|
||||
import { ContentMetadataHeaderComponent } from './components/content-metadata/content-metadata-header.component';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
@@ -32,11 +33,13 @@ import { ExtensionsModule } from '@alfresco/adf-extensions';
|
||||
CoreModule,
|
||||
TagModule,
|
||||
CategoriesModule,
|
||||
ExtensionsModule
|
||||
ExtensionsModule,
|
||||
ContentMetadataHeaderComponent
|
||||
],
|
||||
exports: [
|
||||
ContentMetadataComponent,
|
||||
ContentMetadataCardComponent
|
||||
ContentMetadataCardComponent,
|
||||
ContentMetadataHeaderComponent
|
||||
],
|
||||
declarations: [
|
||||
ContentMetadataComponent,
|
||||
|
@@ -20,4 +20,6 @@ import { CardViewItem } from '@alfresco/adf-core';
|
||||
export interface CardViewGroup {
|
||||
title: string;
|
||||
properties: CardViewItem[];
|
||||
editable?: boolean;
|
||||
expanded?: boolean;
|
||||
}
|
||||
|
@@ -18,4 +18,5 @@
|
||||
export interface ContentMetadataCustomPanel {
|
||||
panelTitle: string;
|
||||
component: string;
|
||||
expanded?: boolean;
|
||||
}
|
||||
|
@@ -16,14 +16,15 @@
|
||||
*/
|
||||
|
||||
export * from './components/content-metadata/content-metadata.component';
|
||||
export * from './components/content-metadata/content-metadata-header.component';
|
||||
export * from './components/content-metadata-card/content-metadata-card.component';
|
||||
|
||||
export * from './services/basic-properties.service';
|
||||
export * from './services/content-metadata.service';
|
||||
export * from './services/property-descriptors.service';
|
||||
export * from './services/property-groups-translator.service';
|
||||
export * from './services/config/content-metadata-config.factory';
|
||||
export * from './services/content-type-property.service';
|
||||
|
||||
export * from './services/config/indifferent-config.service';
|
||||
export * from './services/config/layout-oriented-config.service';
|
||||
export * from './services/config/aspect-oriented-config.service';
|
||||
|
@@ -25,7 +25,7 @@ import {
|
||||
CardViewDateItemModel,
|
||||
CardViewIntItemModel,
|
||||
CardViewFloatItemModel,
|
||||
LogService,
|
||||
NotificationService,
|
||||
CardViewBoolItemModel,
|
||||
CardViewDatetimeItemModel,
|
||||
CardViewSelectItemModel,
|
||||
@@ -42,7 +42,7 @@ describe('PropertyGroupTranslatorService', () => {
|
||||
let propertyGroup: OrganisedPropertyGroup;
|
||||
let property: Property;
|
||||
let propertyValues: { [key: string]: any };
|
||||
let logService: LogService;
|
||||
let notificationService: NotificationService;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
@@ -51,7 +51,7 @@ describe('PropertyGroupTranslatorService', () => {
|
||||
ContentTestingModule
|
||||
]
|
||||
});
|
||||
logService = TestBed.inject(LogService);
|
||||
notificationService = TestBed.inject(NotificationService);
|
||||
service = TestBed.inject(PropertyGroupTranslatorService);
|
||||
|
||||
property = {
|
||||
@@ -137,7 +137,7 @@ describe('PropertyGroupTranslatorService', () => {
|
||||
});
|
||||
|
||||
it('should log an error if unrecognised type is found', () => {
|
||||
spyOn(logService, 'error').and.stub();
|
||||
const showError = spyOn(notificationService, 'showError').and.stub();
|
||||
|
||||
property.name = 'FAS:PLAGUE';
|
||||
property.title = 'The Faro Plague';
|
||||
@@ -149,7 +149,7 @@ describe('PropertyGroupTranslatorService', () => {
|
||||
propertyGroups.push(Object.assign({}, propertyGroup));
|
||||
|
||||
service.translateToCardViewGroups(propertyGroups, propertyValues, null);
|
||||
expect(logService.error).toHaveBeenCalledWith('Unknown type for mapping: daemonic:scorcher');
|
||||
expect(showError).toHaveBeenCalledWith('Unknown type for mapping: daemonic:scorcher');
|
||||
});
|
||||
|
||||
it('should fall back to single-line property type if unrecognised type is found', () => {
|
||||
|
@@ -15,7 +15,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Injectable, inject } from '@angular/core';
|
||||
import {
|
||||
CardViewItemProperties,
|
||||
CardViewItem,
|
||||
@@ -26,10 +26,10 @@ import {
|
||||
CardViewDatetimeItemModel,
|
||||
CardViewIntItemModel,
|
||||
CardViewFloatItemModel,
|
||||
LogService,
|
||||
MultiValuePipe,
|
||||
AppConfigService,
|
||||
DecimalNumberPipe
|
||||
DecimalNumberPipe,
|
||||
NotificationService
|
||||
} from '@alfresco/adf-core';
|
||||
import { Property, CardViewGroup, OrganisedPropertyGroup } from '../interfaces/content-metadata.interfaces';
|
||||
import { of } from 'rxjs';
|
||||
@@ -51,10 +51,10 @@ export const RECOGNISED_ECM_TYPES = [D_TEXT, D_MLTEXT, D_DATE, D_DATETIME, D_INT
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class PropertyGroupTranslatorService {
|
||||
private notificationService = inject(NotificationService);
|
||||
valueSeparator: string;
|
||||
|
||||
constructor(private logService: LogService,
|
||||
private multiValuePipe: MultiValuePipe,
|
||||
constructor(private multiValuePipe: MultiValuePipe,
|
||||
private decimalNumberPipe: DecimalNumberPipe,
|
||||
private appConfig: AppConfigService) {
|
||||
this.valueSeparator = this.appConfig.get<string>('content-metadata.multi-value-pipe-separator');
|
||||
@@ -187,7 +187,7 @@ export class PropertyGroupTranslatorService {
|
||||
|
||||
private checkECMTypeValidity(ecmPropertyType: string) {
|
||||
if (RECOGNISED_ECM_TYPES.indexOf(ecmPropertyType) === -1) {
|
||||
this.logService.error(`Unknown type for mapping: ${ecmPropertyType}`);
|
||||
this.notificationService.showError(`Unknown type for mapping: ${ecmPropertyType}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -148,7 +148,7 @@
|
||||
"NO_EXISTING_TAGS": "No Existing Tags",
|
||||
"TITLE": "Create Tags",
|
||||
"CREATE_TAG": "Create: {{tag}}",
|
||||
"NAME": "Name",
|
||||
"TAG_SEARCH_PLACEHOLDER": "Search Tags",
|
||||
"ERRORS": {
|
||||
"EXISTING_TAG": "Tag already exists",
|
||||
"ALREADY_ADDED_TAG": "Tag is already added",
|
||||
@@ -158,8 +158,7 @@
|
||||
"CREATE_TAGS": "Error while creating the tags"
|
||||
},
|
||||
"TOOLTIPS": {
|
||||
"DELETE_TAG": "Delete tag",
|
||||
"HIDE_INPUT": "Hide input"
|
||||
"DELETE_TAG": "Delete tag"
|
||||
},
|
||||
"TAGS_LOADING": "Tags loading",
|
||||
"CREATE_TAGS_SUCCESS": "Tags created successfully"
|
||||
@@ -169,7 +168,6 @@
|
||||
"CATEGORIES_TITLE": "Categories",
|
||||
"NO_CATEGORIES_CREATED": "No Categories created",
|
||||
"NO_CATEGORIES_ASSIGNED": "No Categories assigned",
|
||||
"ASSIGN_CATEGORIES": "Assign Categories",
|
||||
"UNASSIGN_CATEGORY": "Unassign Category",
|
||||
"DELETE_CATEGORY": "Delete Category",
|
||||
"EXISTING_CATEGORIES": "Existing Categories:",
|
||||
@@ -178,7 +176,7 @@
|
||||
"GENERIC_CREATE": "Create: {{name}}",
|
||||
"NAME": "Category name",
|
||||
"LOADING": "Loading",
|
||||
"HIDE_INPUT": "Hide input",
|
||||
"NO_CATEGORIES_ADDED": "There are currently no categories added",
|
||||
"ERRORS": {
|
||||
"NOT_FOUND": "Categories not found",
|
||||
"REQUIRED": "Category name is required",
|
||||
@@ -187,7 +185,8 @@
|
||||
"DUPLICATED_CATEGORY": "Category is already added",
|
||||
"CREATE_CATEGORIES": "Error while creating categories",
|
||||
"EXISTING_CATEGORIES": "Some categories already exist"
|
||||
}
|
||||
},
|
||||
"CATEGORIES_SEARCH_PLACEHOLDER": "Search Categories"
|
||||
},
|
||||
"ADF_FILE_UPLOAD": {
|
||||
"BUTTON": {
|
||||
@@ -488,7 +487,9 @@
|
||||
"MODIFIED_DATE": "Modified Date",
|
||||
"CONTENT_TYPE": "Content Type",
|
||||
"TAGS": "Tags",
|
||||
"ADD_TAG_TOOLTIP": "Add tag"
|
||||
"NO_TAGS_ADDED": "There are currently no tags added",
|
||||
"NO_ITEMS_MESSAGE": "There are currently no {{ groupTitle }} added",
|
||||
"SAVE_OR_DISCARD_CHANGES": "Save or discard changes to continue"
|
||||
},
|
||||
"CONTENT_TYPE": {
|
||||
"DIALOG": {
|
||||
|
@@ -1,54 +1,37 @@
|
||||
<div class="adf-tags-creation">
|
||||
<div *ngIf="tagNameControlVisible" class="adf-tag-name-field">
|
||||
<input #tagNameInput
|
||||
class="adf-tag-search-field"
|
||||
matInput
|
||||
autocomplete="off"
|
||||
[formControl]="tagNameControl"
|
||||
(keyup.enter)="addTag()"
|
||||
adf-auto-focus
|
||||
placeholder="{{'TAG.TAGS_CREATOR.TAG_SEARCH_PLACEHOLDER' | translate}}"
|
||||
/>
|
||||
<mat-error *ngIf="tagNameControl.invalid && tagNameControl.touched">{{ tagNameErrorMessageKey | translate }}</mat-error>
|
||||
</div>
|
||||
<p
|
||||
class="adf-no-tags-message"
|
||||
*ngIf="!tags.length && !tagNameControlVisible">
|
||||
*ngIf="showEmptyTagMessage">
|
||||
{{ 'TAG.TAGS_CREATOR.NO_TAGS_CREATED' | translate }}
|
||||
</p>
|
||||
<div
|
||||
class="adf-tags-list"
|
||||
[class.adf-tags-list-fixed]="!tagNameControlVisible"
|
||||
#tagsList>
|
||||
<p
|
||||
*ngFor="let tag of tags"
|
||||
class="adf-tag adf-label-with-icon-button">
|
||||
<span *ngFor="let tag of tags" class="adf-tag adf-label-with-icon-button">
|
||||
{{ tag }}
|
||||
<button
|
||||
data-automation-id="remove-tag-button"
|
||||
mat-icon-button
|
||||
(click)="removeTag(tag)"
|
||||
[attr.title]="'TAG.TAGS_CREATOR.TOOLTIPS.DELETE_TAG' | translate"
|
||||
[disabled]="disabledTagsRemoving">
|
||||
<mat-icon>remove</mat-icon>
|
||||
[disabled]="disabledTagsRemoving"
|
||||
class="adf-remove-tag">
|
||||
<mat-icon>close</mat-icon>
|
||||
</button>
|
||||
</p>
|
||||
</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>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
|
@@ -1,15 +1,25 @@
|
||||
adf-tags-creator {
|
||||
display: block;
|
||||
margin-left: -24px;
|
||||
|
||||
.adf-label-with-icon-button {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
background: var(--adf-metadata-buttons-background-color);
|
||||
width: fit-content;
|
||||
min-height: 32px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 16px;
|
||||
padding: 6px 0;
|
||||
padding-left: 12px;
|
||||
|
||||
.adf-remove-tag {
|
||||
line-height: 24px;
|
||||
height: 24px;
|
||||
transform: scale(0.7);
|
||||
}
|
||||
}
|
||||
|
||||
.adf-no-tags-message {
|
||||
margin-left: 9px;
|
||||
margin-top: 28.5px;
|
||||
margin-bottom: 0;
|
||||
height: 30px;
|
||||
@@ -17,23 +27,22 @@ adf-tags-creator {
|
||||
|
||||
.adf-tag-name-field,
|
||||
.adf-tag-name-field[hidden] {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-height: 57px;
|
||||
padding-top: 10px;
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
mat-form-field {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
padding-right: 3px;
|
||||
font-size: 14px;
|
||||
}
|
||||
.adf-tag-search-field {
|
||||
background: var(--adf-metadata-buttons-background-color);
|
||||
height: 32px;
|
||||
border-radius: 12px;
|
||||
padding: 0 6px;
|
||||
}
|
||||
|
||||
.adf-create-tag-label {
|
||||
color: var(--theme-primary-color);
|
||||
cursor: pointer;
|
||||
margin-top: -1px;
|
||||
padding-left: 27px;
|
||||
padding-left: 12px;
|
||||
overflow-wrap: anywhere;
|
||||
display: inline-block;
|
||||
padding-right: 12px;
|
||||
@@ -42,25 +51,21 @@ adf-tags-creator {
|
||||
}
|
||||
|
||||
.adf-tags-list {
|
||||
padding-left: 10px;
|
||||
padding-right: 0;
|
||||
overflow: auto;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.adf-tag {
|
||||
margin-top: 0;
|
||||
margin-top: 8px;
|
||||
overflow-wrap: anywhere;
|
||||
|
||||
& + .adf-tag {
|
||||
margin-top: -14px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.adf-tags-creation {
|
||||
padding-left: 27px;
|
||||
padding-right: 22px;
|
||||
}
|
||||
|
||||
.adf-existing-tags-panel {
|
||||
border-top-left-radius: 6px;
|
||||
border-top-right-radius: 6px;
|
||||
@@ -75,7 +80,7 @@ adf-tags-creator {
|
||||
}
|
||||
|
||||
.adf-tags-list {
|
||||
padding-left: 18px;
|
||||
padding-left: 12px;
|
||||
margin-top: -2px;
|
||||
padding-right: 0;
|
||||
display: flex;
|
||||
@@ -94,7 +99,7 @@ adf-tags-creator {
|
||||
}
|
||||
|
||||
.mat-list-item-content-reverse {
|
||||
padding: 0 6px;
|
||||
padding: 0;
|
||||
|
||||
.mat-pseudo-checkbox {
|
||||
display: none;
|
||||
@@ -126,6 +131,6 @@ adf-tags-creator {
|
||||
|
||||
[hidden] {
|
||||
visibility: hidden;
|
||||
display: unset;
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
@@ -21,7 +21,7 @@ import { NotificationService } from '@alfresco/adf-core';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
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 { ReactiveFormsModule } from '@angular/forms';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
@@ -38,8 +38,6 @@ describe('TagsCreatorComponent', () => {
|
||||
let tagService: TagService;
|
||||
let notificationService: NotificationService;
|
||||
|
||||
const tagNameFieldSelector = '.adf-tag-name-field';
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [TagsCreatorComponent],
|
||||
@@ -113,14 +111,6 @@ describe('TagsCreatorComponent', () => {
|
||||
return elements.map(el => el.nativeElement);
|
||||
}
|
||||
|
||||
/**
|
||||
* Click at the hide name input button
|
||||
*/
|
||||
function clickAtHideNameInputButton() {
|
||||
fixture.debugElement.query(By.css(`[data-automation-id="hide-tag-name-input-button"]`)).nativeElement.click();
|
||||
fixture.detectChanges();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get newly added tags
|
||||
*
|
||||
@@ -301,34 +291,6 @@ describe('TagsCreatorComponent', () => {
|
||||
});
|
||||
|
||||
describe('Tag name field', () => {
|
||||
it('should be visible if tagNameControlVisible is true', () => {
|
||||
component.tagNameControlVisible = true;
|
||||
fixture.detectChanges();
|
||||
|
||||
const tagNameField = fixture.debugElement.query(By.css(tagNameFieldSelector));
|
||||
expect(tagNameField).toBeTruthy();
|
||||
expect(tagNameField.nativeElement.hasAttribute('hidden')).toBeFalsy();
|
||||
expect(tagNameField.query(By.directive(MatFormField))).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should be hidden and cleared after clicking button for hiding input', fakeAsync(() => {
|
||||
component.tagNameControlVisible = true;
|
||||
typeTag('test');
|
||||
fixture.detectChanges();
|
||||
tick(100);
|
||||
|
||||
clickAtHideNameInputButton();
|
||||
|
||||
const tagNameField = fixture.debugElement.query(By.css(tagNameFieldSelector));
|
||||
expect(tagNameField).toBeFalsy();
|
||||
|
||||
component.tagNameControlVisible = true;
|
||||
fixture.detectChanges();
|
||||
tick(100);
|
||||
|
||||
expect(getNameInput().value).toBe('');
|
||||
}));
|
||||
|
||||
it('should input be autofocused', fakeAsync(() => {
|
||||
component.tagNameControlVisible = true;
|
||||
fixture.detectChanges();
|
||||
@@ -341,7 +303,6 @@ describe('TagsCreatorComponent', () => {
|
||||
fixture.detectChanges();
|
||||
tick(100);
|
||||
|
||||
clickAtHideNameInputButton();
|
||||
component.tagNameControlVisible = true;
|
||||
fixture.detectChanges();
|
||||
tick(100);
|
||||
@@ -349,24 +310,13 @@ describe('TagsCreatorComponent', () => {
|
||||
expect(getNameInput()).toBe(document.activeElement as HTMLInputElement);
|
||||
}));
|
||||
|
||||
it('should be hidden and cleared on discard changes', fakeAsync(() => {
|
||||
component.tagNameControlVisible = true;
|
||||
component.tags = ['Passed tag 1', 'Passed tag 2'];
|
||||
typeTag('test');
|
||||
fixture.detectChanges();
|
||||
tick(100);
|
||||
expect(getNameInput().value).toBe('test');
|
||||
|
||||
component.tagNameControlVisible = false;
|
||||
fixture.detectChanges();
|
||||
tick(100);
|
||||
expect(getNameInput()).toBeFalsy();
|
||||
|
||||
component.tagNameControlVisible = true;
|
||||
fixture.detectChanges();
|
||||
tick(100);
|
||||
expect(getNameInput().value).toBe('');
|
||||
}));
|
||||
describe('showEmptyTagMessage', () => {
|
||||
it('should return true when tags empty and non editable state', () => {
|
||||
component.tags = [];
|
||||
component.tagNameControlVisible = false;
|
||||
expect(component.showEmptyTagMessage).toBeTrue();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Errors', () => {
|
||||
/**
|
||||
@@ -487,14 +437,6 @@ describe('TagsCreatorComponent', () => {
|
||||
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(() => {
|
||||
component.mode = TagsCreatorMode.CREATE_AND_ASSIGN;
|
||||
|
||||
|
@@ -104,7 +104,7 @@ export class TagsCreatorComponent implements OnInit, OnDestroy {
|
||||
if (tagNameControlVisible) {
|
||||
this._existingTagsPanelVisible = true;
|
||||
setTimeout(() => {
|
||||
this.tagNameInputElement.nativeElement.scrollIntoView();
|
||||
this.tagNameInputElement?.nativeElement?.scrollIntoView();
|
||||
});
|
||||
} else {
|
||||
this._existingTagsPanelVisible = false;
|
||||
@@ -127,11 +127,6 @@ export class TagsCreatorComponent implements OnInit, OnDestroy {
|
||||
*/
|
||||
@Output()
|
||||
tagsChange = new EventEmitter<string[]>();
|
||||
/**
|
||||
* Emitted when input is showing or hiding.
|
||||
*/
|
||||
@Output()
|
||||
tagNameControlVisibleChange = new EventEmitter<boolean>();
|
||||
|
||||
readonly nameErrorMessagesByErrors = new Map<keyof TagNameControlErrors, string>([
|
||||
['duplicatedExistingTag', 'EXISTING_TAG'],
|
||||
@@ -219,6 +214,13 @@ export class TagsCreatorComponent implements OnInit, OnDestroy {
|
||||
return this._tagNameControl;
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns `true` if tags empty and non editable state, otherwise `false`
|
||||
*/
|
||||
get showEmptyTagMessage(): boolean {
|
||||
return this.tags?.length === 0 && !this.tagNameControlVisible;
|
||||
}
|
||||
|
||||
get existingTags(): TagEntry[] {
|
||||
return this._existingTags;
|
||||
}
|
||||
@@ -243,17 +245,6 @@ export class TagsCreatorComponent implements OnInit, OnDestroy {
|
||||
return this._existingTagsPanelVisible;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide input for typing name for new tag or for searching. When input is hidden then panel of existing tags is hidden as well.
|
||||
*/
|
||||
hideNameInput(): void {
|
||||
this.tagNameControlVisible = false;
|
||||
this._existingTagsPanelVisible = false;
|
||||
this.existingTagsPanelVisibilityChange.emit(this.existingTagsPanelVisible);
|
||||
this.tagNameControlVisibleChange.emit(this.tagNameControlVisible);
|
||||
this.clearTagNameInput();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add tags to top list using value which is set in input. Adding tag is not allowed when value in input is invalid
|
||||
* or if user is still typing what means that validation for input is not called yet.
|
||||
@@ -261,7 +252,6 @@ export class TagsCreatorComponent implements OnInit, OnDestroy {
|
||||
addTag(): void {
|
||||
if (!this._typing && !this.tagNameControl.invalid) {
|
||||
this.tags.push(this.tagNameControl.value.trim());
|
||||
this.hideNameInput();
|
||||
this.clearTagNameInput();
|
||||
this.checkScrollbarVisibility();
|
||||
this.tagsChange.emit(this.tags);
|
||||
|
Reference in New Issue
Block a user