From 90c69b207cb41aef271a672f6e78c68688bcebb4 Mon Sep 17 00:00:00 2001 From: Anukriti Singh <106949107+AnukritiGL@users.noreply.github.com> Date: Wed, 27 Dec 2023 10:26:11 +0530 Subject: [PATCH] [ACS-5645] Property Panel Feature (#3477) * [ACS-5540] changes for edit aspect button * added aspect edit button * [ACS-5540]fixed unit test cases and added unit test cases * [ACS-5540] Modified changes * [ACS-5540] fixed file lock issue * [ACS-5645]Implemented changes as per review comments * [ACS-5540]Modified the test case title * [ACS-5645] changes for aspect icon * [ACS-5645] fixed aspect lock issue in small screen * [ACS-5540] modified the aspect button changes * [ACS-5540] modified the changes * [ACS-5645] added unit test cases * [ACS-5540] removed unwanted code * [ACS-5540] fixed lock-file bug * [ACS-5540] revert the unwanted changes * [ACS-5540] modified changes * [ACS-5540]Implemented the changes as per the review comments * [ACS-5540] added group lock changes * [ACS-5540] added tooltip * [ACS-5540] Implemented the review comments * [ACS-5540] added tooltips * [ACS-5540] Added styles * [ACS-5540]Added focus * [ACS-5551]updated property panel design * [ACS-5551]added null checks * [ACS-5551] update style * [ACS-5540] changes for edit aspect button * added aspect edit button * [ACS-5540]fixed unit test cases and added unit test cases * [ACS-5540] Modified changes * [ACS-5645]Implemented changes as per review comments * [ACS-5645] changes for aspect icon * [ACS-5540] modified the aspect button changes * [ACS-5540] modified the changes * [ACS-5540] revert the unwanted changes * [ACS-5540] added group lock changes * [ACS-5551]updated property panel design * [ACS-5551]added null checks * [ACS-5551] update style * [ACS-5551] name updated * [ACS-5551] unit test fix * [ACS-5551] header issue fixed * [ACS-5645] style updated * [ACS-5645] border updated * [ACS-6117] fixed aspect dispaly issue * [ACS-5645] different node open issu fixed * [ACS-5645] unit test issue fix * [ACS-5645] unit test fix * [ACS-5645] tabs design modify * [ACS-5645] dependency updated * [link-adf:ACS-564 5-property-panel-feature] test linking * "[link-adf:ACS-5645-property-panel-feature]" * [ACS-5645] revert adf linking changes * add adf configs to libs * fix issue with empty paths * try using adf target * [link-adf:ACS-5645-property-panel-feature] fix core mapping * [link-adf:ACS-5645-property-panel-feature] revert target changes * remove useless styles * remove css hacks * cleanup useless properties * remove useless properties * remove useless code * [ACS-5645] added missing code * [ACS-5654] add icon for full screen * [ACS-5654] nodei con methods moved to thumbnail * [ACS-5654] unit test added and code refactor * [ACS-5645] unit test added * [ACS-5645] panel issue fix * [ACS-5645] removed unit test for editable property * [ACS-5645] removed unused unit test * [ACS-5645] unit test updated * [ACS-5645] updated the unit test * Modified the unit test cases for getNodeIcon * Upsteam ADF-6.6.0-7287333895, Js-api-7.5.0-7287333895 version * Fix failing test cases * Fix failing e2e --------- Co-authored-by: Yasa-Nataliya Co-authored-by: pkundu Co-authored-by: Denys Vuika Co-authored-by: rbahirsheth --- .github/actions/adf-linking/action.yml | 2 +- .../suites/extensions/ext-metadata.test.ts | 1 - .../suites/info-drawer/comments.test.ts | 2 +- .../file-folder-properties.test.ts | 24 +--- .../info-drawer/library-properties.test.ts | 2 +- package-lock.json | 72 +++++----- package.json | 14 +- .../aca-content/assets/app.extensions.json | 14 +- projects/aca-content/assets/i18n/en.json | 8 +- .../aca-content/src/lib/aca-content.module.ts | 2 + .../components/details/details.component.html | 25 ++-- .../components/details/details.component.scss | 51 ++++--- .../details/details.component.spec.ts | 65 ++++++++- .../components/details/details.component.ts | 15 +- .../metadata-tab.component.spec.ts | 135 ++++-------------- .../metadata-tab/metadata-tab.component.ts | 24 +--- .../toggle-edit-offline.component.spec.ts | 19 +++ .../toggle-edit-offline.component.ts | 5 +- .../lib/services/node-actions.service.spec.ts | 1 + .../src/lib/services/node-actions.service.ts | 2 +- .../lib/store/effects/node.effects.spec.ts | 2 +- .../src/lib/store/effects/node.effects.ts | 4 +- .../aca-content/src/lib/ui/application.scss | 9 +- .../src/lib/ui/overrides/ay11.scss | 2 +- .../aca-shared/rules/src/app.rules.spec.ts | 58 ++++++++ projects/aca-shared/rules/src/app.rules.ts | 10 ++ .../rules/src/navigation.rules.spec.ts | 22 +++ .../aca-shared/rules/src/navigation.rules.ts | 5 + .../info-drawer/info-drawer.component.html | 2 +- .../info-drawer/info-drawer.component.spec.ts | 31 +++- .../info-drawer/info-drawer.component.ts | 16 ++- .../page-layout/page-layout.component.scss | 2 + .../services/app.extension.service.spec.ts | 9 ++ .../src/lib/services/app.extension.service.ts | 4 + .../src/components/info-drawer/info-drawer.ts | 4 +- .../components/metadata-card/metadata-card.ts | 2 +- tsconfig.adf.json | 2 +- 37 files changed, 419 insertions(+), 248 deletions(-) diff --git a/.github/actions/adf-linking/action.yml b/.github/actions/adf-linking/action.yml index f5f76c0dee..9c86dcd5c6 100644 --- a/.github/actions/adf-linking/action.yml +++ b/.github/actions/adf-linking/action.yml @@ -9,7 +9,7 @@ runs: shell: bash run: | if [[ $COMMIT_MESSAGE == *"[link-adf:"* ]]; then - echo "BUILD_OPTS=--configuration=adfprod,e2e" >> $GITHUB_ENV + echo "BUILD_OPTS=--configuration=adf,e2e" >> $GITHUB_ENV echo "TEST_OPTS=--configuration=adfprod" >> $GITHUB_ENV echo "E2E_PROTRACTOR_OPTS=--with-local-adf" >> $GITHUB_ENV echo "E2E_TSCONFIG=tsconfig.e2e.adf.json" >> $GITHUB_ENV diff --git a/e2e/protractor/suites/extensions/ext-metadata.test.ts b/e2e/protractor/suites/extensions/ext-metadata.test.ts index 33cc8fc84b..82acc58da8 100644 --- a/e2e/protractor/suites/extensions/ext-metadata.test.ts +++ b/e2e/protractor/suites/extensions/ext-metadata.test.ts @@ -94,7 +94,6 @@ describe('Extensions - Metadata presets', () => { await infoDrawer.waitForInfoDrawerToOpen(); await infoDrawer.clickTab(PROPERTIES_TAB.title); - await BrowserActions.click(metadataCard.expandButton); await metadataCard.waitForFirstExpansionPanel(); }); diff --git a/e2e/protractor/suites/info-drawer/comments.test.ts b/e2e/protractor/suites/info-drawer/comments.test.ts index aede26c4b8..95634458fb 100755 --- a/e2e/protractor/suites/info-drawer/comments.test.ts +++ b/e2e/protractor/suites/info-drawer/comments.test.ts @@ -117,7 +117,7 @@ describe('Comments', () => { await infoDrawer.waitForInfoDrawerToOpen(); await infoDrawer.clickCommentsTab(); - expect(await infoDrawer.getActiveTabTitle()).toBe('COMMENTS'); + expect(await infoDrawer.getActiveTabTitle()).toBe('Comments'); expect(await commentsTab.getCommentsTabHeaderText()).toBe('Comments (0)'); expect(await commentsTab.isCommentTextAreaDisplayed()).toBe(true, 'Comment field not present'); expect(await commentsTab.isAddCommentButtonEnabled()).toBe(false, 'Add comment button not disabled'); diff --git a/e2e/protractor/suites/info-drawer/file-folder-properties.test.ts b/e2e/protractor/suites/info-drawer/file-folder-properties.test.ts index 5abd131d30..58eb17652e 100755 --- a/e2e/protractor/suites/info-drawer/file-folder-properties.test.ts +++ b/e2e/protractor/suites/info-drawer/file-folder-properties.test.ts @@ -59,7 +59,6 @@ describe('File / Folder properties', () => { }; const infoDrawer = new InfoDrawer(); - const { propertiesTab } = infoDrawer; const loginPage = new LoginPage(); const page = new BrowsingPage(); @@ -91,33 +90,12 @@ describe('File / Folder properties', () => { await BrowserActions.click(page.toolbar.viewDetailsButton); await infoDrawer.waitForInfoDrawerToOpen(); - expect(await infoDrawer.getHeaderTitle()).toEqual('Details'); + expect(await infoDrawer.getHeaderTitle()).toEqual(file1.name); expect(await infoDrawer.isPropertiesTabDisplayed()).toBe(true, 'Properties tab is not displayed'); expect(await infoDrawer.isCommentsTabDisplayed()).toBe(true, 'Comments tab is not displayed'); expect(await infoDrawer.getTabsCount()).toBe(2, 'Incorrect number of tabs'); }); - it('[C269004] Less / More information buttons', async () => { - await dataTable.selectItem(file1.name); - await BrowserActions.click(page.toolbar.viewDetailsButton); - await infoDrawer.waitForInfoDrawerToOpen(); - - expect(await propertiesTab.isMoreInfoButtonEnabled()).toBe(true, 'More information button not enabled'); - expect(await propertiesTab.isPropertiesListExpanded()).toBe(true, 'Properties list not expanded'); - - await BrowserActions.click(propertiesTab.moreInfoButton); - - expect(await propertiesTab.isMoreInfoButtonDisplayed()).toBe(false, 'More information button displayed'); - expect(await propertiesTab.isLessInfoButtonEnabled()).toBe(true, 'Less information button not enabled'); - expect(await propertiesTab.isPropertiesListExpanded()).toBe(false, 'Properties list expanded'); - - await BrowserActions.click(propertiesTab.lessInfoButton); - - expect(await propertiesTab.isMoreInfoButtonDisplayed()).toBe(true, 'More information button not displayed'); - expect(await propertiesTab.isLessInfoButtonEnabled()).toBe(false, 'Less information button enabled'); - expect(await propertiesTab.isPropertiesListExpanded()).toBe(true, 'Properties list not expanded'); - }); - it('[C599174] Should be able to make the files/folders info drawer expandable as for Sites', async () => { await dataTable.selectItem(file1.name); await BrowserActions.click(page.toolbar.viewDetailsButton); diff --git a/e2e/protractor/suites/info-drawer/library-properties.test.ts b/e2e/protractor/suites/info-drawer/library-properties.test.ts index 7c6bad7c60..9d0a87602d 100755 --- a/e2e/protractor/suites/info-drawer/library-properties.test.ts +++ b/e2e/protractor/suites/info-drawer/library-properties.test.ts @@ -99,7 +99,7 @@ describe('Library properties', () => { await BrowserActions.click(page.toolbar.viewDetailsButton); await infoDrawer.waitForInfoDrawerToOpen(); - expect(await infoDrawer.getHeaderTitle()).toEqual('Details'); + expect(await infoDrawer.getHeaderTitle()).toEqual(site.name); expect(await infoDrawer.isPropertiesTabDisplayed()).toBe(true, 'Properties tab is not displayed'); expect(await aboutTab.isNameDisplayed()).toBe(true, 'Name field not displayed'); expect(await aboutTab.isLibraryIdDisplayed()).toBe(true, 'Library ID field not displayed'); diff --git a/package-lock.json b/package-lock.json index edee6e14fa..a893e00ca0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,11 +10,11 @@ "hasInstallScript": true, "license": "LGPL-3.0", "dependencies": { - "@alfresco/adf-content-services": "6.6.0-7146652899", - "@alfresco/adf-core": "6.6.0-7146652899", - "@alfresco/adf-extensions": "6.6.0-7146652899", - "@alfresco/eslint-plugin-eslint-angular": "6.6.0-7146652899", - "@alfresco/js-api": "7.5.0-7146652899", + "@alfresco/adf-content-services": "6.6.0-7287333895", + "@alfresco/adf-core": "6.6.0-7287333895", + "@alfresco/adf-extensions": "6.6.0-7287333895", + "@alfresco/eslint-plugin-eslint-angular": "6.6.0-7287333895", + "@alfresco/js-api": "7.5.0-7287333895", "@angular/animations": "14.1.3", "@angular/cdk": "14.1.3", "@angular/common": "14.1.3", @@ -42,8 +42,8 @@ "zone.js": "0.11.8" }, "devDependencies": { - "@alfresco/adf-cli": "6.6.0-7146652899", - "@alfresco/adf-testing": "6.6.0-7146652899", + "@alfresco/adf-cli": "6.6.0-7287333895", + "@alfresco/adf-testing": "6.6.0-7287333895", "@angular-devkit/build-angular": "14.2.11", "@angular-devkit/core": "14.1.2", "@angular-devkit/schematics": "14.1.2", @@ -115,12 +115,12 @@ "dev": true }, "node_modules/@alfresco/adf-cli": { - "version": "6.6.0-7146652899", - "resolved": "https://registry.npmjs.org/@alfresco/adf-cli/-/adf-cli-6.6.0-7146652899.tgz", - "integrity": "sha512-WJWF2j3qj6ioVS5MvLGtbUQ4J0arPOL1FjO7e2NTocmXrs4xOOkfKbAboo5Fr3Lugf1eCY654C9405HLwKGv7w==", + "version": "6.6.0-7287333895", + "resolved": "https://registry.npmjs.org/@alfresco/adf-cli/-/adf-cli-6.6.0-7287333895.tgz", + "integrity": "sha512-VPEG2MvtE41XHh1oHa8g57/9EBO4QkKBLkU6zgu/yQhHqVkUUjZExc7P0bThb1HcGqcTa2oZLUbB2V1pH2kQWQ==", "dev": true, "dependencies": { - "@alfresco/js-api": ">=7.5.0-7146652899", + "@alfresco/js-api": ">=7.5.0-7287333895", "commander": "^6.2.1", "ejs": "^3.1.9", "license-checker": "^25.0.1", @@ -135,15 +135,15 @@ } }, "node_modules/@alfresco/adf-content-services": { - "version": "6.6.0-7146652899", - "resolved": "https://registry.npmjs.org/@alfresco/adf-content-services/-/adf-content-services-6.6.0-7146652899.tgz", - "integrity": "sha512-HY7NgVcj6Kee3oIu7fxjRPoQpuzo+sEk0H1WPfIBlT8Tnft0tquiIkOqDGx2QoCCCDDpWJG7U8nV9bxf7643HQ==", + "version": "6.6.0-7287333895", + "resolved": "https://registry.npmjs.org/@alfresco/adf-content-services/-/adf-content-services-6.6.0-7287333895.tgz", + "integrity": "sha512-qFE0O9aV70qbmDDhi1EsnRNw6xaNju9cdZ/Uie8YXSyRrnxOROHjGu02KhPf89b10+wCGbsEH3+9/ebmle5yOw==", "dependencies": { "tslib": "^2.3.0" }, "peerDependencies": { - "@alfresco/adf-core": ">=6.6.0-7146652899", - "@alfresco/js-api": ">=7.5.0-7146652899", + "@alfresco/adf-core": ">=6.6.0-7287333895", + "@alfresco/js-api": ">=7.5.0-7287333895", "@angular/animations": ">=14.1.3", "@angular/cdk": ">=14.1.2", "@angular/common": ">=14.1.3", @@ -158,9 +158,9 @@ } }, "node_modules/@alfresco/adf-core": { - "version": "6.6.0-7146652899", - "resolved": "https://registry.npmjs.org/@alfresco/adf-core/-/adf-core-6.6.0-7146652899.tgz", - "integrity": "sha512-dcPjWWWYyxcRGuPo2HJLt2mhQgdPbrgbSyLV5jvq95DkgZcFzvxNjhndpzOLPldGRaJlpyBiRvPeLPHU9RD9Zw==", + "version": "6.6.0-7287333895", + "resolved": "https://registry.npmjs.org/@alfresco/adf-core/-/adf-core-6.6.0-7287333895.tgz", + "integrity": "sha512-253YTdCk0HBwcjV5FGn04kRP4DCOoFWBGcuSa9Ghtp8WHcg1UCx0wS3RYspHyVzw0jVPPPI2G1svpWx6xiEwgQ==", "dependencies": { "angular-oauth2-oidc": "^13.0.1", "angular-oauth2-oidc-jwks": "^13.0.1", @@ -168,8 +168,8 @@ "tslib": "^2.3.0" }, "peerDependencies": { - "@alfresco/adf-extensions": ">=6.6.0-7146652899", - "@alfresco/js-api": ">=7.5.0-7146652899", + "@alfresco/adf-extensions": ">=6.6.0-7287333895", + "@alfresco/js-api": ">=7.5.0-7287333895", "@angular/animations": ">=14.1.3", "@angular/cdk": ">=14.1.2", "@angular/common": ">=14.1.3", @@ -184,25 +184,25 @@ } }, "node_modules/@alfresco/adf-extensions": { - "version": "6.6.0-7146652899", - "resolved": "https://registry.npmjs.org/@alfresco/adf-extensions/-/adf-extensions-6.6.0-7146652899.tgz", - "integrity": "sha512-DifRgxXH7iFioBwReUMy1jwAk77juUSR478nIpp8KcF2HYFTG0i5LtJhDu2AIRaguqAEXU3pbL/2DdnAQ94Wkw==", + "version": "6.6.0-7287333895", + "resolved": "https://registry.npmjs.org/@alfresco/adf-extensions/-/adf-extensions-6.6.0-7287333895.tgz", + "integrity": "sha512-sHgYcicbbLKTmcMaiTySt5nolqv6woJ0cbSz+I+DaSLDOeaVuhwMqBap3DEX83rx0w3sfD5JJS0L4bdpokWunQ==", "dependencies": { "tslib": "^2.3.0" }, "peerDependencies": { - "@alfresco/js-api": ">=7.5.0-7146652899", + "@alfresco/js-api": ">=7.5.0-7287333895", "@angular/common": ">=14.1.3", "@angular/core": ">=14.1.3" } }, "node_modules/@alfresco/adf-testing": { - "version": "6.6.0-7146652899", - "resolved": "https://registry.npmjs.org/@alfresco/adf-testing/-/adf-testing-6.6.0-7146652899.tgz", - "integrity": "sha512-c2me5X+DinKhHuoRkDXVpCOQG3LSoNxxDpSAWzwOhwdAlzfl63Vq+SO2hI8HTHDyUXG1sUElOGAekJaAfB8z5A==", + "version": "6.6.0-7287333895", + "resolved": "https://registry.npmjs.org/@alfresco/adf-testing/-/adf-testing-6.6.0-7287333895.tgz", + "integrity": "sha512-VYiMwm2arOM8udampURy5lgZ7mHFrdA9A58Ifp/EZWv/+iiNMxO3C3Z85PTMc5rlhnWUZwodA81WxwvtTabQzg==", "dev": true, "dependencies": { - "@alfresco/js-api": ">=7.5.0-7146652899", + "@alfresco/js-api": ">=7.5.0-7287333895", "@angular/compiler": "14.1.3", "@angular/core": "14.1.3", "date-fns": "^2.30.0", @@ -213,18 +213,18 @@ "zone.js": "~0.11.4" }, "peerDependencies": { - "@alfresco/js-api": ">=7.5.0-7146652899" + "@alfresco/js-api": ">=7.5.0-7287333895" } }, "node_modules/@alfresco/eslint-plugin-eslint-angular": { - "version": "6.6.0-7146652899", - "resolved": "https://registry.npmjs.org/@alfresco/eslint-plugin-eslint-angular/-/eslint-plugin-eslint-angular-6.6.0-7146652899.tgz", - "integrity": "sha512-pb/VAfWPa4f5TKJ9T7iwJRTxxJrcJNG0aRaExdXIGXQIOiRVBwyONiTS9qaFeUFiELdH+7ukmFR6Q6dH/lLV4w==" + "version": "6.6.0-7287333895", + "resolved": "https://registry.npmjs.org/@alfresco/eslint-plugin-eslint-angular/-/eslint-plugin-eslint-angular-6.6.0-7287333895.tgz", + "integrity": "sha512-VRO+W+mpyXLW4YJ2jNjtwUKWAXkFVsadOE1P/U0D1dvjc3LYNTBDPCmMeSF51/Ep/fsODn1zDaURS5xUkeDkzw==" }, "node_modules/@alfresco/js-api": { - "version": "7.5.0-7146652899", - "resolved": "https://registry.npmjs.org/@alfresco/js-api/-/js-api-7.5.0-7146652899.tgz", - "integrity": "sha512-bGRMJwRvdbJHlmbOe/kJeVQRrIpsBGwTYuOb9jr272Z9UTEJWt+Lt777+3MXAqBip1D3odH3Zuxc27lWDwruhQ==", + "version": "7.5.0-7287333895", + "resolved": "https://registry.npmjs.org/@alfresco/js-api/-/js-api-7.5.0-7287333895.tgz", + "integrity": "sha512-f3p3hEBCjuiuyj/7lZbNg0dxePCQbBtNld1P64HG+8JTvuEuzRss0GnuNMOEvDrXKxNF4opU7RNknpBGyTl9GQ==", "dependencies": { "event-emitter": "^0.3.5", "superagent": "^8.0.9", diff --git a/package.json b/package.json index 33f0917eac..541a41ab2a 100644 --- a/package.json +++ b/package.json @@ -33,11 +33,11 @@ }, "private": true, "dependencies": { - "@alfresco/adf-content-services": "6.6.0-7146652899", - "@alfresco/adf-core": "6.6.0-7146652899", - "@alfresco/adf-extensions": "6.6.0-7146652899", - "@alfresco/eslint-plugin-eslint-angular": "6.6.0-7146652899", - "@alfresco/js-api": "7.5.0-7146652899", + "@alfresco/adf-content-services": "6.6.0-7287333895", + "@alfresco/adf-core": "6.6.0-7287333895", + "@alfresco/adf-extensions": "6.6.0-7287333895", + "@alfresco/eslint-plugin-eslint-angular": "6.6.0-7287333895", + "@alfresco/js-api": "7.5.0-7287333895", "@angular/animations": "14.1.3", "@angular/cdk": "14.1.3", "@angular/common": "14.1.3", @@ -65,8 +65,8 @@ "zone.js": "0.11.8" }, "devDependencies": { - "@alfresco/adf-cli": "6.6.0-7146652899", - "@alfresco/adf-testing": "6.6.0-7146652899", + "@alfresco/adf-cli": "6.6.0-7287333895", + "@alfresco/adf-testing": "6.6.0-7287333895", "@angular-devkit/build-angular": "14.2.11", "@angular-devkit/core": "14.1.2", "@angular-devkit/schematics": "14.1.2", diff --git a/projects/aca-content/assets/app.extensions.json b/projects/aca-content/assets/app.extensions.json index 2dabdbafa4..4e6cfd14ee 100644 --- a/projects/aca-content/assets/app.extensions.json +++ b/projects/aca-content/assets/app.extensions.json @@ -1243,6 +1243,18 @@ "visible": "showInfoSelectionButton" } }, + { + "id": "app.toolbar.aspects", + "order": 160, + "title": "APP.ACTIONS.ADD_ASPECTS", + "icon": "playlist_add", + "actions": { + "click": "ASPECT_LIST" + }, + "rules": { + "visible": "editAspects" + } + }, { "id": "app.sidebar.expand", "order": 200, @@ -1252,7 +1264,7 @@ "click": "EXPAND_INFO_DRAWER" }, "rules": { - "visible": "app.navigation.isNotLibraries" + "visible": "canShowExpand" } } ], diff --git a/projects/aca-content/assets/i18n/en.json b/projects/aca-content/assets/i18n/en.json index a3a48e7ad3..3cdfde15f6 100644 --- a/projects/aca-content/assets/i18n/en.json +++ b/projects/aca-content/assets/i18n/en.json @@ -259,7 +259,8 @@ "LEAVE": "Leave Library", "EDIT_OFFLINE": "Edit Offline", "EDIT_OFFLINE_CANCEL": "Cancel Editing", - "CHANGE_ASPECT": "Edit Aspects" + "CHANGE_ASPECT": "Edit Aspects", + "ADD_ASPECTS": "Add Aspects" }, "DIALOGS": { "CONFIRM_PURGE": { @@ -394,15 +395,16 @@ }, "INFO_DRAWER": { "TITLE": "Details", - "CLOSE": "Close", + "REDUCE_PANEL": "Reduce panel", "DATA_LOADING": "Data is loading", + "ICON": "Node Icon", "TABS": { "PROPERTIES": "Properties", "LIBRARY_PROPERTIES": "About", "VERSIONS": "Versions", "COMMENTS": "Comments", "PERMISSIONS": "Permissions", - "EXPAND": "Expand" + "EXPAND": "Expand panel" } }, "TOOLTIPS": { diff --git a/projects/aca-content/src/lib/aca-content.module.ts b/projects/aca-content/src/lib/aca-content.module.ts index 87ade007ff..0614036141 100644 --- a/projects/aca-content/src/lib/aca-content.module.ts +++ b/projects/aca-content/src/lib/aca-content.module.ts @@ -192,6 +192,8 @@ export class ContentServiceExtensionModule { canToggleFavorite: rules.canToggleFavorite, isLibraryManager: rules.isLibraryManager, canEditAspects: rules.canEditAspects, + editAspects: rules.editAspects, + canShowExpand: rules.canShowExpand, canInfoPreview: rules.canInfoPreview, showInfoSelectionButton: rules.showInfoSelectionButton, diff --git a/projects/aca-content/src/lib/components/details/details.component.html b/projects/aca-content/src/lib/components/details/details.component.html index b6dfa450d5..fcc9101aa7 100644 --- a/projects/aca-content/src/lib/components/details/details.component.html +++ b/projects/aca-content/src/lib/components/details/details.component.html @@ -8,18 +8,21 @@
- {{ node.name }} - - - {{ 'APP.INFO_DRAWER.TITLE' | translate }} + + {{ 'APP.INFO_DRAWER.ICON' | translate }} + {{ node.name }} +
+
+ +
-
diff --git a/projects/aca-content/src/lib/components/details/details.component.scss b/projects/aca-content/src/lib/components/details/details.component.scss index a1097ae2e4..b9e4ebb1ef 100644 --- a/projects/aca-content/src/lib/components/details/details.component.scss +++ b/projects/aca-content/src/lib/components/details/details.component.scss @@ -1,19 +1,18 @@ app-details-manager { - .aca-close-details-button { - margin-right: 15px; - margin-top: 2px; - outline: none; - border-radius: 4px; - - &:focus { - background-color: var(--theme-selected-background-color); - outline: 2px solid var(--theme-blue-button-color); - border-radius: 4px; - } + .acs-details-buttons { + display: flex; - &:focus-visible { - outline: 2px solid var(--theme-blue-button-color); + .aca-close-details-button { + margin-right: 15px; + margin-top: 12px; + outline: none; border-radius: 4px; + + &:focus { + background-color: var(--theme-selected-background-color); + outline: 2px solid var(--theme-blue-button-color); + border-radius: 4px; + } } } } @@ -25,14 +24,23 @@ app-details-manager { .aca-details-tabs { margin-top: 40px; - height: calc(100% - 80px); + height: calc(100% - 136px); .mat-tab-body-wrapper { height: 100%; } - mat-tab-header { - text-transform: uppercase; + .mat-tab-list { + margin-left: 50px; + + .mat-tab-labels { + .mat-tab-label { + padding: 0 12px; + min-width: 95px; + letter-spacing: 0.25px; + height: 32px; + } + } } } @@ -41,17 +49,24 @@ app-details-manager { display: flex; align-items: center; justify-content: space-between; + color: var(--theme-metadata-property-panel-title-color); + text-overflow: ellipsis; + white-space: normal; } .aca-details-breadcrumb { font-size: 18px; margin-left: 20px; + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; &-library { font-weight: 900; } - &-item { - font-weight: 100; + &-icon { + display: inline-block; + vertical-align: text-bottom; } } diff --git a/projects/aca-content/src/lib/components/details/details.component.spec.ts b/projects/aca-content/src/lib/components/details/details.component.spec.ts index a4563dae66..3856abecc9 100644 --- a/projects/aca-content/src/lib/components/details/details.component.spec.ts +++ b/projects/aca-content/src/lib/components/details/details.component.spec.ts @@ -26,7 +26,7 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { AppTestingModule } from '../../testing/app-testing.module'; import { DetailsComponent } from './details.component'; import { ActivatedRoute } from '@angular/router'; -import { of, Subject } from 'rxjs'; +import { BehaviorSubject, of, Subject } from 'rxjs'; import { NO_ERRORS_SCHEMA } from '@angular/core'; import { Store } from '@ngrx/store'; import { ContentApiService } from '@alfresco/aca-shared'; @@ -34,13 +34,15 @@ import { STORE_INITIAL_APP_DATA, SetSelectedNodesAction, NavigateToFolder } from import { NodeEntry, PathElement } from '@alfresco/js-api'; import { RouterTestingModule } from '@angular/router/testing'; import { AuthenticationService, PageTitleService } from '@alfresco/adf-core'; -import { BreadcrumbComponent, SearchQueryBuilderService } from '@alfresco/adf-content-services'; +import { BreadcrumbComponent, ContentService, SearchQueryBuilderService } from '@alfresco/adf-content-services'; import { By } from '@angular/platform-browser'; +import { ContentActionRef } from '@alfresco/adf-extensions'; describe('DetailsComponent', () => { let component: DetailsComponent; let fixture: ComponentFixture; let contentApiService: ContentApiService; + let contentService: ContentService; let store: Store; let node: NodeEntry; @@ -50,6 +52,15 @@ describe('DetailsComponent', () => { select: () => mockStream }; + const extensionsServiceMock = { + getAllowedSidebarActions: jasmine.createSpy('getAllowedSidebarActions') + }; + + const mockAspectActions: ContentActionRef[] = []; + + const mockAspectActionsSubject$ = new BehaviorSubject(mockAspectActions); + extensionsServiceMock.getAllowedSidebarActions.and.returnValue(mockAspectActionsSubject$.asObservable()); + beforeEach(() => { TestBed.configureTestingModule({ imports: [AppTestingModule, DetailsComponent], @@ -87,6 +98,7 @@ describe('DetailsComponent', () => { fixture = TestBed.createComponent(DetailsComponent); component = fixture.componentInstance; contentApiService = TestBed.inject(ContentApiService); + contentService = TestBed.inject(ContentService); store = TestBed.inject(Store); node = { @@ -140,4 +152,53 @@ describe('DetailsComponent', () => { fixture.detectChanges(); expect(store.dispatch).toHaveBeenCalledWith(new SetSelectedNodesAction([node])); }); + + it('should set aspectActions from extensions', async () => { + extensionsServiceMock.getAllowedSidebarActions.and.returnValue(of(mockAspectActions)); + fixture.detectChanges(); + await fixture.whenStable().then(() => { + expect(component.aspectActions).toEqual(mockAspectActions); + }); + }); + + it('should return the icon when getNodeIcon is called', () => { + const expectedIcon = 'assets/images/ft_ic_folder'; + spyOn(contentService, 'getNodeIcon').and.returnValue(expectedIcon); + fixture.detectChanges(); + component.ngOnInit(); + expect(contentService.getNodeIcon).toHaveBeenCalled(); + expect(component.nodeIcon).toContain(expectedIcon); + }); + + it('should set aspectActions from extension mock', () => { + const extensionMock = { + getAllowedSidebarActions: () => + of([ + { + id: 'app.sidebar.close', + order: 100, + title: 'close', + icon: 'highlight_off' + } + ]) + }; + + extensionsServiceMock.getAllowedSidebarActions.and.returnValue(of(extensionMock)); + fixture.detectChanges(); + fixture + .whenStable() + .then(() => { + expect(component.aspectActions).toEqual([ + { + id: 'app.sidebar.close', + order: 100, + title: 'close', + icon: 'highlight_off' + } as ContentActionRef + ]); + }) + .catch((error) => { + fail(`An error occurred: ${error}`); + }); + }); }); diff --git a/projects/aca-content/src/lib/components/details/details.component.ts b/projects/aca-content/src/lib/components/details/details.component.ts index a5e8ee0eb9..95ebbb1c19 100644 --- a/projects/aca-content/src/lib/components/details/details.component.ts +++ b/projects/aca-content/src/lib/components/details/details.component.ts @@ -27,7 +27,7 @@ import { ActivatedRoute } from '@angular/router'; import { ContentApiService, PageComponent, PageLayoutComponent, ToolbarComponent } from '@alfresco/aca-shared'; import { NavigateToFolder, NavigateToPreviousPage, SetSelectedNodesAction } from '@alfresco/aca-shared/store'; import { Subject } from 'rxjs'; -import { BreadcrumbModule, PermissionManagerModule } from '@alfresco/adf-content-services'; +import { BreadcrumbModule, ContentService, PermissionManagerModule } from '@alfresco/adf-content-services'; import { CommonModule } from '@angular/common'; import { TranslateModule } from '@ngx-translate/core'; import { MatIconModule } from '@angular/material/icon'; @@ -37,6 +37,8 @@ import { MatButtonModule } from '@angular/material/button'; import { MetadataTabComponent } from '../info-drawer/metadata-tab/metadata-tab.component'; import { CommentsTabComponent } from '../info-drawer/comments-tab/comments-tab.component'; import { NodeEntry, PathElement } from '@alfresco/js-api'; +import { takeUntil } from 'rxjs/operators'; +import { ContentActionRef } from '@alfresco/adf-extensions'; @Component({ standalone: true, @@ -64,8 +66,10 @@ export class DetailsComponent extends PageComponent implements OnInit, OnDestroy isLoading: boolean; onDestroy$ = new Subject(); activeTab = 1; + aspectActions: Array = []; + nodeIcon: string; - constructor(private route: ActivatedRoute, private contentApi: ContentApiService) { + constructor(private route: ActivatedRoute, private contentApi: ContentApiService, private contentService: ContentService) { super(); } @@ -84,8 +88,15 @@ export class DetailsComponent extends PageComponent implements OnInit, OnDestroy this.node = node.entry; this.isLoading = false; this.store.dispatch(new SetSelectedNodesAction([{ entry: this.node }])); + this.nodeIcon = this.contentService.getNodeIcon(this.node); }); }); + this.extensions + .getAllowedSidebarActions() + .pipe(takeUntil(this.onDestroy$)) + .subscribe((aspectActions) => { + this.aspectActions = aspectActions; + }); } setActiveTab(tabName: string) { diff --git a/projects/aca-content/src/lib/components/info-drawer/metadata-tab/metadata-tab.component.spec.ts b/projects/aca-content/src/lib/components/info-drawer/metadata-tab/metadata-tab.component.spec.ts index d85b2e4cc6..380edb4838 100644 --- a/projects/aca-content/src/lib/components/info-drawer/metadata-tab/metadata-tab.component.spec.ts +++ b/projects/aca-content/src/lib/components/info-drawer/metadata-tab/metadata-tab.component.spec.ts @@ -27,24 +27,23 @@ import { Node } from '@alfresco/js-api'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { AppTestingModule } from '../../../testing/app-testing.module'; import { AppConfigService } from '@alfresco/adf-core'; -import { Store } from '@ngrx/store'; -import { AppState, EditOfflineAction, SetInfoDrawerMetadataAspectAction } from '@alfresco/aca-shared/store'; +import { EditOfflineAction } from '@alfresco/aca-shared/store'; import { By } from '@angular/platform-browser'; import { AppExtensionService, NodePermissionService } from '@alfresco/aca-shared'; import { Actions } from '@ngrx/effects'; import { of, Subject } from 'rxjs'; import { ContentActionType } from '@alfresco/adf-extensions'; -import { CategoryService, ContentMetadataCardComponent, TagService } from '@alfresco/adf-content-services'; +import { CategoryService, ContentMetadataComponent, ContentMetadataService, TagService } from '@alfresco/adf-content-services'; describe('MetadataTabComponent', () => { let fixture: ComponentFixture; let component: MetadataTabComponent; - let store: Store; let appConfig: AppConfigService; let extensions: AppExtensionService; let nodePermissionService: NodePermissionService; let actions$: Subject; let appExtensionService: AppExtensionService; + let contentMetadataService: ContentMetadataService; const presets = { default: { @@ -65,9 +64,11 @@ describe('MetadataTabComponent', () => { }); nodePermissionService = TestBed.inject(NodePermissionService); appExtensionService = TestBed.inject(AppExtensionService); + contentMetadataService = TestBed.inject(ContentMetadataService); spyOn(nodePermissionService, 'check').and.callFake((source: Node, permissions: string[]) => { return permissions.some((permission) => source.allowableOperations.includes(permission)); }); + spyOn(contentMetadataService, 'getGroupedProperties').and.returnValue(of()); }); afterEach(() => { @@ -99,40 +100,40 @@ describe('MetadataTabComponent', () => { }); }); - describe('canUpdateNode', () => { + describe('readOnly', () => { beforeEach(() => { fixture = TestBed.createComponent(MetadataTabComponent); component = fixture.componentInstance; }); - it('should return true if node is not locked and has update permission', async () => { + it('should return false if node is not locked and has update permission', async () => { component.node = { isLocked: false, allowableOperations: ['update'] } as Node; component.ngOnInit(); - expect(component.canUpdateNode).toBe(true); + expect(component.readOnly).toBe(false); }); - it('should return false if node is locked', () => { + it('should return true if node is locked', () => { component.node = { isLocked: true, allowableOperations: ['update'] } as Node; component.ngOnInit(); - expect(component.canUpdateNode).toBe(false); + expect(component.readOnly).toBe(true); }); - it('should return false if node has no update permission', () => { + it('should return true if node has no update permission', () => { component.node = { isLocked: false, allowableOperations: ['other'] } as Node; component.ngOnInit(); - expect(component.canUpdateNode).toBe(false); + expect(component.readOnly).toBe(true); }); - it('should return false if node has read only property', () => { + it('should return true if node has read only property', () => { component.node = { isLocked: false, allowableOperations: ['update'], @@ -141,7 +142,7 @@ describe('MetadataTabComponent', () => { } } as Node; component.ngOnInit(); - expect(component.canUpdateNode).toBe(false); + expect(component.readOnly).toBe(true); }); describe('set by triggering EditOfflineAction', () => { @@ -160,124 +161,50 @@ describe('MetadataTabComponent', () => { id: component.node.id } as Node }); - component.canUpdateNode = true; + component.readOnly = true; }); - it('should have set true if node is not locked and has update permission', () => { - component.canUpdateNode = false; + it('should have set false if node is not locked and has update permission', () => { + component.readOnly = true; actions$.next(editOfflineAction); - expect(component.canUpdateNode).toBeTrue(); + expect(component.readOnly).toBeFalse(); }); it('should not have set false if changed node has different id than original', () => { editOfflineAction.payload.entry.id = 'some other id'; editOfflineAction.payload.entry.isLocked = true; actions$.next(editOfflineAction); - expect(component.canUpdateNode).toBeTrue(); + expect(component.readOnly).toBeTrue(); }); - it('should have set false if node is locked', () => { + it('should have set true if node is locked', () => { editOfflineAction.payload.entry.isLocked = true; actions$.next(editOfflineAction); - expect(component.canUpdateNode).toBeFalse(); + expect(component.readOnly).toBeTrue(); }); - it('should have set false if node has no update permission', () => { + it('should have set true if node has no update permission', () => { editOfflineAction.payload.entry.allowableOperations = ['other']; actions$.next(editOfflineAction); - expect(component.canUpdateNode).toBeFalse(); + expect(component.readOnly).toBeTrue(); }); - it('should have set false if node has read only property', () => { + it('should have set true if node has read only property', () => { editOfflineAction.payload.entry.properties = { 'cm:lockType': 'WRITE_LOCK' }; actions$.next(editOfflineAction); - expect(component.canUpdateNode).toBeFalse(); + expect(component.readOnly).toBeTrue(); }); }); }); - describe('editable', () => { - let editOfflineAction: EditOfflineAction; + describe('ContentMetadataComponent', () => { + const getContentMetadata = (): ContentMetadataComponent => fixture.debugElement.query(By.directive(ContentMetadataComponent)).componentInstance; beforeEach(() => { fixture = TestBed.createComponent(MetadataTabComponent); component = fixture.componentInstance; - component.node = { - id: 'some id', - allowableOperations: [] - } as Node; - component.ngOnInit(); - editOfflineAction = new EditOfflineAction({ - entry: { - isLocked: false, - allowableOperations: ['update'], - id: component.node.id - } as Node - }); - component.editable = true; - }); - - it('should not have set false if node is not locked and has update permission', () => { - actions$.next(editOfflineAction); - expect(component.editable).toBeTrue(); - }); - - it('should not have set false if changed node has different id than original', () => { - editOfflineAction.payload.entry.id = 'some other id'; - editOfflineAction.payload.entry.isLocked = true; - actions$.next(editOfflineAction); - expect(component.editable).toBeTrue(); - }); - - it('should have set false if node is locked', () => { - editOfflineAction.payload.entry.isLocked = true; - actions$.next(editOfflineAction); - expect(component.editable).toBeFalse(); - }); - - it('should have set false if node has no update permission', () => { - editOfflineAction.payload.entry.allowableOperations = ['other']; - actions$.next(editOfflineAction); - expect(component.editable).toBeFalse(); - }); - - it('should have set false if node has read only property', () => { - editOfflineAction.payload.entry.properties = { - 'cm:lockType': 'WRITE_LOCK' - }; - actions$.next(editOfflineAction); - expect(component.editable).toBeFalse(); - }); - }); - - describe('ContentMetadataCardComponent', () => { - const getContentMetadataCard = (): ContentMetadataCardComponent => - fixture.debugElement.query(By.directive(ContentMetadataCardComponent)).componentInstance; - - beforeEach(() => { - fixture = TestBed.createComponent(MetadataTabComponent); - component = fixture.componentInstance; - }); - - describe('displayAspect', () => { - beforeEach(() => { - store = TestBed.inject(Store); - }); - - it('should show pass empty when store is in initial state', () => { - expect(getContentMetadataCard().displayAspect).toBeFalsy(); - }); - - it('should update the exif if store got updated', () => { - store.dispatch(new SetInfoDrawerMetadataAspectAction('EXIF')); - component.displayAspect$.subscribe((aspect) => { - expect(aspect).toBe('EXIF'); - }); - fixture.detectChanges(); - expect(getContentMetadataCard().displayAspect).toBe('EXIF'); - }); }); describe('Tags and categories', () => { @@ -287,7 +214,7 @@ describe('MetadataTabComponent', () => { fixture.detectChanges(); expect(categoryService.areCategoriesEnabled).toHaveBeenCalled(); - expect(getContentMetadataCard().displayCategories).toBeTrue(); + expect(getContentMetadata().displayCategories).toBeTrue(); }); it('should have assigned displayCategories to false if categoryService.areCategoriesEnabled returns false', () => { @@ -296,7 +223,7 @@ describe('MetadataTabComponent', () => { fixture.detectChanges(); expect(categoryService.areCategoriesEnabled).toHaveBeenCalled(); - expect(getContentMetadataCard().displayCategories).toBeFalse(); + expect(getContentMetadata().displayCategories).toBeFalse(); }); it('should have assigned displayTags to true if tagService.areTagsEnabled returns true', () => { @@ -305,7 +232,7 @@ describe('MetadataTabComponent', () => { fixture.detectChanges(); expect(tagService.areTagsEnabled).toHaveBeenCalled(); - expect(getContentMetadataCard().displayTags).toBeTrue(); + expect(getContentMetadata().displayTags).toBeTrue(); }); it('should have assigned displayTags to false if tagService.areTagsEnabled returns false', () => { @@ -314,7 +241,7 @@ describe('MetadataTabComponent', () => { fixture.detectChanges(); expect(tagService.areTagsEnabled).toHaveBeenCalled(); - expect(getContentMetadataCard().displayTags).toBeFalse(); + expect(getContentMetadata().displayTags).toBeFalse(); }); }); }); diff --git a/projects/aca-content/src/lib/components/info-drawer/metadata-tab/metadata-tab.component.ts b/projects/aca-content/src/lib/components/info-drawer/metadata-tab/metadata-tab.component.ts index 57ee8cbe7f..a8a3f79551 100644 --- a/projects/aca-content/src/lib/components/info-drawer/metadata-tab/metadata-tab.component.ts +++ b/projects/aca-content/src/lib/components/info-drawer/metadata-tab/metadata-tab.component.ts @@ -25,10 +25,9 @@ import { Component, Input, ViewEncapsulation, OnInit, OnDestroy } from '@angular/core'; import { Node } from '@alfresco/js-api'; import { NodePermissionService, isLocked, AppExtensionService } from '@alfresco/aca-shared'; -import { AppStore, EditOfflineAction, infoDrawerMetadataAspect, NodeActionTypes } from '@alfresco/aca-shared/store'; +import { EditOfflineAction, NodeActionTypes } from '@alfresco/aca-shared/store'; import { AppConfigService, NotificationService } from '@alfresco/adf-core'; import { Observable, Subject } from 'rxjs'; -import { Store } from '@ngrx/store'; import { ContentMetadataModule, ContentMetadataService, @@ -45,17 +44,15 @@ import { Actions, ofType } from '@ngrx/effects'; imports: [CommonModule, ContentMetadataModule], selector: 'app-metadata-tab', template: ` - - + `, encapsulation: ViewEncapsulation.None, host: { class: 'app-metadata-tab' } @@ -68,15 +65,12 @@ export class MetadataTabComponent implements OnInit, OnDestroy { @Input() node: Node; - displayAspect$: Observable; - canUpdateNode = false; - editable = false; + readOnly = false; customPanels: Observable; get displayCategories(): boolean { return this._displayCategories; } - get displayTags(): boolean { return this._displayTags; } @@ -85,7 +79,6 @@ export class MetadataTabComponent implements OnInit, OnDestroy { private permission: NodePermissionService, protected extensions: AppExtensionService, private appConfig: AppConfigService, - private store: Store, private notificationService: NotificationService, private contentMetadataService: ContentMetadataService, private actions$: Actions, @@ -95,12 +88,12 @@ export class MetadataTabComponent implements OnInit, OnDestroy { if (this.extensions.contentMetadata) { this.appConfig.config['content-metadata'].presets = this.extensions.contentMetadata.presets; } - this.displayAspect$ = this.store.select(infoDrawerMetadataAspect); } ngOnInit() { this._displayTags = this.tagService.areTagsEnabled(); this._displayCategories = this.categoryService.areCategoriesEnabled(); + this.contentMetadataService.error.pipe(takeUntil(this.onDestroy$)).subscribe((err: { message: string }) => { this.notificationService.showError(err.message); }); @@ -113,9 +106,6 @@ export class MetadataTabComponent implements OnInit, OnDestroy { ) .subscribe((updatedNode) => { this.checkIfNodeIsUpdatable(updatedNode?.payload.entry); - if (!this.canUpdateNode) { - this.editable = false; - } }); this.customPanels = this.extensions.getCustomMetadataPanels({ entry: this.node }).pipe( map((panels) => { @@ -133,6 +123,6 @@ export class MetadataTabComponent implements OnInit, OnDestroy { } private checkIfNodeIsUpdatable(node: Node) { - this.canUpdateNode = node && !isLocked({ entry: node }) ? this.permission.check(node, ['update']) : false; + this.readOnly = !(node && !isLocked({ entry: node }) ? this.permission.check(node, ['update']) : false); } } diff --git a/projects/aca-content/src/lib/components/toolbar/toggle-edit-offline/toggle-edit-offline.component.spec.ts b/projects/aca-content/src/lib/components/toolbar/toggle-edit-offline/toggle-edit-offline.component.spec.ts index ab1d849afd..be324078a8 100644 --- a/projects/aca-content/src/lib/components/toolbar/toggle-edit-offline/toggle-edit-offline.component.spec.ts +++ b/projects/aca-content/src/lib/components/toolbar/toggle-edit-offline/toggle-edit-offline.component.spec.ts @@ -29,6 +29,7 @@ import { Store } from '@ngrx/store'; import { NodeEntry } from '@alfresco/js-api'; import { DownloadNodesAction, EditOfflineAction, SnackbarErrorAction } from '@alfresco/aca-shared/store'; import { AppTestingModule } from '../../../testing/app-testing.module'; +import { AppExtensionService } from '@alfresco/aca-shared'; describe('ToggleEditOfflineComponent', () => { let fixture: ComponentFixture; @@ -38,6 +39,10 @@ describe('ToggleEditOfflineComponent', () => { let selectSpy: jasmine.Spy; let selection: any; + const extensionsMock = { + updateSidebarActions: jasmine.createSpy('updateSidebarActions') + }; + beforeEach(() => { TestBed.configureTestingModule({ imports: [AppTestingModule, ToggleEditOfflineComponent], @@ -48,6 +53,10 @@ describe('ToggleEditOfflineComponent', () => { select: () => {}, dispatch: () => {} } + }, + { + provide: AppExtensionService, + useValue: extensionsMock } ] }); @@ -133,4 +142,14 @@ describe('ToggleEditOfflineComponent', () => { }) ]); }); + + it('should call updateSidebarActions on click', async () => { + selectSpy.and.returnValue(of(selection)); + fixture.detectChanges(); + + await component.onClick(); + fixture.detectChanges(); + + expect(extensionsMock.updateSidebarActions).toHaveBeenCalled(); + }); }); diff --git a/projects/aca-content/src/lib/components/toolbar/toggle-edit-offline/toggle-edit-offline.component.ts b/projects/aca-content/src/lib/components/toolbar/toggle-edit-offline/toggle-edit-offline.component.ts index 2862777322..40410b94b5 100644 --- a/projects/aca-content/src/lib/components/toolbar/toggle-edit-offline/toggle-edit-offline.component.ts +++ b/projects/aca-content/src/lib/components/toolbar/toggle-edit-offline/toggle-edit-offline.component.ts @@ -33,7 +33,7 @@ import { import { NodeEntry, SharedLinkEntry, Node, NodesApi } from '@alfresco/js-api'; import { Component, OnInit, ViewEncapsulation } from '@angular/core'; import { Store } from '@ngrx/store'; -import { isLocked } from '@alfresco/aca-shared'; +import { AppExtensionService, isLocked } from '@alfresco/aca-shared'; import { AlfrescoApiService } from '@alfresco/adf-core'; import { CommonModule } from '@angular/common'; import { TranslateModule } from '@ngx-translate/core'; @@ -59,7 +59,7 @@ export class ToggleEditOfflineComponent implements OnInit { nodeTitle = ''; isNodeLocked = false; - constructor(private store: Store, private alfrescoApiService: AlfrescoApiService) { + constructor(private store: Store, private alfrescoApiService: AlfrescoApiService, private extensions: AppExtensionService) { this.nodesApi = new NodesApi(this.alfrescoApiService.getInstance()); } @@ -73,6 +73,7 @@ export class ToggleEditOfflineComponent implements OnInit { async onClick() { await this.toggleLock(this.selection); + this.extensions.updateSidebarActions(); } private async toggleLock(node: NodeEntry | SharedLinkEntry) { diff --git a/projects/aca-content/src/lib/services/node-actions.service.spec.ts b/projects/aca-content/src/lib/services/node-actions.service.spec.ts index 255801bc8e..1fdb915fbd 100644 --- a/projects/aca-content/src/lib/services/node-actions.service.spec.ts +++ b/projects/aca-content/src/lib/services/node-actions.service.spec.ts @@ -107,6 +107,7 @@ describe('NodeActionsService', () => { service = TestBed.inject(NodeActionsService); apiService = TestBed.inject(AlfrescoApiService); dialog = TestBed.inject(MatDialog); + apiService.reset(); nodesApi = service['nodesApi']; diff --git a/projects/aca-content/src/lib/services/node-actions.service.ts b/projects/aca-content/src/lib/services/node-actions.service.ts index ee3443e718..e43dbbb2cf 100644 --- a/projects/aca-content/src/lib/services/node-actions.service.ts +++ b/projects/aca-content/src/lib/services/node-actions.service.ts @@ -25,7 +25,7 @@ import { Injectable } from '@angular/core'; import { MatDialog } from '@angular/material/dialog'; import { Observable, Subject, of, zip, from } from 'rxjs'; -import { AlfrescoApiService, TranslationService, ThumbnailService } from '@alfresco/adf-core'; +import { AlfrescoApiService, ThumbnailService, TranslationService } from '@alfresco/adf-core'; import { DocumentListService, ContentNodeSelectorComponent, diff --git a/projects/aca-content/src/lib/store/effects/node.effects.spec.ts b/projects/aca-content/src/lib/store/effects/node.effects.spec.ts index e019b1ecfe..8f47bf3b8f 100644 --- a/projects/aca-content/src/lib/store/effects/node.effects.spec.ts +++ b/projects/aca-content/src/lib/store/effects/node.effects.spec.ts @@ -389,7 +389,7 @@ describe('NodeEffects', () => { }); it('should manage permissions from the active selection', () => { - spyOn(store, 'select').and.returnValue(of({ isEmpty: false, first: { entry: { id: 'fileId' } } })); + spyOn(store, 'select').and.returnValue(of({ isEmpty: false, last: { entry: { id: 'fileId' } } })); spyOn(router, 'navigate').and.stub(); store.dispatch(new ManagePermissionsAction(null)); diff --git a/projects/aca-content/src/lib/store/effects/node.effects.ts b/projects/aca-content/src/lib/store/effects/node.effects.ts index 06594660d1..1529471378 100644 --- a/projects/aca-content/src/lib/store/effects/node.effects.ts +++ b/projects/aca-content/src/lib/store/effects/node.effects.ts @@ -293,7 +293,7 @@ export class NodeEffects { .subscribe((selection) => { if (selection && !selection.isEmpty) { const route = 'personal-files/details'; - this.store.dispatch(new NavigateRouteAction([route, selection.first.entry.id, 'permissions'])); + this.store.dispatch(new NavigateRouteAction([route, selection.last.entry.id, 'permissions'])); } }); } @@ -321,7 +321,7 @@ export class NodeEffects { .subscribe((selection) => { if (selection && !selection.isEmpty) { const route = 'personal-files/details'; - this.router.navigate([route, selection.first.entry.id], { + this.router.navigate([route, selection.last.entry.id], { queryParams: { location: this.router.url } diff --git a/projects/aca-content/src/lib/ui/application.scss b/projects/aca-content/src/lib/ui/application.scss index 29e879de64..0762b52a4b 100644 --- a/projects/aca-content/src/lib/ui/application.scss +++ b/projects/aca-content/src/lib/ui/application.scss @@ -49,10 +49,11 @@ ng-component { .aca-sidebar { display: block; - height: 100%; - overflow-y: scroll; - max-width: 350px; - width: 350px; + max-width: 368px; + width: 368px; + margin-bottom: 65px; + border-top: 1px solid var(--theme-metadata-property-panel-border-color); + border-bottom: 1px solid var(--theme-metadata-property-panel-border-color); } .aca-page-title { diff --git a/projects/aca-content/src/lib/ui/overrides/ay11.scss b/projects/aca-content/src/lib/ui/overrides/ay11.scss index 3a82e99a3b..7f76cd6fa6 100644 --- a/projects/aca-content/src/lib/ui/overrides/ay11.scss +++ b/projects/aca-content/src/lib/ui/overrides/ay11.scss @@ -156,8 +156,8 @@ } .mat-expansion-panel .mat-expansion-panel-header { - border: 2px solid transparent; box-sizing: border-box; + border: 2px solid transparent; .mat-button-base.mat-button { outline: none; diff --git a/projects/aca-shared/rules/src/app.rules.spec.ts b/projects/aca-shared/rules/src/app.rules.spec.ts index 7aac081b48..8d48e45ba8 100644 --- a/projects/aca-shared/rules/src/app.rules.spec.ts +++ b/projects/aca-shared/rules/src/app.rules.spec.ts @@ -121,6 +121,28 @@ describe('app.evaluators', () => { }); }); + describe('canShowExpand', () => { + it('should return false when isLibraries returns true', () => { + const context: any = { + navigation: { + url: '/libraries' + } + }; + + expect(app.canShowExpand(context)).toBe(false); + }); + + it('should return false when isDetails returns true', () => { + const context: any = { + navigation: { + url: '/details' + } + }; + + expect(app.canShowExpand(context)).toBe(false); + }); + }); + describe('hasLockedFiles', () => { it('should return [false] if selection not present', () => { const context: any = {}; @@ -830,6 +852,42 @@ describe('app.evaluators', () => { }); }); + describe('editAspects', () => { + let context: TestRuleContext; + + beforeEach(() => { + context = createTestContext(); + }); + + it('should return true for multiselection', () => { + context.selection.count = 2; + + expect(app.editAspects(context)).toBe(true); + }); + + it('should return false if user cannot update the selected node', () => { + context.permissions.check = spyOn(context.permissions, 'check').and.returnValue(false); + + expect(app.editAspects(context)).toBe(false); + }); + + it('should return false if the selected node is write locked', () => { + context.selection.file = { entry: { properties: { 'cm:lockType': 'WRITE_LOCK' } } } as NodeEntry; + + expect(app.editAspects(context)).toBe(false); + }); + + it('should return false if the context is trashcan', () => { + context.navigation = { url: '/trashcan' }; + + expect(app.editAspects(context)).toBe(false); + }); + + it('should return true if all conditions are met', () => { + expect(app.editAspects(context)).toBe(true); + }); + }); + describe('canManagePermissions', () => { let context: TestRuleContext; diff --git a/projects/aca-shared/rules/src/app.rules.ts b/projects/aca-shared/rules/src/app.rules.ts index 35789dd627..992773a346 100644 --- a/projects/aca-shared/rules/src/app.rules.ts +++ b/projects/aca-shared/rules/src/app.rules.ts @@ -498,6 +498,16 @@ export const canEditAspects = (context: RuleContext): boolean => repository.isMajorVersionAvailable(context, '7') ].every(Boolean); +export const editAspects = (context: RuleContext): boolean => + [ + canUpdateSelectedNode(context), + !isWriteLocked(context), + navigation.isNotTrashcan(context), + repository.isMajorVersionAvailable(context, '7') + ].every(Boolean); + +export const canShowExpand = (context: RuleContext): boolean => [!navigation.isLibraries(context), !navigation.isDetails(context)].every(Boolean); + /** * Checks if user can manage permissions for the selected node. * JSON ref: `canManagePermissions` diff --git a/projects/aca-shared/rules/src/navigation.rules.spec.ts b/projects/aca-shared/rules/src/navigation.rules.spec.ts index b0cae7b299..9582223e44 100644 --- a/projects/aca-shared/rules/src/navigation.rules.spec.ts +++ b/projects/aca-shared/rules/src/navigation.rules.spec.ts @@ -225,6 +225,28 @@ describe('navigation.evaluators', () => { }); }); + describe('isDetails', () => { + it('should return true if url includes with `/details`', () => { + const context: any = { + navigation: { + url: '/details/path' + } + }; + + expect(app.isDetails(context)).toBe(true); + }); + + it('should return false if url not includes with `/details`', () => { + const context: any = { + navigation: { + url: '/path' + } + }; + + expect(app.isDetails(context)).toBe(false); + }); + }); + describe('isRecentFiles', () => { it('should return [true] if url starts with `/recent-files`', () => { const context: any = { diff --git a/projects/aca-shared/rules/src/navigation.rules.ts b/projects/aca-shared/rules/src/navigation.rules.ts index 8a3abc762c..335fced649 100644 --- a/projects/aca-shared/rules/src/navigation.rules.ts +++ b/projects/aca-shared/rules/src/navigation.rules.ts @@ -110,6 +110,11 @@ export function isLibraryContent(context: RuleContext): boolean { return url?.endsWith('/libraries') || url?.includes('/libraries/') || url?.startsWith('/search-libraries'); } +export function isDetails(context: RuleContext): boolean { + const { url } = context.navigation; + return url?.includes('/details'); +} + /** * Checks if the activated route is neither **Libraries** nor **Library Search Results**. * JSON ref: `app.navigation.isNotLibraries` diff --git a/projects/aca-shared/src/lib/components/info-drawer/info-drawer.component.html b/projects/aca-shared/src/lib/components/info-drawer/info-drawer.component.html index 20ce0406a6..838cb9ba37 100644 --- a/projects/aca-shared/src/lib/components/info-drawer/info-drawer.component.html +++ b/projects/aca-shared/src/lib/components/info-drawer/info-drawer.component.html @@ -2,7 +2,7 @@
- + diff --git a/projects/aca-shared/src/lib/components/info-drawer/info-drawer.component.spec.ts b/projects/aca-shared/src/lib/components/info-drawer/info-drawer.component.spec.ts index 7d16fadfa8..b1263db892 100644 --- a/projects/aca-shared/src/lib/components/info-drawer/info-drawer.component.spec.ts +++ b/projects/aca-shared/src/lib/components/info-drawer/info-drawer.component.spec.ts @@ -27,16 +27,19 @@ import { NO_ERRORS_SCHEMA } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { Store } from '@ngrx/store'; import { SetInfoDrawerStateAction, ToggleInfoDrawerAction } from '@alfresco/aca-shared/store'; -import { of, Subject } from 'rxjs'; +import { EMPTY, of, Subject } from 'rxjs'; import { InfoDrawerComponent } from './info-drawer.component'; import { LibTestingModule } from '../../testing/lib-testing-module'; import { AppExtensionService } from '../../services/app.extension.service'; import { ContentApiService } from '../../services/content-api.service'; +import { ContentService } from '@alfresco/adf-content-services'; +import { RedirectAuthService } from '@alfresco/adf-core'; describe('InfoDrawerComponent', () => { let fixture: ComponentFixture; let component: InfoDrawerComponent; let contentApiService: ContentApiService; + let contentService: ContentService; let tab: SidebarTabRef; let appExtensionService: AppExtensionService; const mockStream = new Subject(); @@ -62,7 +65,8 @@ describe('InfoDrawerComponent', () => { imports: [LibTestingModule, InfoDrawerComponent], providers: [ { provide: AppExtensionService, useValue: extensionServiceMock }, - { provide: Store, useValue: storeMock } + { provide: Store, useValue: storeMock }, + { provide: RedirectAuthService, useValue: { onLogin: EMPTY } } ], schemas: [NO_ERRORS_SCHEMA] }); @@ -71,7 +75,7 @@ describe('InfoDrawerComponent', () => { component = fixture.componentInstance; appExtensionService = TestBed.inject(AppExtensionService); contentApiService = TestBed.inject(ContentApiService); - + contentService = TestBed.inject(ContentService); tab = { title: 'tab1', id: 'tab1', component: '' }; spyOn(appExtensionService, 'getSidebarTabs').and.returnValue([tab]); }); @@ -140,6 +144,7 @@ describe('InfoDrawerComponent', () => { it('should call getNodeInfo() when node is a recent file', () => { const response: any = { entry: { id: 'nodeId' } }; spyOn(contentApiService, 'getNodeInfo').and.returnValue(of(response)); + const nodeMock: any = { entry: { id: 'nodeId', @@ -153,6 +158,7 @@ describe('InfoDrawerComponent', () => { component.ngOnChanges(); expect(component.displayNode).toBe(response); + expect(component.node.entry).toBe(response); expect(contentApiService.getNodeInfo).toHaveBeenCalled(); }); @@ -182,4 +188,23 @@ describe('InfoDrawerComponent', () => { } as ContentActionRef ]); }); + + it('should get node icon for documents', () => { + const expectedIcon = 'assets/images/ft_ic_folder'; + const response: any = { entry: { id: 'nodeId' } }; + spyOn(contentApiService, 'getNodeInfo').and.returnValue(of(response)); + spyOn(contentService, 'getNodeIcon').and.returnValue(expectedIcon); + const nodeMock: any = { + entry: { id: 'nodeId', guid: 'guidId' }, + isFolder: true + }; + component.node = nodeMock; + + fixture.detectChanges(); + component.ngOnChanges(); + + expect(contentService.getNodeIcon).toHaveBeenCalledWith(response); + expect(component.icon).toBe(expectedIcon); + expect(contentApiService.getNodeInfo).toHaveBeenCalled(); + }); }); diff --git a/projects/aca-shared/src/lib/components/info-drawer/info-drawer.component.ts b/projects/aca-shared/src/lib/components/info-drawer/info-drawer.component.ts index 70c59e0cb4..aa05e018e3 100644 --- a/projects/aca-shared/src/lib/components/info-drawer/info-drawer.component.ts +++ b/projects/aca-shared/src/lib/components/info-drawer/info-drawer.component.ts @@ -37,6 +37,7 @@ import { InfoDrawerModule } from '@alfresco/adf-core'; import { TranslateModule } from '@ngx-translate/core'; import { A11yModule } from '@angular/cdk/a11y'; import { ToolbarComponent } from '../toolbar/toolbar.component'; +import { ContentService, NodesApiService } from '@alfresco/adf-content-services'; @Component({ standalone: true, @@ -58,13 +59,20 @@ export class InfoDrawerComponent implements OnChanges, OnInit, OnDestroy { actions: Array = []; onDestroy$ = new Subject(); preventFromClosing = false; + icon: string = null; @HostListener('keydown.escape') onEscapeKeyboardEvent(): void { this.close(); } - constructor(private store: Store, private contentApi: ContentApiService, private extensions: AppExtensionService) {} + constructor( + private store: Store, + private contentApi: ContentApiService, + private extensions: AppExtensionService, + private nodesService: NodesApiService, + private contentService: ContentService + ) {} ngOnInit() { this.tabs = this.extensions.getSidebarTabs(); @@ -81,6 +89,10 @@ export class InfoDrawerComponent implements OnChanges, OnInit, OnDestroy { .subscribe((isInfoDrawerPreviewOpened) => { this.preventFromClosing = isInfoDrawerPreviewOpened; }); + + this.nodesService.nodeUpdated.pipe(takeUntil(this.onDestroy$)).subscribe((node: any) => { + this.node.entry = node; + }); } ngOnDestroy() { @@ -115,6 +127,7 @@ export class InfoDrawerComponent implements OnChanges, OnInit, OnDestroy { this.contentApi.getNodeInfo(nodeId).subscribe( (entity) => { this.setDisplayNode(entity); + this.node.entry = entity; this.isLoading = false; }, () => (this.isLoading = false) @@ -124,5 +137,6 @@ export class InfoDrawerComponent implements OnChanges, OnInit, OnDestroy { private setDisplayNode(node: any) { this.displayNode = node; + this.icon = this.contentService.getNodeIcon(node); } } diff --git a/projects/aca-shared/src/lib/components/page-layout/page-layout.component.scss b/projects/aca-shared/src/lib/components/page-layout/page-layout.component.scss index d5f0fd36b9..0fed6b2875 100644 --- a/projects/aca-shared/src/lib/components/page-layout/page-layout.component.scss +++ b/projects/aca-shared/src/lib/components/page-layout/page-layout.component.scss @@ -37,6 +37,8 @@ .aca-page-layout-content { @include flex-row; + + border-top: 1px solid var(--theme-metadata-property-panel-border-color); } .aca-page-layout-error { diff --git a/projects/aca-shared/src/lib/services/app.extension.service.spec.ts b/projects/aca-shared/src/lib/services/app.extension.service.spec.ts index 632eb238cb..72da737f68 100644 --- a/projects/aca-shared/src/lib/services/app.extension.service.spec.ts +++ b/projects/aca-shared/src/lib/services/app.extension.service.spec.ts @@ -30,6 +30,7 @@ import { AppStore } from '@alfresco/aca-shared/store'; import { ContentActionType, ExtensionConfig, + ExtensionLoaderService, ExtensionService, filterEnabled, mergeArrays, @@ -53,6 +54,7 @@ describe('AppExtensionService', () => { let logService: LogService; let iconRegistry: MatIconRegistry; let sanitizer: DomSanitizer; + let loader: ExtensionLoaderService; beforeEach(() => { TestBed.configureTestingModule({ @@ -70,6 +72,7 @@ describe('AppExtensionService', () => { extensions = TestBed.inject(ExtensionService); logService = TestBed.inject(LogService); + loader = TestBed.inject(ExtensionLoaderService); }); const applyConfig = (config: ExtensionConfig, selection?: boolean) => { @@ -1835,4 +1838,10 @@ describe('AppExtensionService', () => { done(); }); }); + + it('should update sidebar actions correctly', () => { + spyOn(loader, 'getContentActions').and.callThrough(); + service.updateSidebarActions(); + expect(loader.getContentActions).toHaveBeenCalledWith(service.config, 'features.sidebar.toolbar'); + }); }); diff --git a/projects/aca-shared/src/lib/services/app.extension.service.ts b/projects/aca-shared/src/lib/services/app.extension.service.ts index 15ada1b5b9..11cc78662d 100644 --- a/projects/aca-shared/src/lib/services/app.extension.service.ts +++ b/projects/aca-shared/src/lib/services/app.extension.service.ts @@ -354,6 +354,10 @@ export class AppExtensionService implements RuleContext { }; } + updateSidebarActions() { + this._sidebarActions.next(this.loader.getContentActions(this.config, 'features.sidebar.toolbar')); + } + getCreateActions(): Observable> { return this._createActions.pipe( map((createActions) => diff --git a/projects/aca-testing-shared/src/components/info-drawer/info-drawer.ts b/projects/aca-testing-shared/src/components/info-drawer/info-drawer.ts index c6b3e47cd6..b3300cd4db 100755 --- a/projects/aca-testing-shared/src/components/info-drawer/info-drawer.ts +++ b/projects/aca-testing-shared/src/components/info-drawer/info-drawer.ts @@ -36,11 +36,11 @@ export class InfoDrawer extends Component { aboutTab = new LibraryMetadata('adf-info-drawer'); propertiesTab = new ContentMetadata('adf-info-drawer'); header = this.byCss('.adf-info-drawer-layout-header'); - headerTitle = this.byCss('.adf-info-drawer-layout-header-title'); + headerTitle = this.byCss('.adf-info-drawer-layout-header-title > div'); tabLabelsList = this.allByCss('.mat-tab-label-content'); tabActiveLabel = this.byCss('.mat-tab-label-active'); tabActiveContent = this.byCss('.mat-tab-body-active .mat-tab-body-content adf-dynamic-tab'); - expandDetailsButton = TestElement.byCss(`button[title='Expand']`); + expandDetailsButton = TestElement.byCss(`button[title='Expand panel']`); selectedTab = TestElement.byCss(`.mat-tab-list [aria-selected='true'] div`); expandedDetailsPermissionsTab = TestElement.byText('.aca-details-container .mat-tab-label-content', 'Permissions'); previewButton = TestElement.byCss(`button[title='Preview File']`); diff --git a/projects/aca-testing-shared/src/components/metadata-card/metadata-card.ts b/projects/aca-testing-shared/src/components/metadata-card/metadata-card.ts index 288d936833..b69145ba84 100644 --- a/projects/aca-testing-shared/src/components/metadata-card/metadata-card.ts +++ b/projects/aca-testing-shared/src/components/metadata-card/metadata-card.ts @@ -30,7 +30,7 @@ export class MetadataCard extends Component { expansionPanels = this.allByCss('.adf-metadata-grouped-properties-container mat-expansion-panel'); constructor(ancestor?: string) { - super('adf-content-metadata-card', ancestor); + super('adf-content-metadata', ancestor); } async waitForFirstExpansionPanel() { diff --git a/tsconfig.adf.json b/tsconfig.adf.json index 015743b3b9..190fbd023c 100644 --- a/tsconfig.adf.json +++ b/tsconfig.adf.json @@ -24,7 +24,7 @@ "@alfresco/adf-testing": ["../alfresco-ng2-components/lib/testing"], "@alfresco/adf-testing/shared": ["../alfresco-ng2-components/lib/testing/src/lib/shared"], "@alfresco/playwright-shared": ["projects/aca-playwright-shared/src/index.ts"], - "@alfresco/adf-core": ["../alfresco-ng2-components/lib/core"], + "@alfresco/adf-core": ["../alfresco-ng2-components/lib/core/index.ts"], "@alfresco/adf-core/*": ["../alfresco-ng2-components/lib/core/*/public-api.ts"], "@alfresco/adf-core/shell": ["../alfresco-ng2-components/lib/core/shell/src/index.ts"], "@alfresco/adf-core/auth": ["../alfresco-ng2-components/lib/core/auth/src/index.ts"],