From 1a53f8d2aa366a9693d46a79ce436ae7d7504eae Mon Sep 17 00:00:00 2001 From: Denys Vuika Date: Tue, 19 Jun 2018 08:16:53 +0100 Subject: [PATCH] unified selection state (#433) * selection state * use unified selection state * cleanup tests * remove "console.log" * remove old selection property * remove coma --- .../favorites/favorites.component.html | 32 ++++++------- src/app/components/files/files.component.html | 36 +++++++------- src/app/components/page.component.spec.ts | 48 ------------------- src/app/components/page.component.ts | 43 +++++------------ .../components/preview/preview.component.html | 6 +-- .../recent-files/recent-files.component.html | 32 ++++++------- .../components/search/search.component.html | 24 +++++----- .../shared-files/shared-files.component.html | 36 +++++++------- .../trashcan/trashcan.component.html | 6 +-- src/app/store/reducers/app.reducer.ts | 29 ++++++++++- src/app/store/selectors/app.selectors.ts | 2 +- src/app/store/states/app.state.ts | 10 ++-- src/app/store/states/selection.state.ts | 36 ++++++++++++++ 13 files changed, 171 insertions(+), 169 deletions(-) create mode 100644 src/app/store/states/selection.state.ts diff --git a/src/app/components/favorites/favorites.component.html b/src/app/components/favorites/favorites.component.html index 891235405..a6c71fa4e 100644 --- a/src/app/components/favorites/favorites.component.html +++ b/src/app/components/favorites/favorites.component.html @@ -3,14 +3,14 @@ - + @@ -18,16 +18,16 @@ mat-icon-button color="primary" title="{{ 'APP.ACTIONS.DOWNLOAD' | translate }}" - [adfNodeDownload]="selectedNodes"> + [adfNodeDownload]="selection.nodes"> get_app @@ -49,39 +49,39 @@ [overlapTrigger]="false"> @@ -169,7 +169,7 @@
- +
diff --git a/src/app/components/files/files.component.html b/src/app/components/files/files.component.html index e24f683c7..446090a0d 100644 --- a/src/app/components/files/files.component.html +++ b/src/app/components/files/files.component.html @@ -6,13 +6,13 @@ (navigate)="onBreadcrumbNavigate($event)"> - + @@ -20,16 +20,16 @@ color="primary" mat-icon-button title="{{ 'APP.ACTIONS.DOWNLOAD' | translate }}" - [adfNodeDownload]="selectedNodes"> + [adfNodeDownload]="selection.nodes"> get_app @@ -52,40 +52,40 @@ [overlapTrigger]="false"> @@ -167,7 +167,7 @@
- +
diff --git a/src/app/components/page.component.spec.ts b/src/app/components/page.component.spec.ts index 680eee69b..ee65afb82 100644 --- a/src/app/components/page.component.spec.ts +++ b/src/app/components/page.component.spec.ts @@ -24,15 +24,10 @@ */ import { PageComponent } from './page.component'; -import { MinimalNodeEntity } from 'alfresco-js-api'; class TestClass extends PageComponent { node: any; - setSelection(selection: MinimalNodeEntity[] = []) { - this.onSelectionChanged(selection); - } - constructor() { super(null, null, null); } @@ -58,47 +53,4 @@ describe('PageComponent', () => { expect(component.getParentNodeId()).toBe(null); }); }); - - describe('hasSelection()', () => { - it('returns true when it has nodes selected', () => { - component.setSelection([ - { entry: { isFile: true } }, - { entry: { isFile: true } } - ]); - expect(component.hasSelection).toBe(true); - }); - - it('returns false when it has no selections', () => { - component.setSelection([]); - expect(component.hasSelection).toBe(false); - }); - }); - - describe('selectedFile', () => { - it('returns true if selected node is file', () => { - const selection = [ { entry: { isFile: true } } ]; - component.setSelection(selection); - expect(component.selectedFile).toBe(selection[0]); - }); - - it('returns false if selected node is folder', () => { - const selection = [ { entry: { isFile: false, isFolder: true } } ]; - component.setSelection(selection); - expect(component.selectedFile).toBeFalsy(); - }); - }); - - describe('selectedFolder', () => { - it('returns true if selected node is folder', () => { - const selection = [ { entry: { isFile: false, isFolder: true } } ]; - component.setSelection(selection); - expect(component.selectedFolder).toBe(selection[0]); - }); - - it('returns false if selected node is file', () => { - const selection = [ { entry: { isFile: true, isFolder: false } } ]; - component.setSelection(selection); - expect(component.selectedFolder).toBeFalsy(); - }); - }); }); diff --git a/src/app/components/page.component.ts b/src/app/components/page.component.ts index a81857959..3bb25af10 100644 --- a/src/app/components/page.component.ts +++ b/src/app/components/page.component.ts @@ -32,12 +32,13 @@ import { MinimalNodeEntity, MinimalNodeEntryEntity, Pagination } from 'alfresco- import { takeUntil } from 'rxjs/operators'; import { Subject, Subscription } from 'rxjs/Rx'; import { SnackbarErrorAction, ViewNodeAction, SetSelectedNodesAction } from '../store/actions'; -import { selectedNodes } from '../store/selectors/app.selectors'; +import { appSelection } from '../store/selectors/app.selectors'; import { AppStore } from '../store/states/app.state'; +import { SelectionState } from '../store/states/selection.state'; export abstract class PageComponent implements OnInit, OnDestroy { - onDestroy$: Subject = new Subject(); + onDestroy$: Subject = new Subject(); @ViewChild(DocumentListComponent) documentList: DocumentListComponent; @@ -45,13 +46,7 @@ export abstract class PageComponent implements OnInit, OnDestroy { title = 'Page'; infoDrawerOpened = false; node: MinimalNodeEntryEntity; - - selectedFolder: MinimalNodeEntity; - selectedFile: MinimalNodeEntity; - - hasSelection = false; - lastSelectedNode: MinimalNodeEntity; - selectedNodes: MinimalNodeEntity[]; + selection: SelectionState; protected subscriptions: Subscription[] = []; @@ -70,35 +65,24 @@ export abstract class PageComponent implements OnInit, OnDestroy { ngOnInit() { this.store - .select(selectedNodes) + .select(appSelection) .pipe(takeUntil(this.onDestroy$)) - .subscribe(selection => this.onSelectionChanged(selection)); + .subscribe(selection => { + this.selection = selection; + if (selection.isEmpty) { + this.infoDrawerOpened = false; + } + }); } ngOnDestroy() { this.subscriptions.forEach(subscription => subscription.unsubscribe()); this.subscriptions = []; + + this.onDestroy$.next(true); this.onDestroy$.complete(); } - // Precalculates all the "static state" flags so that UI does not re-evaluate that on every tick - protected onSelectionChanged(selection: MinimalNodeEntity[] = []) { - this.selectedNodes = selection; - this.hasSelection = selection.length > 0; - this.selectedFolder = null; - this.selectedFile = null; - - if (selection.length > 0) { - if (selection.length === 1) { - this.selectedFile = selection.find(entity => entity.entry.isFile); - this.selectedFolder = selection.find(entity => entity.entry.isFolder); - } - } else { - this.lastSelectedNode = null; - this.infoDrawerOpened = false; - } - } - showPreview(node: MinimalNodeEntity) { if (node && node.entry) { const { id, nodeId, name, isFile, isFolder } = node.entry; @@ -130,7 +114,6 @@ export abstract class PageComponent implements OnInit, OnDestroy { this.unSelectLockedNodes(documentList); } - this.lastSelectedNode = event.detail.node; this.store.dispatch(new SetSelectedNodesAction(documentList.selection)); } } diff --git a/src/app/components/preview/preview.component.html b/src/app/components/preview/preview.component.html index 666879f6e..8a19b3151 100644 --- a/src/app/components/preview/preview.component.html +++ b/src/app/components/preview/preview.component.html @@ -35,10 +35,10 @@ diff --git a/src/app/components/recent-files/recent-files.component.html b/src/app/components/recent-files/recent-files.component.html index 70eb1765e..18ce84406 100644 --- a/src/app/components/recent-files/recent-files.component.html +++ b/src/app/components/recent-files/recent-files.component.html @@ -3,14 +3,14 @@ - + @@ -18,7 +18,7 @@ mat-icon-button color="primary" title="{{ 'APP.ACTIONS.DOWNLOAD' | translate }}" - [adfNodeDownload]="selectedNodes"> + [adfNodeDownload]="selection.nodes"> get_app @@ -41,40 +41,40 @@ [overlapTrigger]="false"> @@ -157,7 +157,7 @@
- +
diff --git a/src/app/components/search/search.component.html b/src/app/components/search/search.component.html index 614de260e..d1af64136 100644 --- a/src/app/components/search/search.component.html +++ b/src/app/components/search/search.component.html @@ -2,13 +2,13 @@
- + @@ -16,7 +16,7 @@ color="primary" mat-icon-button title="{{ 'APP.ACTIONS.DOWNLOAD' | translate }}" - [acaDownloadNodes]="selectedNodes"> + [acaDownloadNodes]="selection.nodes"> get_app @@ -38,24 +38,24 @@ @@ -128,7 +128,7 @@
- +
diff --git a/src/app/components/shared-files/shared-files.component.html b/src/app/components/shared-files/shared-files.component.html index 7cce92bfd..451525317 100644 --- a/src/app/components/shared-files/shared-files.component.html +++ b/src/app/components/shared-files/shared-files.component.html @@ -3,13 +3,13 @@ - + @@ -17,7 +17,7 @@ color="primary" mat-icon-button title="{{ 'APP.ACTIONS.DOWNLOAD' | translate }}" - [adfNodeDownload]="selectedNodes"> + [adfNodeDownload]="selection.nodes"> get_app @@ -40,32 +40,32 @@ [overlapTrigger]="false"> @@ -175,7 +175,7 @@
- +
diff --git a/src/app/components/trashcan/trashcan.component.html b/src/app/components/trashcan/trashcan.component.html index 3e0c7d956..c6b1de7e3 100644 --- a/src/app/components/trashcan/trashcan.component.html +++ b/src/app/components/trashcan/trashcan.component.html @@ -3,11 +3,11 @@ - + @@ -15,7 +15,7 @@ diff --git a/src/app/store/reducers/app.reducer.ts b/src/app/store/reducers/app.reducer.ts index daff5aeb9..19b1d3f3e 100644 --- a/src/app/store/reducers/app.reducer.ts +++ b/src/app/store/reducers/app.reducer.ts @@ -90,6 +90,33 @@ function updateSelectedNodes( action: SetSelectedNodesAction ): AppState { const newState = Object.assign({}, state); - newState.selectedNodes = [...action.payload]; + const nodes = [...action.payload]; + const count = nodes.length; + const isEmpty = nodes.length === 0; + + let first = null; + let last = null; + let file = null; + let folder = null; + + if (nodes.length > 0) { + first = nodes[0]; + last = nodes[nodes.length - 1]; + + if (nodes.length === 1) { + file = nodes.find(entity => entity.entry.isFile); + folder = nodes.find(entity => entity.entry.isFolder); + } + } + + newState.selection = { + count, + nodes, + isEmpty, + first, + last, + file, + folder + }; return newState; } diff --git a/src/app/store/selectors/app.selectors.ts b/src/app/store/selectors/app.selectors.ts index 068e5ee26..5d7a7cc64 100644 --- a/src/app/store/selectors/app.selectors.ts +++ b/src/app/store/selectors/app.selectors.ts @@ -30,4 +30,4 @@ export const selectApp = (state: AppStore) => state.app; export const selectHeaderColor = createSelector(selectApp, (state: AppState) => state.headerColor); export const selectAppName = createSelector(selectApp, (state: AppState) => state.appName); export const selectLogoPath = createSelector(selectApp, (state: AppState) => state.logoPath); -export const selectedNodes = createSelector(selectApp, (state: AppState) => state.selectedNodes); +export const appSelection = createSelector(selectApp, (state: AppState) => state.selection); diff --git a/src/app/store/states/app.state.ts b/src/app/store/states/app.state.ts index 3de91e926..c10145d21 100644 --- a/src/app/store/states/app.state.ts +++ b/src/app/store/states/app.state.ts @@ -23,20 +23,24 @@ * along with Alfresco. If not, see . */ -import { MinimalNodeEntity } from 'alfresco-js-api'; +import { SelectionState } from './selection.state'; export interface AppState { appName: string; headerColor: string; logoPath: string; - selectedNodes: MinimalNodeEntity[]; + selection: SelectionState; } export const INITIAL_APP_STATE: AppState = { appName: 'Alfresco Example Content Application', headerColor: '#2196F3', logoPath: 'assets/images/alfresco-logo-white.svg', - selectedNodes: [] + selection: { + nodes: [], + isEmpty: true, + count: 0 + } }; export interface AppStore { diff --git a/src/app/store/states/selection.state.ts b/src/app/store/states/selection.state.ts new file mode 100644 index 000000000..9db206192 --- /dev/null +++ b/src/app/store/states/selection.state.ts @@ -0,0 +1,36 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { MinimalNodeEntity } from 'alfresco-js-api'; + +export interface SelectionState { + count: number; + nodes: MinimalNodeEntity[]; + isEmpty: boolean; + first?: MinimalNodeEntity; + last?: MinimalNodeEntity; + folder?: MinimalNodeEntity; + file?: MinimalNodeEntity; +}