diff --git a/lib/content-services/src/lib/category/categories-management/categories-management.component.html b/lib/content-services/src/lib/category/categories-management/categories-management.component.html index 516f0acca9..cf00eb29bb 100644 --- a/lib/content-services/src/lib/category/categories-management/categories-management.component.html +++ b/lib/content-services/src/lib/category/categories-management/categories-management.component.html @@ -1,14 +1,20 @@
-

- {{ noCategoriesMsg | translate }} -

-
+
+ + {{ categoryNameErrorMessageKey | translate }} +
+
+ class="adf-assigned-categories"> {{ category.name }}
-
- - search - - {{ 'CATEGORIES_MANAGEMENT.NAME' | translate }} - - - {{ categoryNameErrorMessageKey | translate }} - - -
+

+ {{ noCategoriesMsg | translate }} +

{{ 'CATEGORIES_MANAGEMENT.GENERIC_CREATE' | translate : { name: categoryNameControl.value } }} @@ -77,8 +59,8 @@
diff --git a/lib/content-services/src/lib/category/categories-management/categories-management.component.scss b/lib/content-services/src/lib/category/categories-management/categories-management.component.scss index 31911029be..4fa8102723 100644 --- a/lib/content-services/src/lib/category/categories-management/categories-management.component.scss +++ b/lib/content-services/src/lib/category/categories-management/categories-management.component.scss @@ -1,8 +1,15 @@ .adf-categories-management { + padding-top: 12px; + .adf-category-name-field { display: flex; justify-content: space-between; width: 100%; + background: var(--adf-metadata-tags-background-color); + height: 32px; + border-radius: 12px; + align-items: center; + padding: 0 12px; mat-form-field { width: 100%; @@ -34,7 +41,6 @@ } .adf-categories-list { - padding-bottom: 10px; .mat-list-base .mat-list-item, .mat-list-base .mat-list-option { diff --git a/lib/content-services/src/lib/category/categories-management/categories-management.component.spec.ts b/lib/content-services/src/lib/category/categories-management/categories-management.component.spec.ts index d7e3801e14..161e866635 100644 --- a/lib/content-services/src/lib/category/categories-management/categories-management.component.spec.ts +++ b/lib/content-services/src/lib/category/categories-management/categories-management.component.spec.ts @@ -224,40 +224,13 @@ describe('CategoriesManagementComponent', () => { component.categoryNameControlVisible = false; fixture.detectChanges(); const categoryControl: HTMLDivElement = fixture.debugElement.query(By.css('.adf-category-name-field')).nativeElement; - expect(categoryControl.hidden).toBeTrue(); + expect(categoryControl.hidden).toBeFalse(); }); it('should be visible when categoryNameControlVisible is true', () => { 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'); - }); - - 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', () => { @@ -335,7 +308,7 @@ describe('CategoriesManagementComponent', () => { it('should have no required validator set for category control', () => { expect(component.categoryNameControl.hasValidator(Validators.required)).toBeFalse(); }); - + it('should display validation error when searching for empty category', fakeAsync(() => { typeCategory(' '); @@ -476,18 +449,6 @@ describe('CategoriesManagementComponent', () => { expect(categoriesChangeSpy).toHaveBeenCalledOnceWith(component.categories); })); - it('should clear and hide input after category is created', fakeAsync(() => { - const controlVisibilityChangeSpy = spyOn(component.categoryNameControlVisibleChange, 'emit'); - createCategory('test'); - const categoryControl: HTMLDivElement = fixture.debugElement.query(By.css('.adf-category-name-field')).nativeElement; - - expect(categoryControl.hidden).toBeTrue(); - expect(controlVisibilityChangeSpy).toHaveBeenCalledOnceWith(false); - expect(getExistingCategoriesList()).toEqual([]); - expect(component.categoryNameControl.value).toBe(''); - expect(component.categoryNameControl.untouched).toBeTrue(); - })); - it('should be able to remove added category', fakeAsync(() => { createCategory('test'); diff --git a/lib/content-services/src/lib/category/categories-management/categories-management.component.ts b/lib/content-services/src/lib/category/categories-management/categories-management.component.ts index 51d4683fe1..31a0991469 100644 --- a/lib/content-services/src/lib/category/categories-management/categories-management.component.ts +++ b/lib/content-services/src/lib/category/categories-management/categories-management.component.ts @@ -223,8 +223,9 @@ 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.categoryNameControl.setValue(''); + this.categoryNameControl.markAsUntouched(); this._existingCategories = null; this.categoriesChange.emit(this.categories); } diff --git a/lib/content-services/src/lib/content-metadata/components/content-metadata-card/content-metadata-card.component.html b/lib/content-services/src/lib/content-metadata/components/content-metadata-card/content-metadata-card.component.html index 3b9930e3b9..0278ba6332 100644 --- a/lib/content-services/src/lib/content-metadata/components/content-metadata-card/content-metadata-card.component.html +++ b/lib/content-services/src/lib/content-metadata/components/content-metadata-card/content-metadata-card.component.html @@ -24,24 +24,6 @@ data-automation-id="meta-data-card-edit-aspect"> menu -
- diff --git a/lib/content-services/src/lib/content-metadata/components/content-metadata-card/content-metadata-card.component.spec.ts b/lib/content-services/src/lib/content-metadata/components/content-metadata-card/content-metadata-card.component.spec.ts index 8d8416f17a..a6a3bc7e39 100644 --- a/lib/content-services/src/lib/content-metadata/components/content-metadata-card/content-metadata-card.component.spec.ts +++ b/lib/content-services/src/lib/content-metadata/components/content-metadata-card/content-metadata-card.component.spec.ts @@ -147,55 +147,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 +162,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(); diff --git a/lib/content-services/src/lib/content-metadata/components/content-metadata/content-metadata.component.html b/lib/content-services/src/lib/content-metadata/components/content-metadata/content-metadata.component.html index d5d41f07c3..c6fc49445d 100644 --- a/lib/content-services/src/lib/content-metadata/components/content-metadata/content-metadata.component.html +++ b/lib/content-services/src/lib/content-metadata/components/content-metadata/content-metadata.component.html @@ -3,12 +3,45 @@ [multi]="multi"> + [attr.data-automation-id]="'adf-metadata-group-properties'" + hideToggle + (opened)="handleGneralPanelOpen()" + (closed)="handleGeneralPanelClose()"> - +
+ + {{ generalInfoPanelState ? 'expand_more' : 'chevron_right'}} + + +
+ +
+
- + - {{ 'METADATA.BASIC.TAGS' | translate }} +
+ + {{ tagsPanelState ? 'expand_more' : 'chevron_right'}} + + +
+
+ +
+
- -
- diff --git a/lib/content-services/src/lib/content-metadata/components/content-metadata/content-metadata.component.scss b/lib/content-services/src/lib/content-metadata/components/content-metadata/content-metadata.component.scss index 99468a7eb3..b090c83c02 100644 --- a/lib/content-services/src/lib/content-metadata/components/content-metadata/content-metadata.component.scss +++ b/lib/content-services/src/lib/content-metadata/components/content-metadata/content-metadata.component.scss @@ -1,37 +1,71 @@ .adf { &-metadata-properties { - .mat-expansion-panel-header.mat-expanded:hover, - .mat-expansion-panel-header.mat-expanded:focus { - background: var(--adf-theme-background-hover-color); - } + + .mat-expansion-panel { + border: 1px solid var(--adf-metadata-property-panel-border-color); + border-radius: 12px !important; + margin-bottom: 12px; mat-expansion-panel-header { - height: 64px; + height: 56px; .adf-metadata-properties-title { font-weight: normal; font-size: 15px; + padding-left: 12px; } } + } .mat-expansion-panel:not([class*='mat-elevation-z']) { box-shadow: none; } - .adf-metadata-properties-tag { - height: 40px; + .adf-mat-divider { + margin-left: -24px; + margin-right: -24px; + } + + .adf-edit-icon-buttons { + color: var(--adf-theme-foreground-text-color-054); + } + + .adf-toggle-icons { display: flex; align-items: center; - margin-top: -14px; - margin-bottom: 1em; + flex: 1; + } - &:first-of-type { - margin-top: 5px; - } + .adf-metadata-properties-tag { + height: 32px; + display: inline-flex; + align-items: center; + border-radius: 16px; + width: fit-content; + background: var(--adf-metadata-tags-background-color); + margin-top: 12px; + padding: 6px 12px; + justify-content: center; + margin-left: 8px; + } + + .adf-metadata-no-tags-added { + display: flex; + align-items: center; + justify-content: center; + font-size: 15px; + padding: 24px; + } + + .adf-metadata-no-catagories-added { + display: flex; + align-items: center; + justify-content: center; + font-size: 15px; + padding: 24px; } &-tags { - padding: 0 10px 0 26px; &-title { display: flex; @@ -42,15 +76,9 @@ } 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 { @@ -73,7 +101,7 @@ &-metadata-categories-header { display: flex; flex-direction: column; - padding: 0 24px; + padding-right: 24px; .adf-metadata-categories-title { display: flex; @@ -92,3 +120,24 @@ } } } + +.acs-details-container { + .mat-tab-body-content { + .adf-content-metadata-card { + .adf-metadata-properties { + .mat-expansion-panel { + width: 755px; + border: 1px solid var(--adf-metadata-property-panel-border-color); + margin: 24px; + border-radius: 12px !important; + } + } + } + } +} + +.adf-content-metadata-card { + .mat-card:not([class*=mat-elevation-z]) { + box-shadow: none; + } +} diff --git a/lib/content-services/src/lib/content-metadata/components/content-metadata/content-metadata.component.spec.ts b/lib/content-services/src/lib/content-metadata/components/content-metadata/content-metadata.component.spec.ts index 9bb10750b3..2effa87398 100644 --- a/lib/content-services/src/lib/content-metadata/components/content-metadata/content-metadata.component.spec.ts +++ b/lib/content-services/src/lib/content-metadata/components/content-metadata/content-metadata.component.spec.ts @@ -49,6 +49,8 @@ describe('ContentMetadataComponent', () => { let folderNode: Node; let tagService: TagService; let categoryService: CategoryService; + let classesApi: ClassesApi; + let getClassSpy: jasmine.Spy; const preset = 'custom-preset'; @@ -71,7 +73,7 @@ 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; @@ -80,17 +82,23 @@ describe('ContentMetadataComponent', () => { fixture.detectChanges(); }; - const findSaveButton = (): HTMLButtonElement => fixture.debugElement.query(By.css('[data-automation-id=save-metadata]')).nativeElement; + const findSaveGeneralInfoButton = (): HTMLButtonElement => fixture.debugElement.query(By.css('[data-automation-id=save-generalInfo-metadata]')).nativeElement; + const findSaveTagsButton = (): HTMLButtonElement => fixture.debugElement.query(By.css('[data-automation-id=save-tags-metadata]')).nativeElement; + const findSaveCategoriesButton = (): HTMLButtonElement => fixture.debugElement.query(By.css('[data-automation-id=save-categories-metadata]')).nativeElement; - const clickOnSave = () => { - 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 findShowingTagInputButton = (): HTMLButtonElement => fixture.debugElement.query(By.css('.adf-tags-buttons')).nativeElement; /** * Get metadata categories @@ -116,7 +124,7 @@ describe('ContentMetadataComponent', () => { * @returns native element */ function getAssignCategoriesBtn(): HTMLButtonElement { - return fixture.debugElement.query(By.css('.adf-metadata-categories-title button')).nativeElement; + return fixture.debugElement.query(By.css('.adf-metadata-categories-title')).nativeElement; } /** @@ -135,8 +143,9 @@ describe('ContentMetadataComponent', () => { fixture.detectChanges(); await fixture.whenStable(); - clickOnSave(); - + const buttonType = 'group'; + const event = new MouseEvent('click'); + component.saveGroupChanges(buttonType, event); await fixture.whenStable(); } @@ -175,6 +184,8 @@ describe('ContentMetadataComponent', () => { nodesApiService = TestBed.inject(NodesApiService); tagService = TestBed.inject(TagService); categoryService = TestBed.inject(CategoryService); + const propertyDescriptorsService = TestBed.inject(PropertyDescriptorsService); + classesApi = propertyDescriptorsService['classesApi']; node = { id: 'node-id', @@ -197,6 +208,7 @@ describe('ContentMetadataComponent', () => { component.node = node; component.preset = preset; spyOn(contentMetadataService, 'getContentTypeProperty').and.returnValue(of([])); + getClassSpy = spyOn(classesApi, 'getClass'); fixture.detectChanges(); }); @@ -286,11 +298,11 @@ describe('ContentMetadataComponent', () => { const tagName2 = 'New tag 3'; updateService.update(property, 'updated-value'); - tick(600); fixture.detectChanges(); findTagsCreator().tagsChange.emit([tagName1, tagName2]); - clickOnSave(); + const mockEvent = new Event('click'); + component.saveTagsChanges(mockEvent); const tag1 = new TagBody(); tag1.tag = tagName1; @@ -300,8 +312,8 @@ describe('ContentMetadataComponent', () => { expect(tagService.assignTagsToNode).toHaveBeenCalledWith(node.id, [tag1, tag2]); })); - it('should call getTagsByNodeId on TagService on save click', fakeAsync(() => { - component.editable = true; + it('should call getTagsByNodeId on TagService on save click', async() => { + component.editableTags = true; component.displayTags = true; const property = { key: 'properties.property-key', value: 'original-value' } as CardViewBaseItemModel; const expectedNode = { ...node, name: 'some-modified-value' }; @@ -313,15 +325,15 @@ 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(); + const mockEvent = new Event('click'); + component.saveTagsChanges(mockEvent); expect(tagService.getTagsByNodeId).toHaveBeenCalledWith(node.id); - })); + }); it('should throw error on unsuccessful save', fakeAsync(() => { const logService: LogService = TestBed.inject(LogService); @@ -340,7 +352,7 @@ describe('ContentMetadataComponent', () => { spyOn(nodesApiService, 'updateNode').and.returnValue(throwError(new Error('My bad'))); fixture.detectChanges(); - fixture.whenStable().then(() => clickOnSave()); + fixture.whenStable().then(() => clickOnGeneralInfoSave()); discardPeriodicTasks(); flush(); })); @@ -357,7 +369,7 @@ describe('ContentMetadataComponent', () => { fixture.detectChanges(); tick(100); - clickOnSave(); + clickOnGeneralInfoSave(); tick(100); expect(component.node).toEqual(expectedNode); @@ -367,7 +379,7 @@ describe('ContentMetadataComponent', () => { })); it('should call removeTag and assignTagsToNode on TagService after confirming confirmation dialog when content type is changed', fakeAsync(() => { - component.editable = true; + component.editableTags = true; component.displayTags = true; const property = { key: 'nodeType', value: 'ft:sbiruli' } as CardViewBaseItemModel; const expectedNode = { ...node, nodeType: 'ft:sbiruli' }; @@ -388,7 +400,7 @@ describe('ContentMetadataComponent', () => { findTagsCreator().tagsChange.emit([tagName1, tagName2]); tick(100); fixture.detectChanges(); - clickOnSave(); + clickOnTagsSave(); tick(100); const tag1 = new TagBody(); @@ -412,7 +424,7 @@ describe('ContentMetadataComponent', () => { fixture.detectChanges(); tick(100); - clickOnSave(); + clickOnGeneralInfoSave(); tick(100); expect(component.node).toEqual(expectedNode); @@ -562,9 +574,8 @@ 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'); component.ngOnChanges({ node: new SimpleChange(node, expectedNode, false) }); @@ -577,21 +588,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'); component.ngOnChanges({ node: new SimpleChange(node, expectedNode, false) }); @@ -627,7 +624,6 @@ describe('ContentMetadataComponent', () => { describe('Display properties with aspect oriented config', () => { let appConfig: AppConfigService; - let classesApi: ClassesApi; let expectedNode: Node; const verResponse: PropertyGroup = { @@ -725,7 +721,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 +741,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 +761,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 +782,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 +802,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 +826,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 +860,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(); @@ -900,8 +896,6 @@ describe('ContentMetadataComponent', () => { 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'; @@ -938,10 +932,8 @@ describe('ContentMetadataComponent', () => { 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(); }); }); @@ -985,6 +977,7 @@ describe('ContentMetadataComponent', () => { it('should render tags after loading tags in ngOnInit', () => { spyOn(tagService, 'getTagsByNodeId').and.returnValue(of(tagPaging)); component.ngOnInit(); + fixture.whenStable(); fixture.detectChanges(); const tagElements = findTagElements(); expect(tagElements).toHaveSize(2); @@ -1053,7 +1046,7 @@ describe('ContentMetadataComponent', () => { }); it('should render tags after loading tags after clicking on Cancel button', fakeAsync(() => { - component.editable = true; + component.editableTags = true; fixture.detectChanges(); TestBed.inject(CardViewContentUpdateService).itemUpdated$.next({ changed: {} @@ -1062,8 +1055,9 @@ describe('ContentMetadataComponent', () => { fixture.detectChanges(); spyOn(tagService, 'getTagsByNodeId').and.returnValue(of(tagPaging)); - clickOnCancel(); - component.editable = false; + const mockEvent = new Event('click'); + component.CancelTagsChanges(mockEvent); + component.editableTags = false; fixture.detectChanges(); const tagElements = findTagElements(); expect(tagElements).toHaveSize(2); @@ -1077,7 +1071,7 @@ describe('ContentMetadataComponent', () => { component.ngOnInit(); fixture.detectChanges(); - component.editable = true; + component.editableTags = true; fixture.detectChanges(); expect(findTagElements()).toHaveSize(0); }); @@ -1087,7 +1081,7 @@ describe('ContentMetadataComponent', () => { let tagsCreator: TagsCreatorComponent; beforeEach(() => { - component.editable = true; + component.editableTags = true; component.displayTags = true; fixture.detectChanges(); tagsCreator = findTagsCreator(); @@ -1097,12 +1091,6 @@ 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(); @@ -1126,7 +1114,7 @@ describe('ContentMetadataComponent', () => { it('should enable save button after emitting tagsChange event', () => { tagsCreator.tagsChange.emit(['New tag 1', 'New tag 2', 'New tag 3']); fixture.detectChanges(); - expect(findSaveButton().disabled).toBeFalse(); + expect(findSaveTagsButton().disabled).toBeFalse(); }); it('should have assigned false to disabledTagsRemoving', () => { @@ -1137,7 +1125,7 @@ describe('ContentMetadataComponent', () => { tagsCreator.tagsChange.emit([]); fixture.detectChanges(); - clickOnSave(); + clickOnTagsSave(); expect(tagsCreator.disabledTagsRemoving).toBeTrue(); }); @@ -1154,11 +1142,11 @@ describe('ContentMetadataComponent', () => { const tagName2 = 'New tag 3'; updateService.update(property, 'updated-value'); - tick(600); + // tick(800); fixture.detectChanges(); tagsCreator.tagsChange.emit([tagName1, tagName2]); - clickOnSave(); + clickOnTagsSave(); expect(tagsCreator.disabledTagsRemoving).toBeFalse(); })); @@ -1168,7 +1156,7 @@ describe('ContentMetadataComponent', () => { tagsCreator.tagsChange.emit([]); fixture.detectChanges(); - clickOnSave(); + clickOnTagsSave(); expect(tagsCreator.tagNameControlVisible).toBeFalse(); }); @@ -1199,7 +1187,7 @@ describe('ContentMetadataComponent', () => { }); it('should show tags creator if editable is true and displayTags is true', () => { - component.editable = true; + component.editableTags = true; component.displayTags = true; fixture.detectChanges(); expect(findTagsCreator()).toBeDefined(); @@ -1263,7 +1251,7 @@ describe('ContentMetadataComponent', () => { }); it('should render categories after discard changes button is clicked', fakeAsync(() => { - component.editable = true; + component.editableCategories = true; fixture.detectChanges(); TestBed.inject(CardViewContentUpdateService).itemUpdated$.next({ changed: {} @@ -1285,7 +1273,7 @@ describe('ContentMetadataComponent', () => { })); it('should be hidden when editable is true', () => { - component.editable = true; + component.editableCategories = true; fixture.detectChanges(); expect(getCategories().length).toBe(0); }); @@ -1295,7 +1283,7 @@ describe('ContentMetadataComponent', () => { let categoriesManagementComponent: CategoriesManagementComponent; beforeEach(() => { - component.editable = true; + component.editableCategories = true; component.displayCategories = true; component.node.aspectNames.push('generalclassifiable'); spyOn(categoryService, 'getCategoryLinksForNode').and.returnValue(of(categoryPagingResponse)); @@ -1307,12 +1295,6 @@ 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(); @@ -1341,22 +1323,14 @@ describe('ContentMetadataComponent', () => { categoriesManagementComponent.categoriesChange.emit([category1, category2]); 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 +1341,12 @@ describe('ContentMetadataComponent', () => { spyOn(categoryService, 'linkNodeToCategory').and.returnValue(throwError({})); updateService.update(property, 'updated-value'); - tick(600); 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', () => { diff --git a/lib/content-services/src/lib/content-metadata/components/content-metadata/content-metadata.component.ts b/lib/content-services/src/lib/content-metadata/components/content-metadata/content-metadata.component.ts index 1a293ec23b..78a351496e 100644 --- a/lib/content-services/src/lib/content-metadata/components/content-metadata/content-metadata.component.ts +++ b/lib/content-services/src/lib/content-metadata/components/content-metadata/content-metadata.component.ts @@ -15,7 +15,7 @@ * limitations under the License. */ -import { Component, Input, OnChanges, OnDestroy, OnInit, SimpleChanges, ViewEncapsulation } from '@angular/core'; +import { ChangeDetectorRef, Component, Input, OnChanges, OnDestroy, OnInit, SimpleChanges, ViewChild, ViewEncapsulation } from '@angular/core'; import { Category, CategoryEntry, @@ -44,6 +44,7 @@ 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 { MatExpansionPanel } from '@angular/material/expansion'; const DEFAULT_SEPARATOR = ', '; @@ -55,6 +56,7 @@ const DEFAULT_SEPARATOR = ', '; encapsulation: ViewEncapsulation.None }) export class ContentMetadataComponent implements OnChanges, OnInit, OnDestroy { + @ViewChild(MatExpansionPanel) panel: MatExpansionPanel; protected onDestroy$ = new Subject(); /** (required) The node entity to fetch metadata about */ @@ -119,6 +121,12 @@ 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; @@ -140,6 +148,11 @@ export class ContentMetadataComponent implements OnChanges, OnInit, OnDestroy { categoriesManagementMode = CategoriesManagementMode.ASSIGN; categoryControlVisible = false; classifiableChanged = this.classifiableChangedSubject.asObservable(); + generalInfoPanelState: boolean; + tagsPanelState: boolean; + editableTags: boolean = false; + categoriesPanelState: boolean; + editableCategories: boolean = false; constructor( private contentMetadataService: ContentMetadataService, @@ -149,7 +162,8 @@ export class ContentMetadataComponent implements OnChanges, OnInit, OnDestroy { private translationService: TranslationService, private appConfig: AppConfigService, private tagService: TagService, - private categoryService: CategoryService + private categoryService: CategoryService, + private cdr: ChangeDetectorRef ) { this.copyToClipboardAction = this.appConfig.get('content-metadata.copy-to-clipboard-action'); this.multiValueSeparator = this.appConfig.get('content-metadata.multi-value-pipe-separator') || DEFAULT_SEPARATOR; @@ -243,7 +257,8 @@ export class ContentMetadataComponent implements OnChanges, OnInit, OnDestroy { * Called after clicking save button. It confirms all changes done for metadata and hides both category and tag name controls. * Before clicking on that button they are not saved. */ - saveChanges() { + saveChanges(event: Event) { + event.stopPropagation(); this._saving = true; this.tagNameControlVisible = false; this.categoryControlVisible = false; @@ -256,6 +271,26 @@ export class ContentMetadataComponent implements OnChanges, OnInit, OnDestroy { } } + saveGeneralInfoChanges(event:Event) { + this.saveChanges(event); + this.editable = !this.editable; + } + + saveTagsChanges(event: Event) { + this.saveChanges(event); + this.editableTags = !this.editableTags; + } + + saveCategoriesChanges(event: Event) { + this.saveChanges(event); + this.editableCategories = !this.editableCategories; + } + + saveGroupChanges(group: any, event:Event) { + this.saveChanges(event); + group.editable = !group.editable; + } + /** * Register all tags which should be assigned to node. Please note that they are just in "register" state and are not yet saved * until button for saving data is clicked. Calling that function causes that save button is enabled. @@ -285,11 +320,100 @@ export class ContentMetadataComponent implements OnChanges, OnInit, OnDestroy { this.categoryControlVisible = false; } - cancelChanges() { + cancelChanges(event: Event) { + event.stopPropagation(); this.revertChanges(); this.loadProperties(this.node); } + cancelGroupChanges(group: any, event: Event) { + this.cancelChanges(event); + group.editable = !group.editable; + } + + CancelTagsChanges(event: Event) { + this.cancelChanges(event); + this.editableTags = !this.editableTags; + } + + cancelCategoriesChanges(event: Event) { + this.cancelChanges(event); + this.editableCategories = !this.editableCategories; + } + + cancelGeneralInfoChanges(event: Event) { + this.cancelChanges(event); + this.editable = !this.editable; + } + + toggleGeneralEdit(event: Event): void { + event.stopPropagation(); + this.editable = !this.editable; + if (!this.panel.expanded) { + this.panel.open(); + } + } + + toggleTagsEdit(event: Event): void { + event.stopPropagation(); + this.editableTags = !this.editableTags; + this.tagNameControlVisible = true; + if (this.editableTags) { + this.tagsPanelState = true; + } else { + this.tagsPanelState = false; + } + } + + toggleCategoriesEdit(event: Event): void { + event.stopPropagation(); + this.editableCategories = !this.editableCategories; + this.categoryControlVisible = true + if (this.editableCategories) { + this.categoriesPanelState = true; + } else { + this.categoriesPanelState = false; + } + } + + toggleEdit(group: any, event: Event): void { + event.stopPropagation(); + group.editable = !group.editable; + if(group.editable) { + group.expanded = true; + } + } + + handlePanelOpen() { + this.tagsPanelState = true; + this.cdr.detectChanges(); + } + + handlePanelClose() { + this.tagsPanelState = false; + this.cdr.detectChanges(); + } + + handleGneralPanelOpen() { + this.generalInfoPanelState = true; + this.cdr.detectChanges(); + } + + handleGeneralPanelClose() { + this.generalInfoPanelState = false; + this.cdr.detectChanges(); + } + + handleCategoryPanelOpen() { + this.categoriesPanelState = true; + this.cdr.detectChanges(); + } + + handleCategoryPanelClose() { + this.categoriesPanelState = false; + this.cdr.detectChanges(); + } + showGroup(group: CardViewGroup): boolean { const properties = group.properties.filter((property) => !this.isEmpty(property.displayValue)); diff --git a/lib/content-services/src/lib/content-metadata/components/content-metadata/mock-data.ts b/lib/content-services/src/lib/content-metadata/components/content-metadata/mock-data.ts index dbc4a45f21..c73212de96 100644 --- a/lib/content-services/src/lib/content-metadata/components/content-metadata/mock-data.ts +++ b/lib/content-services/src/lib/content-metadata/components/content-metadata/mock-data.ts @@ -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, } ]; diff --git a/lib/content-services/src/lib/content-metadata/interfaces/card-view-group.interface.ts b/lib/content-services/src/lib/content-metadata/interfaces/card-view-group.interface.ts index 48c22f4f5e..c85baa54b1 100644 --- a/lib/content-services/src/lib/content-metadata/interfaces/card-view-group.interface.ts +++ b/lib/content-services/src/lib/content-metadata/interfaces/card-view-group.interface.ts @@ -20,4 +20,6 @@ import { CardViewItem } from '@alfresco/adf-core'; export interface CardViewGroup { title: string; properties: CardViewItem[]; + editable: boolean; + expanded: boolean } diff --git a/lib/content-services/src/lib/i18n/en.json b/lib/content-services/src/lib/i18n/en.json index 2d6e5b5bd6..1a73e57020 100644 --- a/lib/content-services/src/lib/i18n/en.json +++ b/lib/content-services/src/lib/i18n/en.json @@ -149,6 +149,7 @@ "TITLE": "Create Tags", "CREATE_TAG": "Create: {{tag}}", "NAME": "Name", + "INPUT_PLACEHOLDER": "Add a tag to these items", "ERRORS": { "EXISTING_TAG": "Tag already exists", "ALREADY_ADDED_TAG": "Tag is already added", @@ -179,6 +180,8 @@ "NAME": "Category name", "LOADING": "Loading", "HIDE_INPUT": "Hide input", + "INPUT_PLACEHOLDER": "Add a categories to these items", + "NO_CATEGORIES_ADDED": "There are currently no categories added", "ERRORS": { "NOT_FOUND": "Categories not found", "REQUIRED": "Category name is required", @@ -490,7 +493,9 @@ "MODIFIED_DATE": "Modified Date", "CONTENT_TYPE": "Content Type", "TAGS": "Tags", - "ADD_TAG_TOOLTIP": "Add tag" + "ADD_TAG_TOOLTIP": "Add tag", + "HEADER_TITLE": "General info", + "NO_TAGS_ADDED": "There are currently no tags added" }, "CONTENT_TYPE": { "DIALOG" :{ diff --git a/lib/content-services/src/lib/tag/tags-creator/tags-creator.component.html b/lib/content-services/src/lib/tag/tags-creator/tags-creator.component.html index 9e7f83145c..351e37a33e 100644 --- a/lib/content-services/src/lib/tag/tags-creator/tags-creator.component.html +++ b/lib/content-services/src/lib/tag/tags-creator/tags-creator.component.html @@ -1,54 +1,39 @@
+
+
+ +
+
+ {{ tagNameErrorMessageKey | translate }}

+ *ngIf="!tags.length"> {{ 'TAG.TAGS_CREATOR.NO_TAGS_CREATED' | translate }}

-

+ {{ tag }} -

-
-
- - search - - {{ 'TAG.TAGS_CREATOR.NAME' | translate }} - - - {{ tagNameErrorMessageKey | translate }} - - +
{ 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 * @@ -308,27 +300,9 @@ describe('TagsCreatorComponent', () => { const tagNameField = fixture.debugElement.query(By.css(tagNameFieldSelector)); expect(tagNameField).toBeTruthy(); expect(tagNameField.nativeElement.hasAttribute('hidden')).toBeFalsy(); - expect(tagNameField.query(By.directive(MatFormField))).toBeTruthy(); + expect(tagNameField).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(); @@ -336,38 +310,6 @@ describe('TagsCreatorComponent', () => { expect(getNameInput()).toBe(document.activeElement as HTMLInputElement); })); - it('should input be autofocused after showing input second time', fakeAsync(() => { - component.tagNameControlVisible = true; - fixture.detectChanges(); - tick(100); - - clickAtHideNameInputButton(); - component.tagNameControlVisible = true; - fixture.detectChanges(); - tick(100); - - expect(getNameInput()).toBe(document.activeElement as HTMLInputElement); - })); - - 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('Errors', () => { /** * Get first error @@ -487,14 +429,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; diff --git a/lib/core/src/lib/i18n/en.json b/lib/core/src/lib/i18n/en.json index 2da9563fd5..b78227e430 100644 --- a/lib/core/src/lib/i18n/en.json +++ b/lib/core/src/lib/i18n/en.json @@ -193,7 +193,7 @@ }, "METADATA": { "BASIC": { - "HEADER": "Properties", + "HEADER": "General info", "NAME": "Name", "TITLE": "Title", "DESCRIPTION": "Description", diff --git a/lib/core/src/lib/styles/_components-variables.scss b/lib/core/src/lib/styles/_components-variables.scss index 4f99fded8e..b3f7d856cb 100644 --- a/lib/core/src/lib/styles/_components-variables.scss +++ b/lib/core/src/lib/styles/_components-variables.scss @@ -45,6 +45,8 @@ --adf-identity-user-info-line-height: $adf-ref-line-height, --adf-identity-user-info-font-size: var(--theme-adf-picture-1-font-size), --adf-user-info-container-margin-right: $adf-ref-margin-right, + --adf-metadata-property-panel-border-color: $adf-metadata-property-panel-border-color, + --adf-metadata-tags-background-color: $adf-metadata-tags-background-color ); // propagates SCSS variables into the CSS variables scope diff --git a/lib/core/src/lib/styles/_reference-variables.scss b/lib/core/src/lib/styles/_reference-variables.scss index d214c5fff0..e82999a004 100644 --- a/lib/core/src/lib/styles/_reference-variables.scss +++ b/lib/core/src/lib/styles/_reference-variables.scss @@ -27,3 +27,5 @@ $adf-ref-height: 40px; $adf-ref-width: 40px; $adf-ref-line-height: 40px; $adf-ref-margin-right: 8px; +$adf-metadata-property-panel-border-color: rgba(0, 0, 0, 0.12); +$adf-metadata-tags-background-color: rgba(33, 33, 33, 0.05);