mirror of
https://github.com/Alfresco/alfresco-content-app.git
synced 2025-07-31 17:38:28 +00:00
[AAE-11496] Move 'content-plugin' to projects folder as 'aca-content' (#2817)
* [AAE-11496] Move content-plugin to projects * Fix unit test
This commit is contained in:
77
projects/aca-content/src/lib/store/effects/app.effects.ts
Normal file
77
projects/aca-content/src/lib/store/effects/app.effects.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
/*!
|
||||
* @license
|
||||
* Alfresco Example Content Application
|
||||
*
|
||||
* Copyright (C) 2005 - 2020 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Actions, ofType, createEffect } from '@ngrx/effects';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { map } from 'rxjs/operators';
|
||||
import { AppActionTypes, LogoutAction, ReloadDocumentListAction, ResetSelectionAction } from '@alfresco/aca-shared/store';
|
||||
import { AuthenticationService } from '@alfresco/adf-core';
|
||||
import { Router } from '@angular/router';
|
||||
import { AppHookService } from '@alfresco/aca-shared';
|
||||
|
||||
@Injectable()
|
||||
export class AppEffects {
|
||||
constructor(private actions$: Actions, private auth: AuthenticationService, private router: Router, private appHookService: AppHookService) {}
|
||||
|
||||
reload = createEffect(
|
||||
() =>
|
||||
this.actions$.pipe(
|
||||
ofType<ReloadDocumentListAction>(AppActionTypes.ReloadDocumentList),
|
||||
map((action) => {
|
||||
this.appHookService.reload.next(action);
|
||||
})
|
||||
),
|
||||
{ dispatch: false }
|
||||
);
|
||||
|
||||
resetSelection = createEffect(
|
||||
() =>
|
||||
this.actions$.pipe(
|
||||
ofType<ResetSelectionAction>(AppActionTypes.ResetSelection),
|
||||
map((action) => {
|
||||
this.appHookService.reset.next(action);
|
||||
})
|
||||
),
|
||||
{ dispatch: false }
|
||||
);
|
||||
|
||||
logout$ = createEffect(
|
||||
() =>
|
||||
this.actions$.pipe(
|
||||
ofType<LogoutAction>(AppActionTypes.Logout),
|
||||
map(() => {
|
||||
this.auth.logout().subscribe(
|
||||
() => this.redirectToLogin(),
|
||||
() => this.redirectToLogin()
|
||||
);
|
||||
})
|
||||
),
|
||||
{ dispatch: false }
|
||||
);
|
||||
|
||||
private redirectToLogin(): Promise<boolean> {
|
||||
return this.router.navigate(['login']);
|
||||
}
|
||||
}
|
@@ -0,0 +1,66 @@
|
||||
/*!
|
||||
* @license
|
||||
* Alfresco Example Content Application
|
||||
*
|
||||
* Copyright (C) 2005 - 2020 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import { AppTestingModule } from '../../testing/app-testing.module';
|
||||
import { ContextMenuEffects } from './contextmenu.effects';
|
||||
import { EffectsModule } from '@ngrx/effects';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { ContextMenu } from '@alfresco/aca-shared/store';
|
||||
import { ContextMenuService } from '../../components/context-menu/context-menu.service';
|
||||
import { OverlayRef } from '@angular/cdk/overlay';
|
||||
import { ContextMenuOverlayRef } from '../../components/context-menu/context-menu-overlay';
|
||||
|
||||
describe('ContextMenuEffects', () => {
|
||||
let store: Store<any>;
|
||||
let contextMenuService: ContextMenuService;
|
||||
const overlayRefMock = new ContextMenuOverlayRef({} as OverlayRef);
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [AppTestingModule, EffectsModule.forRoot([ContextMenuEffects])],
|
||||
providers: [ContextMenuService]
|
||||
});
|
||||
|
||||
store = TestBed.inject(Store);
|
||||
contextMenuService = TestBed.inject(ContextMenuService);
|
||||
|
||||
spyOn(overlayRefMock, 'close').and.callFake(() => {});
|
||||
spyOn(contextMenuService, 'open').and.returnValue(overlayRefMock);
|
||||
});
|
||||
|
||||
it('should open dialog', () => {
|
||||
store.dispatch(new ContextMenu(new MouseEvent('click')));
|
||||
expect(contextMenuService.open).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should close dialog reference if previously was opened', () => {
|
||||
store.dispatch(new ContextMenu(new MouseEvent('click')));
|
||||
expect(contextMenuService.open).toHaveBeenCalled();
|
||||
|
||||
store.dispatch(new ContextMenu(new MouseEvent('click')));
|
||||
expect(overlayRefMock.close).toHaveBeenCalled();
|
||||
});
|
||||
});
|
@@ -0,0 +1,58 @@
|
||||
/*!
|
||||
* @license
|
||||
* Alfresco Example Content Application
|
||||
*
|
||||
* Copyright (C) 2005 - 2020 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { ContextMenuActionTypes, ContextMenu } from '@alfresco/aca-shared/store';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Actions, ofType, createEffect } from '@ngrx/effects';
|
||||
import { map } from 'rxjs/operators';
|
||||
import { ContextMenuOverlayRef } from '../../components/context-menu/context-menu-overlay';
|
||||
import { ContextMenuService } from '../../components/context-menu/context-menu.service';
|
||||
|
||||
@Injectable()
|
||||
export class ContextMenuEffects {
|
||||
private overlayRef: ContextMenuOverlayRef = null;
|
||||
|
||||
constructor(private contextMenuService: ContextMenuService, private actions$: Actions) {}
|
||||
|
||||
contextMenu$ = createEffect(
|
||||
() =>
|
||||
this.actions$.pipe(
|
||||
ofType<ContextMenu>(ContextMenuActionTypes.ContextMenu),
|
||||
map((action) => {
|
||||
if (this.overlayRef) {
|
||||
this.overlayRef.close();
|
||||
}
|
||||
|
||||
this.overlayRef = this.contextMenuService.open({
|
||||
source: action.event,
|
||||
hasBackdrop: false,
|
||||
backdropClass: 'cdk-overlay-transparent-backdrop',
|
||||
panelClass: 'cdk-overlay-pane'
|
||||
});
|
||||
})
|
||||
),
|
||||
{ dispatch: false }
|
||||
);
|
||||
}
|
@@ -0,0 +1,115 @@
|
||||
/*!
|
||||
* @license
|
||||
* Alfresco Example Content Application
|
||||
*
|
||||
* Copyright (C) 2005 - 2020 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import { AppTestingModule } from '../../testing/app-testing.module';
|
||||
import { EffectsModule } from '@ngrx/effects';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { BehaviorSubject, Subject } from 'rxjs';
|
||||
import { MatDialog, MatDialogRef } from '@angular/material/dialog';
|
||||
import { DownloadNodesAction } from '@alfresco/aca-shared/store';
|
||||
import { SelectionState } from '@alfresco/adf-extensions';
|
||||
import { VersionEntry } from '@alfresco/js-api';
|
||||
import { DownloadEffects } from './download.effects';
|
||||
|
||||
describe('DownloadEffects', () => {
|
||||
let store: Store;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [AppTestingModule, EffectsModule.forRoot([DownloadEffects])]
|
||||
});
|
||||
store = TestBed.inject(Store);
|
||||
});
|
||||
|
||||
describe('downloadNode$', () => {
|
||||
let dialog: MatDialog;
|
||||
|
||||
beforeEach(() => {
|
||||
dialog = TestBed.inject(MatDialog);
|
||||
});
|
||||
|
||||
it('should focus element indicated by passed selector after closing modal', () => {
|
||||
const elementToFocusSelector = 'button';
|
||||
const afterClosed$ = new Subject<void>();
|
||||
spyOn(dialog, 'open').and.returnValue({
|
||||
afterClosed: () => afterClosed$.asObservable()
|
||||
} as MatDialogRef<any>);
|
||||
const elementToFocus = document.createElement(elementToFocusSelector);
|
||||
spyOn(elementToFocus, 'focus');
|
||||
spyOn(document, 'querySelector').withArgs(elementToFocusSelector).and.returnValue(elementToFocus);
|
||||
spyOn(store, 'select').and.returnValues(
|
||||
new BehaviorSubject({
|
||||
isEmpty: false,
|
||||
nodes: [
|
||||
{
|
||||
entry: {
|
||||
id: 'someId',
|
||||
isFolder: true
|
||||
}
|
||||
}
|
||||
]
|
||||
} as SelectionState),
|
||||
new BehaviorSubject<VersionEntry>(null)
|
||||
);
|
||||
store.dispatch(
|
||||
new DownloadNodesAction([], {
|
||||
focusedElementOnCloseSelector: elementToFocusSelector
|
||||
})
|
||||
);
|
||||
afterClosed$.next();
|
||||
expect(elementToFocus.focus).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should not looking for element to focus if passed selector is empty string', () => {
|
||||
const afterClosed$ = new Subject<void>();
|
||||
spyOn(dialog, 'open').and.returnValue({
|
||||
afterClosed: () => afterClosed$.asObservable()
|
||||
} as MatDialogRef<any>);
|
||||
spyOn(document, 'querySelector');
|
||||
spyOn(store, 'select').and.returnValues(
|
||||
new BehaviorSubject({
|
||||
isEmpty: false,
|
||||
nodes: [
|
||||
{
|
||||
entry: {
|
||||
id: 'someId',
|
||||
isFolder: true
|
||||
}
|
||||
}
|
||||
]
|
||||
} as SelectionState),
|
||||
new BehaviorSubject<VersionEntry>(null)
|
||||
);
|
||||
store.dispatch(
|
||||
new DownloadNodesAction([], {
|
||||
focusedElementOnCloseSelector: ''
|
||||
})
|
||||
);
|
||||
afterClosed$.next();
|
||||
expect(document.querySelector).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
171
projects/aca-content/src/lib/store/effects/download.effects.ts
Normal file
171
projects/aca-content/src/lib/store/effects/download.effects.ts
Normal file
@@ -0,0 +1,171 @@
|
||||
/*!
|
||||
* @license
|
||||
* Alfresco Example Content Application
|
||||
*
|
||||
* Copyright (C) 2005 - 2020 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { AppStore, DownloadNodesAction, NodeActionTypes, NodeInfo, getAppSelection, getCurrentVersion } from '@alfresco/aca-shared/store';
|
||||
import { DownloadZipDialogComponent } from '@alfresco/adf-core';
|
||||
import { MinimalNodeEntity, Version } from '@alfresco/js-api';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { Actions, ofType, createEffect } from '@ngrx/effects';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { map, take } from 'rxjs/operators';
|
||||
import { ContentApiService } from '@alfresco/aca-shared';
|
||||
import { ContentUrlService } from '../../services/content-url.service';
|
||||
|
||||
@Injectable()
|
||||
export class DownloadEffects {
|
||||
constructor(
|
||||
private store: Store<AppStore>,
|
||||
private actions$: Actions,
|
||||
private contentApi: ContentApiService,
|
||||
private dialog: MatDialog,
|
||||
private contentUrlService: ContentUrlService
|
||||
) {}
|
||||
|
||||
downloadNode$ = createEffect(
|
||||
() =>
|
||||
this.actions$.pipe(
|
||||
ofType<DownloadNodesAction>(NodeActionTypes.Download),
|
||||
map((action) => {
|
||||
if (action.payload?.length > 0) {
|
||||
this.downloadNodes(action.payload);
|
||||
} else {
|
||||
this.store
|
||||
.select(getAppSelection)
|
||||
.pipe(take(1))
|
||||
.subscribe((selection) => {
|
||||
if (selection && !selection.isEmpty) {
|
||||
this.store
|
||||
.select(getCurrentVersion)
|
||||
.pipe(take(1))
|
||||
.subscribe((version) => {
|
||||
if (version) {
|
||||
this.downloadFileVersion(selection.nodes[0].entry, version.entry);
|
||||
} else {
|
||||
this.downloadNodes(selection.nodes, action.configuration?.focusedElementOnCloseSelector);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
),
|
||||
{ dispatch: false }
|
||||
);
|
||||
|
||||
private downloadNodes(toDownload: Array<MinimalNodeEntity>, focusedElementSelector?: string) {
|
||||
const nodes = toDownload.map((node) => {
|
||||
const { id, nodeId, name, isFile, isFolder } = node.entry as any;
|
||||
|
||||
return {
|
||||
id: this.isSharedLinkPreview ? id : nodeId || id,
|
||||
name,
|
||||
isFile,
|
||||
isFolder
|
||||
};
|
||||
});
|
||||
|
||||
if (!nodes || nodes.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (nodes.length === 1) {
|
||||
this.downloadNode(nodes[0], focusedElementSelector);
|
||||
} else {
|
||||
this.downloadZip(nodes, focusedElementSelector);
|
||||
}
|
||||
}
|
||||
|
||||
private downloadNode(node: NodeInfo, focusedElementSelector?: string) {
|
||||
if (node) {
|
||||
if (node.isFolder) {
|
||||
this.downloadZip([node], focusedElementSelector);
|
||||
} else {
|
||||
this.downloadFile(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private downloadFile(node: NodeInfo) {
|
||||
if (node && !this.isSharedLinkPreview) {
|
||||
this.contentUrlService.getNodeContentUrl(node.id, true).subscribe((contentUrl) => {
|
||||
this.download(contentUrl, node.name);
|
||||
});
|
||||
}
|
||||
|
||||
if (node && this.isSharedLinkPreview) {
|
||||
this.download(this.contentApi.getSharedLinkContent(node.id, false), node.name);
|
||||
}
|
||||
}
|
||||
|
||||
private downloadFileVersion(node: NodeInfo, version: Version) {
|
||||
if (node && version) {
|
||||
this.contentUrlService.getVersionContentUrl(node.id, version.id, true).subscribe((contentUrl) => {
|
||||
this.download(contentUrl, node.name);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private downloadZip(nodes: Array<NodeInfo>, focusedElementSelector?: string) {
|
||||
if (nodes && nodes.length > 0) {
|
||||
const nodeIds = nodes.map((node) => node.id);
|
||||
|
||||
this.dialog
|
||||
.open(DownloadZipDialogComponent, {
|
||||
width: '600px',
|
||||
disableClose: true,
|
||||
data: {
|
||||
nodeIds
|
||||
}
|
||||
})
|
||||
.afterClosed()
|
||||
.subscribe(() => this.focusAfterClose(focusedElementSelector));
|
||||
}
|
||||
}
|
||||
|
||||
private download(url: string, fileName: string) {
|
||||
if (url && fileName) {
|
||||
const link = document.createElement('a');
|
||||
|
||||
link.style.display = 'none';
|
||||
link.download = fileName;
|
||||
link.href = url;
|
||||
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
}
|
||||
}
|
||||
|
||||
private get isSharedLinkPreview() {
|
||||
return location.href.includes('/preview/s/');
|
||||
}
|
||||
|
||||
private focusAfterClose(focusedElementSelector: string): void {
|
||||
if (focusedElementSelector) {
|
||||
document.querySelector<HTMLElement>(focusedElementSelector).focus();
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,80 @@
|
||||
/*!
|
||||
* @license
|
||||
* Alfresco Example Content Application
|
||||
*
|
||||
* Copyright (C) 2005 - 2020 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Actions, ofType, createEffect } from '@ngrx/effects';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { map, take } from 'rxjs/operators';
|
||||
import { AppStore, NodeActionTypes, AddFavoriteAction, RemoveFavoriteAction, getAppSelection } from '@alfresco/aca-shared/store';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { ContentManagementService } from '../../services/content-management.service';
|
||||
|
||||
@Injectable()
|
||||
export class FavoriteEffects {
|
||||
constructor(private store: Store<AppStore>, private actions$: Actions, private content: ContentManagementService) {}
|
||||
|
||||
addFavorite$ = createEffect(
|
||||
() =>
|
||||
this.actions$.pipe(
|
||||
ofType<AddFavoriteAction>(NodeActionTypes.AddFavorite),
|
||||
map((action) => {
|
||||
if (action.payload && action.payload.length > 0) {
|
||||
this.content.addFavorite(action.payload);
|
||||
} else {
|
||||
this.store
|
||||
.select(getAppSelection)
|
||||
.pipe(take(1))
|
||||
.subscribe((selection) => {
|
||||
if (selection && !selection.isEmpty) {
|
||||
this.content.addFavorite(selection.nodes);
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
),
|
||||
{ dispatch: false }
|
||||
);
|
||||
|
||||
removeFavorite$ = createEffect(
|
||||
() =>
|
||||
this.actions$.pipe(
|
||||
ofType<RemoveFavoriteAction>(NodeActionTypes.RemoveFavorite),
|
||||
map((action) => {
|
||||
if (action.payload && action.payload.length > 0) {
|
||||
this.content.removeFavorite(action.payload);
|
||||
} else {
|
||||
this.store
|
||||
.select(getAppSelection)
|
||||
.pipe(take(1))
|
||||
.subscribe((selection) => {
|
||||
if (selection && !selection.isEmpty) {
|
||||
this.content.removeFavorite(selection.nodes);
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
),
|
||||
{ dispatch: false }
|
||||
);
|
||||
}
|
159
projects/aca-content/src/lib/store/effects/library.effects.ts
Normal file
159
projects/aca-content/src/lib/store/effects/library.effects.ts
Normal file
@@ -0,0 +1,159 @@
|
||||
/*!
|
||||
* @license
|
||||
* Alfresco Example Content Application
|
||||
*
|
||||
* Copyright (C) 2005 - 2020 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {
|
||||
AppStore,
|
||||
CreateLibraryAction,
|
||||
DeleteLibraryAction,
|
||||
LeaveLibraryAction,
|
||||
LibraryActionTypes,
|
||||
NavigateLibraryAction,
|
||||
NavigateRouteAction,
|
||||
SnackbarErrorAction,
|
||||
UpdateLibraryAction,
|
||||
getAppSelection
|
||||
} from '@alfresco/aca-shared/store';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Actions, ofType, createEffect } from '@ngrx/effects';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { map, mergeMap, take } from 'rxjs/operators';
|
||||
import { ContentApiService } from '@alfresco/aca-shared';
|
||||
import { ContentManagementService } from '../../services/content-management.service';
|
||||
|
||||
@Injectable()
|
||||
export class LibraryEffects {
|
||||
constructor(
|
||||
private store: Store<AppStore>,
|
||||
private actions$: Actions,
|
||||
private content: ContentManagementService,
|
||||
private contentApi: ContentApiService
|
||||
) {}
|
||||
|
||||
deleteLibrary$ = createEffect(
|
||||
() =>
|
||||
this.actions$.pipe(
|
||||
ofType<DeleteLibraryAction>(LibraryActionTypes.Delete),
|
||||
map((action) => {
|
||||
if (action.payload) {
|
||||
this.content.deleteLibrary(action.payload);
|
||||
} else {
|
||||
this.store
|
||||
.select(getAppSelection)
|
||||
.pipe(take(1))
|
||||
.subscribe((selection) => {
|
||||
if (selection && selection.library) {
|
||||
this.content.deleteLibrary(selection.library.entry.id);
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
),
|
||||
{ dispatch: false }
|
||||
);
|
||||
|
||||
leaveLibrary$ = createEffect(
|
||||
() =>
|
||||
this.actions$.pipe(
|
||||
ofType<LeaveLibraryAction>(LibraryActionTypes.Leave),
|
||||
map((action) => {
|
||||
if (action.payload) {
|
||||
this.content.leaveLibrary(action.payload);
|
||||
} else {
|
||||
this.store
|
||||
.select(getAppSelection)
|
||||
.pipe(take(1))
|
||||
.subscribe((selection) => {
|
||||
if (selection && selection.library) {
|
||||
this.content.leaveLibrary(selection.library.entry.id, action.configuration?.focusedElementOnCloseSelector);
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
),
|
||||
{ dispatch: false }
|
||||
);
|
||||
|
||||
createLibrary$ = createEffect(
|
||||
() =>
|
||||
this.actions$.pipe(
|
||||
ofType<CreateLibraryAction>(LibraryActionTypes.Create),
|
||||
mergeMap(() => this.content.createLibrary()),
|
||||
map((libraryId) => new NavigateLibraryAction(libraryId))
|
||||
),
|
||||
{ dispatch: true }
|
||||
);
|
||||
|
||||
navigateLibrary$ = createEffect(
|
||||
() =>
|
||||
this.actions$.pipe(
|
||||
ofType<NavigateLibraryAction>(LibraryActionTypes.Navigate),
|
||||
map((action) => {
|
||||
const libraryId = action.payload;
|
||||
if (libraryId) {
|
||||
this.contentApi
|
||||
.getNode(libraryId, { relativePath: '/documentLibrary' })
|
||||
.pipe(map((node) => node.entry.id))
|
||||
.subscribe(
|
||||
(id) => {
|
||||
const route = action.route ? action.route : 'libraries';
|
||||
this.store.dispatch(new NavigateRouteAction([route, id]));
|
||||
},
|
||||
() => {
|
||||
this.store.dispatch(new SnackbarErrorAction('APP.MESSAGES.ERRORS.MISSING_CONTENT'));
|
||||
}
|
||||
);
|
||||
}
|
||||
})
|
||||
),
|
||||
{ dispatch: false }
|
||||
);
|
||||
|
||||
updateLibrary$ = createEffect(
|
||||
() =>
|
||||
this.actions$.pipe(
|
||||
ofType<UpdateLibraryAction>(LibraryActionTypes.Update),
|
||||
map((action) => {
|
||||
this.store
|
||||
.select(getAppSelection)
|
||||
.pipe(take(1))
|
||||
.subscribe((selection) => {
|
||||
if (selection && selection.library) {
|
||||
const { id } = selection.library.entry;
|
||||
const { title, description, visibility } = action.payload;
|
||||
|
||||
const siteBody = {
|
||||
title,
|
||||
description,
|
||||
visibility
|
||||
};
|
||||
|
||||
this.content.updateLibrary(id, siteBody);
|
||||
}
|
||||
});
|
||||
})
|
||||
),
|
||||
{ dispatch: false }
|
||||
);
|
||||
}
|
509
projects/aca-content/src/lib/store/effects/node.effects.spec.ts
Normal file
509
projects/aca-content/src/lib/store/effects/node.effects.spec.ts
Normal file
@@ -0,0 +1,509 @@
|
||||
/*!
|
||||
* @license
|
||||
* Alfresco Example Content Application
|
||||
*
|
||||
* Copyright (C) 2005 - 2020 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { TestBed, fakeAsync, tick } from '@angular/core/testing';
|
||||
import { AppTestingModule } from '../../testing/app-testing.module';
|
||||
import { NodeEffects } from './node.effects';
|
||||
import { EffectsModule } from '@ngrx/effects';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { ContentManagementService } from '../../services/content-management.service';
|
||||
import {
|
||||
SharedStoreModule,
|
||||
ShareNodeAction,
|
||||
SetSelectedNodesAction,
|
||||
UnshareNodesAction,
|
||||
PurgeDeletedNodesAction,
|
||||
RestoreDeletedNodesAction,
|
||||
DeleteNodesAction,
|
||||
UndoDeleteNodesAction,
|
||||
CreateFolderAction,
|
||||
EditFolderAction,
|
||||
CopyNodesAction,
|
||||
MoveNodesAction,
|
||||
UnlockWriteAction,
|
||||
FullscreenViewerAction,
|
||||
PrintFileAction,
|
||||
SetCurrentFolderAction,
|
||||
ManageAspectsAction,
|
||||
ManagePermissionsAction,
|
||||
ShowLoaderAction
|
||||
} from '@alfresco/aca-shared/store';
|
||||
import { ViewUtilService } from '@alfresco/adf-core';
|
||||
import { ViewerEffects } from './viewer.effects';
|
||||
import { Router } from '@angular/router';
|
||||
import { of } from 'rxjs';
|
||||
|
||||
describe('NodeEffects', () => {
|
||||
let store: Store<any>;
|
||||
let contentService: ContentManagementService;
|
||||
let viewUtilService: ViewUtilService;
|
||||
let viewerEffects: ViewerEffects;
|
||||
let router: Router;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [AppTestingModule, SharedStoreModule, EffectsModule.forRoot([NodeEffects, ViewerEffects])],
|
||||
providers: [ViewUtilService]
|
||||
});
|
||||
|
||||
store = TestBed.inject(Store);
|
||||
contentService = TestBed.inject(ContentManagementService);
|
||||
viewUtilService = TestBed.inject(ViewUtilService);
|
||||
viewerEffects = TestBed.inject(ViewerEffects);
|
||||
router = TestBed.inject(Router);
|
||||
});
|
||||
|
||||
describe('shareNode$', () => {
|
||||
it('should share node from payload', () => {
|
||||
spyOn(contentService, 'shareNode').and.stub();
|
||||
|
||||
const node: any = {
|
||||
entry: {}
|
||||
};
|
||||
store.dispatch(new ShareNodeAction(node));
|
||||
|
||||
expect(contentService.shareNode).toHaveBeenCalledWith(node, undefined);
|
||||
});
|
||||
|
||||
it('should share node from active selection', fakeAsync(() => {
|
||||
spyOn(contentService, 'shareNode').and.stub();
|
||||
|
||||
const node: any = { entry: { isFile: true } };
|
||||
store.dispatch(new SetSelectedNodesAction([node]));
|
||||
|
||||
tick(100);
|
||||
|
||||
store.dispatch(new ShareNodeAction(null));
|
||||
expect(contentService.shareNode).toHaveBeenCalledWith(node, undefined);
|
||||
}));
|
||||
|
||||
it('should do nothing if invoking share with no data', () => {
|
||||
spyOn(contentService, 'shareNode').and.stub();
|
||||
|
||||
store.dispatch(new ShareNodeAction(null));
|
||||
|
||||
expect(contentService.shareNode).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('unshareNodes$', () => {
|
||||
it('should unshare nodes from the payload', () => {
|
||||
spyOn(contentService, 'unshareNodes').and.stub();
|
||||
|
||||
const node: any = {};
|
||||
store.dispatch(new UnshareNodesAction([node]));
|
||||
|
||||
expect(contentService.unshareNodes).toHaveBeenCalledWith([node]);
|
||||
});
|
||||
|
||||
it('should unshare nodes from the active selection', fakeAsync(() => {
|
||||
spyOn(contentService, 'unshareNodes').and.stub();
|
||||
|
||||
const node: any = { entry: { isFile: true } };
|
||||
store.dispatch(new SetSelectedNodesAction([node]));
|
||||
|
||||
tick(100);
|
||||
|
||||
store.dispatch(new UnshareNodesAction(null));
|
||||
expect(contentService.unshareNodes).toHaveBeenCalledWith([node]);
|
||||
}));
|
||||
|
||||
it('should do nothing if invoking unshare with no data', () => {
|
||||
spyOn(contentService, 'unshareNodes').and.stub();
|
||||
|
||||
store.dispatch(new UnshareNodesAction(null));
|
||||
|
||||
expect(contentService.unshareNodes).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('purgeDeletedNodes$', () => {
|
||||
it('should purge deleted nodes from the payload', () => {
|
||||
spyOn(contentService, 'purgeDeletedNodes').and.stub();
|
||||
|
||||
const node: any = {};
|
||||
store.dispatch(new PurgeDeletedNodesAction([node]));
|
||||
|
||||
expect(contentService.purgeDeletedNodes).toHaveBeenCalledWith([node]);
|
||||
});
|
||||
|
||||
it('should purge nodes from the active selection', fakeAsync(() => {
|
||||
spyOn(contentService, 'purgeDeletedNodes').and.stub();
|
||||
|
||||
const node: any = { entry: { isFile: true } };
|
||||
store.dispatch(new SetSelectedNodesAction([node]));
|
||||
|
||||
tick(100);
|
||||
|
||||
store.dispatch(new PurgeDeletedNodesAction(null));
|
||||
expect(contentService.purgeDeletedNodes).toHaveBeenCalledWith([node]);
|
||||
}));
|
||||
|
||||
it('should do nothing if invoking purge with no data', () => {
|
||||
spyOn(contentService, 'purgeDeletedNodes').and.stub();
|
||||
|
||||
store.dispatch(new PurgeDeletedNodesAction(null));
|
||||
|
||||
expect(contentService.purgeDeletedNodes).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('restoreDeletedNodes$', () => {
|
||||
it('should restore deleted nodes from the payload', () => {
|
||||
spyOn(contentService, 'restoreDeletedNodes').and.stub();
|
||||
|
||||
const node: any = {};
|
||||
store.dispatch(new RestoreDeletedNodesAction([node]));
|
||||
|
||||
expect(contentService.restoreDeletedNodes).toHaveBeenCalledWith([node]);
|
||||
});
|
||||
|
||||
it('should restore deleted nodes from the active selection', fakeAsync(() => {
|
||||
spyOn(contentService, 'restoreDeletedNodes').and.stub();
|
||||
|
||||
const node: any = { entry: { isFile: true } };
|
||||
store.dispatch(new SetSelectedNodesAction([node]));
|
||||
|
||||
tick(100);
|
||||
|
||||
store.dispatch(new RestoreDeletedNodesAction(null));
|
||||
expect(contentService.restoreDeletedNodes).toHaveBeenCalledWith([node]);
|
||||
}));
|
||||
|
||||
it('should do nothing if invoking restore with no data', () => {
|
||||
spyOn(contentService, 'restoreDeletedNodes').and.stub();
|
||||
|
||||
store.dispatch(new RestoreDeletedNodesAction(null));
|
||||
|
||||
expect(contentService.restoreDeletedNodes).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('deleteNodes$', () => {
|
||||
it('should delete nodes from the payload', () => {
|
||||
spyOn(contentService, 'deleteNodes').and.stub();
|
||||
spyOn(store, 'dispatch').and.callThrough();
|
||||
const node: any = {};
|
||||
store.dispatch(new DeleteNodesAction([node]));
|
||||
|
||||
expect(store.dispatch).toHaveBeenCalledWith(new DeleteNodesAction([node]));
|
||||
expect(store.dispatch).toHaveBeenCalledWith(new ShowLoaderAction(true));
|
||||
expect(contentService.deleteNodes).toHaveBeenCalledWith([node]);
|
||||
});
|
||||
|
||||
it('should delete nodes from the active selection', fakeAsync(() => {
|
||||
spyOn(contentService, 'deleteNodes').and.stub();
|
||||
spyOn(store, 'dispatch').and.callThrough();
|
||||
const node: any = { entry: { isFile: true } };
|
||||
store.dispatch(new SetSelectedNodesAction([node]));
|
||||
|
||||
tick(100);
|
||||
|
||||
store.dispatch(new DeleteNodesAction(null));
|
||||
|
||||
expect(store.dispatch).toHaveBeenCalledWith(new DeleteNodesAction(null));
|
||||
expect(store.dispatch).toHaveBeenCalledWith(new ShowLoaderAction(true));
|
||||
expect(contentService.deleteNodes).toHaveBeenCalledWith([node]);
|
||||
}));
|
||||
|
||||
it('should do nothing if invoking delete with no data', () => {
|
||||
spyOn(contentService, 'deleteNodes').and.stub();
|
||||
spyOn(store, 'dispatch').and.callThrough();
|
||||
store.dispatch(new DeleteNodesAction(null));
|
||||
|
||||
expect(store.dispatch).toHaveBeenCalledWith(new DeleteNodesAction(null));
|
||||
expect(store.dispatch).toHaveBeenCalledWith(new ShowLoaderAction(true));
|
||||
expect(contentService.deleteNodes).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('undoDeleteNodes$', () => {
|
||||
it('should undo deleted nodes from the payload', () => {
|
||||
spyOn(contentService, 'undoDeleteNodes').and.stub();
|
||||
|
||||
const node: any = {};
|
||||
store.dispatch(new UndoDeleteNodesAction([node]));
|
||||
|
||||
expect(contentService.undoDeleteNodes).toHaveBeenCalledWith([node]);
|
||||
});
|
||||
|
||||
it('should do nothing if undoing deletion with no data', () => {
|
||||
spyOn(contentService, 'undoDeleteNodes').and.stub();
|
||||
|
||||
store.dispatch(new UndoDeleteNodesAction([]));
|
||||
|
||||
expect(contentService.undoDeleteNodes).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('createFolder$', () => {
|
||||
beforeEach(() => {
|
||||
spyOn(contentService, 'createFolder').and.stub();
|
||||
});
|
||||
|
||||
it('should create folder from the payload', () => {
|
||||
const currentFolder = 'folder1';
|
||||
store.dispatch(new CreateFolderAction(currentFolder));
|
||||
|
||||
expect(contentService.createFolder).toHaveBeenCalledWith(currentFolder);
|
||||
});
|
||||
|
||||
it('should create folder in the active selected one', fakeAsync(() => {
|
||||
const currentFolder: any = { isFolder: true, id: 'folder1' };
|
||||
store.dispatch(new SetCurrentFolderAction(currentFolder));
|
||||
|
||||
tick(100);
|
||||
|
||||
store.dispatch(new CreateFolderAction(null));
|
||||
expect(contentService.createFolder).toHaveBeenCalledWith(currentFolder.id);
|
||||
}));
|
||||
});
|
||||
|
||||
describe('editFolder$', () => {
|
||||
it('should edit folder from the payload', () => {
|
||||
spyOn(contentService, 'editFolder').and.stub();
|
||||
|
||||
const node: any = { entry: { isFolder: true, id: 'folder1' } };
|
||||
store.dispatch(new EditFolderAction(node));
|
||||
|
||||
expect(contentService.editFolder).toHaveBeenCalledWith(node);
|
||||
});
|
||||
|
||||
it('should edit folder from the active selection', fakeAsync(() => {
|
||||
spyOn(contentService, 'editFolder').and.stub();
|
||||
|
||||
const currentFolder: any = {
|
||||
entry: { isFolder: true, isFile: false, id: 'folder1' }
|
||||
};
|
||||
store.dispatch(new SetSelectedNodesAction([currentFolder]));
|
||||
|
||||
tick(100);
|
||||
|
||||
store.dispatch(new EditFolderAction(null));
|
||||
expect(contentService.editFolder).toHaveBeenCalledWith(currentFolder, undefined);
|
||||
}));
|
||||
|
||||
it('should do nothing if editing folder with no selection and payload', () => {
|
||||
spyOn(contentService, 'editFolder').and.stub();
|
||||
|
||||
store.dispatch(new EditFolderAction(null));
|
||||
|
||||
expect(contentService.editFolder).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('copyNodes$', () => {
|
||||
it('should copy nodes from the payload', () => {
|
||||
spyOn(contentService, 'copyNodes').and.stub();
|
||||
|
||||
const node: any = { entry: { isFile: true } };
|
||||
store.dispatch(new CopyNodesAction([node]));
|
||||
|
||||
expect(contentService.copyNodes).toHaveBeenCalledWith([node]);
|
||||
});
|
||||
|
||||
it('should copy nodes from the active selection', fakeAsync(() => {
|
||||
spyOn(contentService, 'copyNodes').and.stub();
|
||||
|
||||
const node: any = { entry: { isFile: true } };
|
||||
store.dispatch(new SetSelectedNodesAction([node]));
|
||||
|
||||
tick(100);
|
||||
|
||||
store.dispatch(new CopyNodesAction(null));
|
||||
|
||||
expect(contentService.copyNodes).toHaveBeenCalledWith([node], undefined);
|
||||
}));
|
||||
|
||||
it('should do nothing if invoking copy with no data', () => {
|
||||
spyOn(contentService, 'copyNodes').and.stub();
|
||||
|
||||
store.dispatch(new CopyNodesAction(null));
|
||||
|
||||
expect(contentService.copyNodes).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('moveNodes$', () => {
|
||||
it('should move nodes from the payload', () => {
|
||||
spyOn(contentService, 'moveNodes').and.stub();
|
||||
|
||||
const node: any = { entry: { isFile: true } };
|
||||
store.dispatch(new MoveNodesAction([node]));
|
||||
|
||||
expect(contentService.moveNodes).toHaveBeenCalledWith([node]);
|
||||
});
|
||||
|
||||
it('should move nodes from the active selection', fakeAsync(() => {
|
||||
spyOn(contentService, 'moveNodes').and.stub();
|
||||
|
||||
const node: any = { entry: { isFile: true } };
|
||||
store.dispatch(new SetSelectedNodesAction([node]));
|
||||
|
||||
tick(100);
|
||||
|
||||
store.dispatch(new MoveNodesAction(null));
|
||||
|
||||
expect(contentService.moveNodes).toHaveBeenCalledWith([node], undefined);
|
||||
}));
|
||||
|
||||
it('should do nothing if invoking move with no data', () => {
|
||||
spyOn(contentService, 'moveNodes').and.stub();
|
||||
|
||||
store.dispatch(new MoveNodesAction(null));
|
||||
|
||||
expect(contentService.moveNodes).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('managePermissions$', () => {
|
||||
it('should manage permissions from the payload', () => {
|
||||
spyOn(router, 'navigate').and.stub();
|
||||
const node: any = { entry: { isFile: true, id: 'fileId' } };
|
||||
store.dispatch(new ManagePermissionsAction(node));
|
||||
|
||||
expect(router.navigate).toHaveBeenCalledWith(['personal-files/details', 'fileId', 'permissions']);
|
||||
});
|
||||
|
||||
it('should manage permissions from the active selection', () => {
|
||||
spyOn(store, 'select').and.returnValue(of({ isEmpty: false, first: { entry: { id: 'fileId' } } }));
|
||||
spyOn(router, 'navigate').and.stub();
|
||||
store.dispatch(new ManagePermissionsAction(null));
|
||||
|
||||
expect(router.navigate).toHaveBeenCalledWith(['personal-files/details', 'fileId', 'permissions']);
|
||||
});
|
||||
|
||||
it('should do nothing if invoking manage permissions with no data', () => {
|
||||
spyOn(store, 'select').and.returnValue(of(null));
|
||||
spyOn(router, 'navigate').and.stub();
|
||||
store.dispatch(new ManagePermissionsAction(null));
|
||||
|
||||
expect(router.navigate).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('printFile$', () => {
|
||||
it('it should print node content from payload', () => {
|
||||
spyOn(viewUtilService, 'printFileGeneric').and.stub();
|
||||
const node: any = {
|
||||
entry: { id: 'node-id', content: { mimeType: 'text/json' } }
|
||||
};
|
||||
|
||||
store.dispatch(new PrintFileAction(node));
|
||||
|
||||
expect(viewUtilService.printFileGeneric).toHaveBeenCalledWith('node-id', 'text/json');
|
||||
});
|
||||
|
||||
it('it should print node content from store', fakeAsync(() => {
|
||||
spyOn(viewUtilService, 'printFileGeneric').and.stub();
|
||||
const node: any = {
|
||||
entry: {
|
||||
isFile: true,
|
||||
id: 'node-id',
|
||||
content: { mimeType: 'text/json' }
|
||||
}
|
||||
};
|
||||
|
||||
store.dispatch(new SetSelectedNodesAction([node]));
|
||||
|
||||
tick(100);
|
||||
|
||||
store.dispatch(new PrintFileAction(null));
|
||||
|
||||
expect(viewUtilService.printFileGeneric).toHaveBeenCalledWith('node-id', 'text/json');
|
||||
}));
|
||||
});
|
||||
|
||||
describe('fullscreenViewer$', () => {
|
||||
it('should call fullscreen viewer', () => {
|
||||
spyOn(viewerEffects, 'enterFullScreen').and.stub();
|
||||
|
||||
store.dispatch(new FullscreenViewerAction(null));
|
||||
|
||||
expect(viewerEffects.enterFullScreen).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('unlockWrite$', () => {
|
||||
it('should unlock node from payload', () => {
|
||||
spyOn(contentService, 'unlockNode').and.stub();
|
||||
const node: any = { entry: { id: 'node-id' } };
|
||||
|
||||
store.dispatch(new UnlockWriteAction(node));
|
||||
|
||||
expect(contentService.unlockNode).toHaveBeenCalledWith(node);
|
||||
});
|
||||
|
||||
it('should unlock node from store selection', fakeAsync(() => {
|
||||
spyOn(contentService, 'unlockNode').and.stub();
|
||||
const node: any = { entry: { isFile: true, id: 'node-id' } };
|
||||
|
||||
store.dispatch(new SetSelectedNodesAction([node]));
|
||||
|
||||
tick(100);
|
||||
|
||||
store.dispatch(new UnlockWriteAction(null));
|
||||
|
||||
expect(contentService.unlockNode).toHaveBeenCalledWith(node);
|
||||
}));
|
||||
});
|
||||
|
||||
describe('aspectList$', () => {
|
||||
it('should call aspect dialog', () => {
|
||||
const node: any = { entry: { isFile: true } };
|
||||
spyOn(contentService, 'manageAspects').and.stub();
|
||||
|
||||
store.dispatch(new ManageAspectsAction(node));
|
||||
|
||||
expect(contentService.manageAspects).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should call aspect dialog from the active file selection', fakeAsync(() => {
|
||||
spyOn(contentService, 'manageAspects').and.stub();
|
||||
|
||||
const node: any = { entry: { isFile: true, id: 'file-node-id' } };
|
||||
store.dispatch(new SetSelectedNodesAction([node]));
|
||||
|
||||
tick(100);
|
||||
|
||||
store.dispatch(new ManageAspectsAction(null));
|
||||
|
||||
expect(contentService.manageAspects).toHaveBeenCalledWith({ entry: { isFile: true, id: 'file-node-id' } }, undefined);
|
||||
}));
|
||||
|
||||
it('should call aspect dialog from the active folder selection', fakeAsync(() => {
|
||||
spyOn(contentService, 'manageAspects').and.stub();
|
||||
|
||||
const node: any = { entry: { isFile: false, id: 'folder-node-id' } };
|
||||
store.dispatch(new SetSelectedNodesAction([node]));
|
||||
|
||||
tick(100);
|
||||
|
||||
store.dispatch(new ManageAspectsAction(null));
|
||||
|
||||
expect(contentService.manageAspects).toHaveBeenCalledWith({ entry: { isFile: false, id: 'folder-node-id' } }, undefined);
|
||||
}));
|
||||
});
|
||||
});
|
448
projects/aca-content/src/lib/store/effects/node.effects.ts
Normal file
448
projects/aca-content/src/lib/store/effects/node.effects.ts
Normal file
@@ -0,0 +1,448 @@
|
||||
/*!
|
||||
* @license
|
||||
* Alfresco Example Content Application
|
||||
*
|
||||
* Copyright (C) 2005 - 2020 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Actions, ofType, createEffect } from '@ngrx/effects';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { map, take } from 'rxjs/operators';
|
||||
import { Store } from '@ngrx/store';
|
||||
import {
|
||||
AppStore,
|
||||
NodeActionTypes,
|
||||
PurgeDeletedNodesAction,
|
||||
DeleteNodesAction,
|
||||
UndoDeleteNodesAction,
|
||||
CreateFolderAction,
|
||||
EditFolderAction,
|
||||
RestoreDeletedNodesAction,
|
||||
ShareNodeAction,
|
||||
ManageVersionsAction,
|
||||
UnlockWriteAction,
|
||||
UnshareNodesAction,
|
||||
CopyNodesAction,
|
||||
MoveNodesAction,
|
||||
ManagePermissionsAction,
|
||||
PrintFileAction,
|
||||
getCurrentFolder,
|
||||
getAppSelection,
|
||||
ManageAspectsAction,
|
||||
NavigateRouteAction,
|
||||
ExpandInfoDrawerAction,
|
||||
ManageRulesAction,
|
||||
ShowLoaderAction
|
||||
} from '@alfresco/aca-shared/store';
|
||||
import { ContentManagementService } from '../../services/content-management.service';
|
||||
import { ViewUtilService } from '@alfresco/adf-core';
|
||||
|
||||
@Injectable()
|
||||
export class NodeEffects {
|
||||
constructor(
|
||||
private store: Store<AppStore>,
|
||||
private actions$: Actions,
|
||||
private contentService: ContentManagementService,
|
||||
private viewUtils: ViewUtilService
|
||||
) {}
|
||||
|
||||
shareNode$ = createEffect(
|
||||
() =>
|
||||
this.actions$.pipe(
|
||||
ofType<ShareNodeAction>(NodeActionTypes.Share),
|
||||
map((action) => {
|
||||
if (action.payload) {
|
||||
this.contentService.shareNode(action.payload, action.configuration?.focusedElementOnCloseSelector);
|
||||
} else {
|
||||
this.store
|
||||
.select(getAppSelection)
|
||||
.pipe(take(1))
|
||||
.subscribe((selection) => {
|
||||
if (selection && selection.file) {
|
||||
this.contentService.shareNode(selection.file, action.configuration?.focusedElementOnCloseSelector);
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
),
|
||||
{ dispatch: false }
|
||||
);
|
||||
|
||||
unshareNodes$ = createEffect(
|
||||
() =>
|
||||
this.actions$.pipe(
|
||||
ofType<UnshareNodesAction>(NodeActionTypes.Unshare),
|
||||
map((action) => {
|
||||
if (action && action.payload && action.payload.length > 0) {
|
||||
this.contentService.unshareNodes(action.payload);
|
||||
} else {
|
||||
this.store
|
||||
.select(getAppSelection)
|
||||
.pipe(take(1))
|
||||
.subscribe((selection) => {
|
||||
if (selection && !selection.isEmpty) {
|
||||
this.contentService.unshareNodes(selection.nodes);
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
),
|
||||
{ dispatch: false }
|
||||
);
|
||||
|
||||
purgeDeletedNodes$ = createEffect(
|
||||
() =>
|
||||
this.actions$.pipe(
|
||||
ofType<PurgeDeletedNodesAction>(NodeActionTypes.PurgeDeleted),
|
||||
map((action) => {
|
||||
if (action && action.payload && action.payload.length > 0) {
|
||||
this.contentService.purgeDeletedNodes(action.payload);
|
||||
} else {
|
||||
this.store
|
||||
.select(getAppSelection)
|
||||
.pipe(take(1))
|
||||
.subscribe((selection) => {
|
||||
if (selection && selection.count > 0) {
|
||||
this.contentService.purgeDeletedNodes(selection.nodes);
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
),
|
||||
{ dispatch: false }
|
||||
);
|
||||
|
||||
restoreDeletedNodes$ = createEffect(
|
||||
() =>
|
||||
this.actions$.pipe(
|
||||
ofType<RestoreDeletedNodesAction>(NodeActionTypes.RestoreDeleted),
|
||||
map((action) => {
|
||||
if (action && action.payload && action.payload.length > 0) {
|
||||
this.contentService.restoreDeletedNodes(action.payload);
|
||||
} else {
|
||||
this.store
|
||||
.select(getAppSelection)
|
||||
.pipe(take(1))
|
||||
.subscribe((selection) => {
|
||||
if (selection && selection.count > 0) {
|
||||
this.contentService.restoreDeletedNodes(selection.nodes);
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
),
|
||||
{ dispatch: false }
|
||||
);
|
||||
|
||||
deleteNodes$ = createEffect(
|
||||
() =>
|
||||
this.actions$.pipe(
|
||||
ofType<DeleteNodesAction>(NodeActionTypes.Delete),
|
||||
map((action) => {
|
||||
this.store.dispatch(new ShowLoaderAction(true));
|
||||
if (action && action.payload && action.payload.length > 0) {
|
||||
this.contentService.deleteNodes(action.payload);
|
||||
} else {
|
||||
this.store
|
||||
.select(getAppSelection)
|
||||
.pipe(take(1))
|
||||
.subscribe((selection) => {
|
||||
if (selection && selection.count > 0) {
|
||||
this.contentService.deleteNodes(selection.nodes);
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
),
|
||||
{ dispatch: false }
|
||||
);
|
||||
|
||||
undoDeleteNodes$ = createEffect(
|
||||
() =>
|
||||
this.actions$.pipe(
|
||||
ofType<UndoDeleteNodesAction>(NodeActionTypes.UndoDelete),
|
||||
map((action) => {
|
||||
if (action.payload.length > 0) {
|
||||
this.contentService.undoDeleteNodes(action.payload);
|
||||
}
|
||||
})
|
||||
),
|
||||
{ dispatch: false }
|
||||
);
|
||||
|
||||
createFolder$ = createEffect(
|
||||
() =>
|
||||
this.actions$.pipe(
|
||||
ofType<CreateFolderAction>(NodeActionTypes.CreateFolder),
|
||||
map((action) => {
|
||||
if (action.payload) {
|
||||
this.contentService.createFolder(action.payload);
|
||||
} else {
|
||||
this.store
|
||||
.select(getCurrentFolder)
|
||||
.pipe(take(1))
|
||||
.subscribe((node) => {
|
||||
if (node && node.id) {
|
||||
this.contentService.createFolder(node.id);
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
),
|
||||
{ dispatch: false }
|
||||
);
|
||||
|
||||
editFolder$ = createEffect(
|
||||
() =>
|
||||
this.actions$.pipe(
|
||||
ofType<EditFolderAction>(NodeActionTypes.EditFolder),
|
||||
map((action) => {
|
||||
if (action.payload) {
|
||||
this.contentService.editFolder(action.payload);
|
||||
} else {
|
||||
this.store
|
||||
.select(getAppSelection)
|
||||
.pipe(take(1))
|
||||
.subscribe((selection) => {
|
||||
if (selection && selection.folder) {
|
||||
this.contentService.editFolder(selection.folder, action.configuration?.focusedElementOnCloseSelector);
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
),
|
||||
{ dispatch: false }
|
||||
);
|
||||
|
||||
copyNodes$ = createEffect(
|
||||
() =>
|
||||
this.actions$.pipe(
|
||||
ofType<CopyNodesAction>(NodeActionTypes.Copy),
|
||||
map((action) => {
|
||||
if (action.payload?.length > 0) {
|
||||
this.contentService.copyNodes(action.payload);
|
||||
} else {
|
||||
this.store
|
||||
.select(getAppSelection)
|
||||
.pipe(take(1))
|
||||
.subscribe((selection) => {
|
||||
if (selection && !selection.isEmpty) {
|
||||
this.contentService.copyNodes(selection.nodes, action.configuration?.focusedElementOnCloseSelector);
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
),
|
||||
{ dispatch: false }
|
||||
);
|
||||
|
||||
moveNodes$ = createEffect(
|
||||
() =>
|
||||
this.actions$.pipe(
|
||||
ofType<MoveNodesAction>(NodeActionTypes.Move),
|
||||
map((action) => {
|
||||
if (action.payload?.length > 0) {
|
||||
this.contentService.moveNodes(action.payload);
|
||||
} else {
|
||||
this.store
|
||||
.select(getAppSelection)
|
||||
.pipe(take(1))
|
||||
.subscribe((selection) => {
|
||||
if (selection && !selection.isEmpty) {
|
||||
this.contentService.moveNodes(selection.nodes, action.configuration?.focusedElementOnCloseSelector);
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
),
|
||||
{ dispatch: false }
|
||||
);
|
||||
|
||||
managePermissions$ = createEffect(
|
||||
() =>
|
||||
this.actions$.pipe(
|
||||
ofType<ManagePermissionsAction>(NodeActionTypes.ManagePermissions),
|
||||
map((action) => {
|
||||
if (action?.payload) {
|
||||
const route = 'personal-files/details';
|
||||
this.store.dispatch(new NavigateRouteAction([route, action.payload.entry.id, 'permissions']));
|
||||
} else {
|
||||
this.store
|
||||
.select(getAppSelection)
|
||||
.pipe(take(1))
|
||||
.subscribe((selection) => {
|
||||
if (selection && !selection.isEmpty) {
|
||||
const route = 'personal-files/details';
|
||||
this.store.dispatch(new NavigateRouteAction([route, selection.first.entry.id, 'permissions']));
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
),
|
||||
{ dispatch: false }
|
||||
);
|
||||
|
||||
expandInfoDrawer$ = createEffect(
|
||||
() =>
|
||||
this.actions$.pipe(
|
||||
ofType<ExpandInfoDrawerAction>(NodeActionTypes.ExpandInfoDrawer),
|
||||
map((action) => {
|
||||
if (action?.payload) {
|
||||
const route = 'personal-files/details';
|
||||
this.store.dispatch(new NavigateRouteAction([route, action.payload.entry.id]));
|
||||
} else {
|
||||
this.store
|
||||
.select(getAppSelection)
|
||||
.pipe(take(1))
|
||||
.subscribe((selection) => {
|
||||
if (selection && !selection.isEmpty) {
|
||||
const route = 'personal-files/details';
|
||||
this.store.dispatch(new NavigateRouteAction([route, selection.first.entry.id]));
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
),
|
||||
{ dispatch: false }
|
||||
);
|
||||
|
||||
manageVersions$ = createEffect(
|
||||
() =>
|
||||
this.actions$.pipe(
|
||||
ofType<ManageVersionsAction>(NodeActionTypes.ManageVersions),
|
||||
map((action) => {
|
||||
if (action?.payload) {
|
||||
this.contentService.manageVersions(action.payload);
|
||||
} else {
|
||||
this.store
|
||||
.select(getAppSelection)
|
||||
.pipe(take(1))
|
||||
.subscribe((selection) => {
|
||||
if (selection && selection.file) {
|
||||
this.contentService.manageVersions(selection.file, action.configuration?.focusedElementOnCloseSelector);
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
),
|
||||
{ dispatch: false }
|
||||
);
|
||||
|
||||
printFile$ = createEffect(
|
||||
() =>
|
||||
this.actions$.pipe(
|
||||
ofType<PrintFileAction>(NodeActionTypes.PrintFile),
|
||||
map((action) => {
|
||||
if (action && action.payload) {
|
||||
this.printFile(action.payload);
|
||||
} else {
|
||||
this.store
|
||||
.select(getAppSelection)
|
||||
.pipe(take(1))
|
||||
.subscribe((selection) => {
|
||||
if (selection && selection.file) {
|
||||
this.printFile(selection.file);
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
),
|
||||
{ dispatch: false }
|
||||
);
|
||||
|
||||
unlockWrite$ = createEffect(
|
||||
() =>
|
||||
this.actions$.pipe(
|
||||
ofType<UnlockWriteAction>(NodeActionTypes.UnlockForWriting),
|
||||
map((action) => {
|
||||
if (action && action.payload) {
|
||||
this.contentService.unlockNode(action.payload);
|
||||
} else {
|
||||
this.store
|
||||
.select(getAppSelection)
|
||||
.pipe(take(1))
|
||||
.subscribe((selection) => {
|
||||
if (selection && selection.file) {
|
||||
this.contentService.unlockNode(selection.file);
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
),
|
||||
{ dispatch: false }
|
||||
);
|
||||
|
||||
aspectList$ = createEffect(
|
||||
() =>
|
||||
this.actions$.pipe(
|
||||
ofType<ManageAspectsAction>(NodeActionTypes.ChangeAspects),
|
||||
map((action) => {
|
||||
if (action?.payload) {
|
||||
this.contentService.manageAspects(action.payload);
|
||||
} else {
|
||||
this.store
|
||||
.select(getAppSelection)
|
||||
.pipe(take(1))
|
||||
.subscribe((selection) => {
|
||||
if (selection && !selection.isEmpty) {
|
||||
this.contentService.manageAspects(selection.nodes[0], action.configuration?.focusedElementOnCloseSelector);
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
),
|
||||
{ dispatch: false }
|
||||
);
|
||||
|
||||
printFile(node: any) {
|
||||
if (node && node.entry) {
|
||||
// shared and favorite
|
||||
const id = node.entry.nodeId || node.entry.guid || node.entry.id;
|
||||
const mimeType = node.entry.content.mimeType;
|
||||
|
||||
if (id) {
|
||||
this.viewUtils.printFileGeneric(id, mimeType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
manageRules$ = createEffect(
|
||||
() =>
|
||||
this.actions$.pipe(
|
||||
ofType<ManageRulesAction>(NodeActionTypes.ManageRules),
|
||||
map((action) => {
|
||||
if (action?.payload) {
|
||||
this.store.dispatch(new NavigateRouteAction(['nodes', action.payload.entry.id, 'rules']));
|
||||
} else {
|
||||
this.store
|
||||
.select(getAppSelection)
|
||||
.pipe(take(1))
|
||||
.subscribe((selection) => {
|
||||
if (selection && !selection.isEmpty) {
|
||||
this.store.dispatch(new NavigateRouteAction(['nodes', selection.first.entry.id, 'rules']));
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
),
|
||||
{ dispatch: false }
|
||||
);
|
||||
}
|
@@ -0,0 +1,81 @@
|
||||
/*!
|
||||
* @license
|
||||
* Alfresco Example Content Application
|
||||
*
|
||||
* Copyright (C) 2005 - 2020 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { TestBed, fakeAsync, tick } from '@angular/core/testing';
|
||||
import { AppTestingModule } from '../../testing/app-testing.module';
|
||||
import { SearchEffects } from './search.effects';
|
||||
import { EffectsModule } from '@ngrx/effects';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { Router } from '@angular/router';
|
||||
import { SearchOptionIds, SearchByTermAction } from '@alfresco/aca-shared/store';
|
||||
|
||||
describe('SearchEffects', () => {
|
||||
let store: Store<any>;
|
||||
let router: Router;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [AppTestingModule, EffectsModule.forRoot([SearchEffects])]
|
||||
});
|
||||
|
||||
store = TestBed.inject(Store);
|
||||
router = TestBed.inject(Router);
|
||||
|
||||
spyOn(router, 'navigateByUrl').and.stub();
|
||||
});
|
||||
|
||||
describe('searchByTerm$', () => {
|
||||
it('should navigate to `search` when search options has library false', fakeAsync(() => {
|
||||
store.dispatch(new SearchByTermAction('test', []));
|
||||
tick();
|
||||
expect(router.navigateByUrl).toHaveBeenCalledWith('/search;q=test');
|
||||
}));
|
||||
|
||||
it('should navigate to `search-libraries` when search options has library true', fakeAsync(() => {
|
||||
store.dispatch(
|
||||
new SearchByTermAction('test', [
|
||||
{
|
||||
id: SearchOptionIds.Libraries,
|
||||
value: true,
|
||||
key: '',
|
||||
shouldDisable: null
|
||||
}
|
||||
])
|
||||
);
|
||||
|
||||
tick();
|
||||
|
||||
expect(router.navigateByUrl).toHaveBeenCalledWith('/search-libraries;q=test');
|
||||
}));
|
||||
|
||||
it('should encode search string for parentheses', fakeAsync(() => {
|
||||
store.dispatch(new SearchByTermAction('(test)', []));
|
||||
|
||||
tick();
|
||||
|
||||
expect(router.navigateByUrl).toHaveBeenCalledWith('/search;q=%2528test%2529');
|
||||
}));
|
||||
});
|
||||
});
|
54
projects/aca-content/src/lib/store/effects/search.effects.ts
Normal file
54
projects/aca-content/src/lib/store/effects/search.effects.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
/*!
|
||||
* @license
|
||||
* Alfresco Example Content Application
|
||||
*
|
||||
* Copyright (C) 2005 - 2020 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Actions, ofType, createEffect } from '@ngrx/effects';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { map } from 'rxjs/operators';
|
||||
import { SearchActionTypes, SearchByTermAction, SearchOptionIds } from '@alfresco/aca-shared/store';
|
||||
import { Router } from '@angular/router';
|
||||
|
||||
@Injectable()
|
||||
export class SearchEffects {
|
||||
constructor(private actions$: Actions, private router: Router) {}
|
||||
|
||||
searchByTerm$ = createEffect(
|
||||
() =>
|
||||
this.actions$.pipe(
|
||||
ofType<SearchByTermAction>(SearchActionTypes.SearchByTerm),
|
||||
map((action) => {
|
||||
const query = action.payload.replace(/[(]/g, '%28').replace(/[)]/g, '%29');
|
||||
|
||||
const libItem = action.searchOptions.find((item) => item.id === SearchOptionIds.Libraries);
|
||||
const librarySelected = !!libItem && libItem.value;
|
||||
if (librarySelected) {
|
||||
this.router.navigateByUrl('/search-libraries;q=' + encodeURIComponent(query));
|
||||
} else {
|
||||
this.router.navigateByUrl('/search;q=' + encodeURIComponent(query));
|
||||
}
|
||||
})
|
||||
),
|
||||
{ dispatch: false }
|
||||
);
|
||||
}
|
@@ -0,0 +1,223 @@
|
||||
/*!
|
||||
* @license
|
||||
* Alfresco Example Content Application
|
||||
*
|
||||
* Copyright (C) 2005 - 2020 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { fakeAsync, TestBed, tick } from '@angular/core/testing';
|
||||
import { AppTestingModule } from '../../testing/app-testing.module';
|
||||
import { TemplateEffects } from './template.effects';
|
||||
import { EffectsModule } from '@ngrx/effects';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { CreateFromTemplate, CreateFromTemplateSuccess, FileFromTemplate, FolderFromTemplate, SnackbarErrorAction } from '@alfresco/aca-shared/store';
|
||||
import { NodeTemplateService } from '../../services/node-template.service';
|
||||
import { of, Subject } from 'rxjs';
|
||||
import { Node, NodeEntry } from '@alfresco/js-api';
|
||||
import { MatDialog, MatDialogRef } from '@angular/material/dialog';
|
||||
import { CreateFromTemplateDialogComponent } from '../../dialogs/node-template/create-from-template.dialog';
|
||||
import { AppHookService } from '@alfresco/aca-shared';
|
||||
|
||||
describe('TemplateEffects', () => {
|
||||
let store: Store<any>;
|
||||
let nodeTemplateService: NodeTemplateService;
|
||||
let appHookService: AppHookService;
|
||||
let templateEffects: TemplateEffects;
|
||||
let copyNodeSpy;
|
||||
let updateNodeSpy;
|
||||
let matDialog: MatDialog;
|
||||
const node: Node = {
|
||||
name: 'node-name',
|
||||
id: 'node-id',
|
||||
nodeType: 'cm:content',
|
||||
isFolder: false,
|
||||
isFile: true,
|
||||
modifiedAt: null,
|
||||
modifiedByUser: null,
|
||||
createdAt: null,
|
||||
createdByUser: null,
|
||||
properties: {
|
||||
'cm:title': 'title',
|
||||
'cm:description': 'description'
|
||||
}
|
||||
};
|
||||
const fileTemplateConfig = {
|
||||
primaryPathName: 'app:node_templates',
|
||||
selectionType: 'file'
|
||||
};
|
||||
|
||||
const folderTemplateConfig = {
|
||||
primaryPathName: 'app:space_templates',
|
||||
selectionType: 'folder'
|
||||
};
|
||||
|
||||
let subject: Subject<Node[]>;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [AppTestingModule, EffectsModule.forRoot([TemplateEffects])],
|
||||
providers: [
|
||||
NodeTemplateService,
|
||||
{
|
||||
provide: MatDialog,
|
||||
useValue: {
|
||||
closeAll: jasmine.createSpy('closeAll')
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
store = TestBed.inject(Store);
|
||||
nodeTemplateService = TestBed.inject(NodeTemplateService);
|
||||
templateEffects = TestBed.inject(TemplateEffects);
|
||||
appHookService = TestBed.inject(AppHookService);
|
||||
matDialog = TestBed.inject(MatDialog);
|
||||
subject = new Subject<Node[]>();
|
||||
|
||||
spyOn(store, 'dispatch').and.callThrough();
|
||||
spyOn(appHookService.reload, 'next');
|
||||
spyOn(store, 'select').and.returnValue(of({ id: 'parent-id' }));
|
||||
spyOn(nodeTemplateService, 'selectTemplateDialog').and.returnValue(subject);
|
||||
|
||||
copyNodeSpy = spyOn(templateEffects['nodesApi'], 'copyNode');
|
||||
updateNodeSpy = spyOn(templateEffects['nodesApi'], 'updateNode');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
copyNodeSpy.calls.reset();
|
||||
updateNodeSpy.calls.reset();
|
||||
});
|
||||
|
||||
it('should call createTemplateDialog on FileFromTemplate action', fakeAsync(() => {
|
||||
spyOn(nodeTemplateService, 'createTemplateDialog');
|
||||
store.dispatch(new FileFromTemplate());
|
||||
subject.next([node]);
|
||||
tick(300);
|
||||
|
||||
expect(nodeTemplateService.createTemplateDialog).toHaveBeenCalledWith(node);
|
||||
}));
|
||||
|
||||
it('should call createTemplateDialog on FolderFromTemplate action', fakeAsync(() => {
|
||||
spyOn(nodeTemplateService, 'createTemplateDialog');
|
||||
store.dispatch(new FolderFromTemplate());
|
||||
subject.next([node]);
|
||||
tick(300);
|
||||
|
||||
expect(nodeTemplateService.createTemplateDialog).toHaveBeenCalledWith(node);
|
||||
}));
|
||||
|
||||
it('should open dialog to select template files', fakeAsync(() => {
|
||||
spyOn(nodeTemplateService, 'createTemplateDialog').and.returnValue({
|
||||
afterClosed: () => of(node)
|
||||
} as MatDialogRef<CreateFromTemplateDialogComponent>);
|
||||
|
||||
store.dispatch(new FileFromTemplate());
|
||||
tick();
|
||||
|
||||
expect(nodeTemplateService.selectTemplateDialog).toHaveBeenCalledWith(fileTemplateConfig);
|
||||
}));
|
||||
|
||||
it('should open dialog to select template folders', fakeAsync(() => {
|
||||
spyOn(nodeTemplateService, 'createTemplateDialog').and.returnValue({
|
||||
afterClosed: () => of(node)
|
||||
} as MatDialogRef<CreateFromTemplateDialogComponent>);
|
||||
|
||||
store.dispatch(new FolderFromTemplate());
|
||||
tick();
|
||||
|
||||
expect(nodeTemplateService.selectTemplateDialog).toHaveBeenCalledWith(folderTemplateConfig);
|
||||
}));
|
||||
|
||||
it('should create node from template successful', fakeAsync(() => {
|
||||
copyNodeSpy.and.returnValue(of({ entry: { id: 'node-id', properties: {} } }));
|
||||
updateNodeSpy.and.returnValue(of({ entry: node }));
|
||||
|
||||
store.dispatch(new CreateFromTemplate(node));
|
||||
tick();
|
||||
|
||||
expect(store.dispatch['calls'].mostRecent().args[0]).toEqual(new CreateFromTemplateSuccess(node));
|
||||
}));
|
||||
|
||||
it('should raise generic error when copyNode api fails', fakeAsync(() => {
|
||||
copyNodeSpy.and.returnValue(
|
||||
Promise.reject({
|
||||
message: `{ "error": { "statusCode": 404 } } `
|
||||
})
|
||||
);
|
||||
|
||||
store.dispatch(new CreateFromTemplate(node));
|
||||
tick();
|
||||
|
||||
expect(store.dispatch['calls'].mostRecent().args[0]).not.toEqual(new CreateFromTemplateSuccess(node));
|
||||
expect(store.dispatch['calls'].argsFor(1)[0]).toEqual(new SnackbarErrorAction('APP.MESSAGES.ERRORS.GENERIC'));
|
||||
}));
|
||||
|
||||
it('should raise name conflict error when copyNode api returns 409', fakeAsync(() => {
|
||||
copyNodeSpy.and.returnValue(
|
||||
Promise.reject({
|
||||
message: `{ "error": { "statusCode": 409 } } `
|
||||
})
|
||||
);
|
||||
|
||||
store.dispatch(new CreateFromTemplate(node));
|
||||
tick();
|
||||
|
||||
expect(store.dispatch['calls'].mostRecent().args[0]).not.toEqual(new CreateFromTemplateSuccess(node));
|
||||
expect(store.dispatch['calls'].argsFor(1)[0]).toEqual(new SnackbarErrorAction('APP.MESSAGES.ERRORS.CONFLICT'));
|
||||
}));
|
||||
|
||||
it('should resolve error with current node value when updateNode api fails', fakeAsync(() => {
|
||||
const TEST_NODE = {
|
||||
entry: {
|
||||
id: 'test-node-id',
|
||||
properties: {
|
||||
'cm:title': 'test-node-title',
|
||||
'cm:description': 'test-node-description'
|
||||
}
|
||||
}
|
||||
} as NodeEntry;
|
||||
copyNodeSpy.and.returnValue(of(TEST_NODE));
|
||||
|
||||
updateNodeSpy.and.returnValue(
|
||||
Promise.reject({
|
||||
message: `{ "error": { "statusCode": 404 } } `
|
||||
})
|
||||
);
|
||||
|
||||
store.dispatch(new CreateFromTemplate(TEST_NODE.entry));
|
||||
tick();
|
||||
|
||||
expect(store.dispatch['calls'].mostRecent().args[0]).toEqual(new CreateFromTemplateSuccess(TEST_NODE.entry));
|
||||
}));
|
||||
|
||||
it('should close dialog on create template success', fakeAsync(() => {
|
||||
store.dispatch(new CreateFromTemplateSuccess({} as Node));
|
||||
tick();
|
||||
expect(matDialog.closeAll).toHaveBeenCalled();
|
||||
}));
|
||||
|
||||
it('should should reload content on create template success', fakeAsync(() => {
|
||||
const TEST_NODE = { id: 'test-node-id' } as Node;
|
||||
store.dispatch(new CreateFromTemplateSuccess(TEST_NODE));
|
||||
tick();
|
||||
expect(appHookService.reload.next).toHaveBeenCalledWith(TEST_NODE);
|
||||
}));
|
||||
});
|
172
projects/aca-content/src/lib/store/effects/template.effects.ts
Normal file
172
projects/aca-content/src/lib/store/effects/template.effects.ts
Normal file
@@ -0,0 +1,172 @@
|
||||
/*!
|
||||
* @license
|
||||
* Alfresco Example Content Application
|
||||
*
|
||||
* Copyright (C) 2005 - 2020 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Actions, ofType, createEffect } from '@ngrx/effects';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { map, switchMap, debounceTime, take, catchError } from 'rxjs/operators';
|
||||
import { Store } from '@ngrx/store';
|
||||
import {
|
||||
FileFromTemplate,
|
||||
FolderFromTemplate,
|
||||
CreateFromTemplate,
|
||||
CreateFromTemplateSuccess,
|
||||
TemplateActionTypes,
|
||||
getCurrentFolder,
|
||||
AppStore,
|
||||
SnackbarErrorAction
|
||||
} from '@alfresco/aca-shared/store';
|
||||
import { NodeTemplateService, TemplateDialogConfig } from '../../services/node-template.service';
|
||||
import { AlfrescoApiService } from '@alfresco/adf-core';
|
||||
import { AppHookService } from '@alfresco/aca-shared';
|
||||
import { from, Observable, of } from 'rxjs';
|
||||
import { NodeEntry, NodeBodyUpdate, Node, NodesApi } from '@alfresco/js-api';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
|
||||
@Injectable()
|
||||
export class TemplateEffects {
|
||||
private _nodesApi: NodesApi;
|
||||
get nodesApi(): NodesApi {
|
||||
this._nodesApi = this._nodesApi ?? new NodesApi(this.apiService.getInstance());
|
||||
return this._nodesApi;
|
||||
}
|
||||
|
||||
constructor(
|
||||
private matDialog: MatDialog,
|
||||
private appHookService: AppHookService,
|
||||
private store: Store<AppStore>,
|
||||
private apiService: AlfrescoApiService,
|
||||
private actions$: Actions,
|
||||
private nodeTemplateService: NodeTemplateService
|
||||
) {}
|
||||
|
||||
fileFromTemplate$ = createEffect(
|
||||
() =>
|
||||
this.actions$.pipe(
|
||||
ofType<FileFromTemplate>(TemplateActionTypes.FileFromTemplate),
|
||||
map(() => {
|
||||
this.openDialog({
|
||||
primaryPathName: 'app:node_templates',
|
||||
selectionType: 'file'
|
||||
});
|
||||
})
|
||||
),
|
||||
{ dispatch: false }
|
||||
);
|
||||
|
||||
folderFromTemplate$ = createEffect(
|
||||
() =>
|
||||
this.actions$.pipe(
|
||||
ofType<FolderFromTemplate>(TemplateActionTypes.FolderFromTemplate),
|
||||
map(() =>
|
||||
this.openDialog({
|
||||
primaryPathName: 'app:space_templates',
|
||||
selectionType: 'folder'
|
||||
})
|
||||
)
|
||||
),
|
||||
{ dispatch: false }
|
||||
);
|
||||
|
||||
createFromTemplate$ = createEffect(
|
||||
() =>
|
||||
this.actions$.pipe(
|
||||
ofType<CreateFromTemplate>(TemplateActionTypes.CreateFromTemplate),
|
||||
map((action) => {
|
||||
this.store
|
||||
.select(getCurrentFolder)
|
||||
.pipe(
|
||||
switchMap((folder) => this.copyNode(action.payload, folder.id)),
|
||||
take(1)
|
||||
)
|
||||
.subscribe((node: NodeEntry | null) => {
|
||||
if (node) {
|
||||
this.store.dispatch(new CreateFromTemplateSuccess(node.entry));
|
||||
}
|
||||
});
|
||||
})
|
||||
),
|
||||
{ dispatch: false }
|
||||
);
|
||||
|
||||
createFromTemplateSuccess$ = createEffect(
|
||||
() =>
|
||||
this.actions$.pipe(
|
||||
ofType<CreateFromTemplateSuccess>(TemplateActionTypes.CreateFromTemplateSuccess),
|
||||
map((payload) => {
|
||||
this.matDialog.closeAll();
|
||||
this.appHookService.reload.next(payload.node);
|
||||
})
|
||||
),
|
||||
{ dispatch: false }
|
||||
);
|
||||
|
||||
private openDialog(config: TemplateDialogConfig) {
|
||||
this.nodeTemplateService
|
||||
.selectTemplateDialog(config)
|
||||
.pipe(debounceTime(300))
|
||||
.subscribe(([node]) => this.nodeTemplateService.createTemplateDialog(node));
|
||||
}
|
||||
|
||||
private copyNode(source: Node, parentId: string): Observable<NodeEntry> {
|
||||
return from(
|
||||
this.nodesApi.copyNode(source.id, {
|
||||
targetParentId: parentId,
|
||||
name: source.name
|
||||
})
|
||||
).pipe(
|
||||
switchMap((node) =>
|
||||
this.updateNode(node, {
|
||||
properties: {
|
||||
'cm:title': source.properties['cm:title'],
|
||||
'cm:description': source.properties['cm:description']
|
||||
}
|
||||
})
|
||||
),
|
||||
catchError((error) => this.handleError(error))
|
||||
);
|
||||
}
|
||||
|
||||
private updateNode(node: NodeEntry, update: NodeBodyUpdate): Observable<NodeEntry> {
|
||||
return from(this.nodesApi.updateNode(node.entry.id, update)).pipe(catchError(() => of(node)));
|
||||
}
|
||||
|
||||
private handleError(error: Error): Observable<null> {
|
||||
let statusCode: number;
|
||||
|
||||
try {
|
||||
statusCode = JSON.parse(error.message).error.statusCode;
|
||||
} catch (e) {
|
||||
statusCode = null;
|
||||
}
|
||||
|
||||
if (statusCode !== 409) {
|
||||
this.store.dispatch(new SnackbarErrorAction('APP.MESSAGES.ERRORS.GENERIC'));
|
||||
} else {
|
||||
this.store.dispatch(new SnackbarErrorAction('APP.MESSAGES.ERRORS.CONFLICT'));
|
||||
}
|
||||
|
||||
return of(null);
|
||||
}
|
||||
}
|
@@ -0,0 +1,286 @@
|
||||
/*!
|
||||
* @license
|
||||
* Alfresco Example Content Application
|
||||
*
|
||||
* Copyright (C) 2005 - 2020 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Store } from '@ngrx/store';
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import { EffectsModule } from '@ngrx/effects';
|
||||
import { UploadEffects } from './upload.effects';
|
||||
import { AppTestingModule } from '../../testing/app-testing.module';
|
||||
import { NgZone } from '@angular/core';
|
||||
import { UploadService, FileUploadCompleteEvent, FileModel } from '@alfresco/adf-core';
|
||||
import { UnlockWriteAction, UploadFilesAction, UploadFileVersionAction, UploadFolderAction } from '@alfresco/aca-shared/store';
|
||||
import { ContentManagementService } from '../../services/content-management.service';
|
||||
|
||||
describe('UploadEffects', () => {
|
||||
let store: Store<any>;
|
||||
let uploadService: UploadService;
|
||||
let effects: UploadEffects;
|
||||
let zone: NgZone;
|
||||
let contentManagementService: ContentManagementService;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [AppTestingModule, EffectsModule.forRoot([UploadEffects])]
|
||||
});
|
||||
|
||||
zone = TestBed.inject(NgZone);
|
||||
spyOn(zone, 'run').and.callFake((fn: () => any) => fn());
|
||||
|
||||
contentManagementService = TestBed.inject(ContentManagementService);
|
||||
store = TestBed.inject(Store);
|
||||
uploadService = TestBed.inject(UploadService);
|
||||
effects = TestBed.inject(UploadEffects);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
spyOn(effects['fileVersionInput'], 'click');
|
||||
spyOn(effects, 'uploadVersion').and.callThrough();
|
||||
});
|
||||
|
||||
describe('uploadFiles$', () => {
|
||||
let createMenuButton: HTMLButtonElement;
|
||||
const focusedClass = 'cdk-program-focused';
|
||||
|
||||
beforeEach(() => {
|
||||
createMenuButton = document.createElement('button');
|
||||
document.body.appendChild(createMenuButton);
|
||||
store.dispatch(new UploadFilesAction({}));
|
||||
spyOn(document, 'querySelector').withArgs('app-create-menu button').and.returnValue(createMenuButton);
|
||||
});
|
||||
|
||||
it('should call focus function on create menu button', () => {
|
||||
spyOn(createMenuButton, 'focus');
|
||||
window.dispatchEvent(new FocusEvent('focus'));
|
||||
expect(createMenuButton.focus).toHaveBeenCalledWith();
|
||||
});
|
||||
|
||||
it('should not call focus function on create menu button if handler for focus of window is not fired', () => {
|
||||
spyOn(createMenuButton, 'focus');
|
||||
expect(createMenuButton.focus).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should add cdk-program-focused class to create menu button', () => {
|
||||
window.dispatchEvent(new FocusEvent('focus'));
|
||||
createMenuButton.dispatchEvent(new FocusEvent('focus'));
|
||||
expect(createMenuButton).toHaveClass(focusedClass);
|
||||
});
|
||||
|
||||
it('should not add cdk-program-focused class to create menu button if handler for focus of window is not fired', () => {
|
||||
expect(createMenuButton).not.toHaveClass(focusedClass);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
createMenuButton.remove();
|
||||
});
|
||||
});
|
||||
|
||||
describe('uploadFolder$', () => {
|
||||
let createMenuButton: HTMLButtonElement;
|
||||
const focusedClass = 'cdk-program-focused';
|
||||
|
||||
beforeEach(() => {
|
||||
createMenuButton = document.createElement('button');
|
||||
document.body.appendChild(createMenuButton);
|
||||
store.dispatch(new UploadFolderAction({}));
|
||||
spyOn(document, 'querySelector').withArgs('app-create-menu button').and.returnValue(createMenuButton);
|
||||
});
|
||||
|
||||
it('should call focus function on create menu button', () => {
|
||||
spyOn(createMenuButton, 'focus');
|
||||
window.dispatchEvent(new FocusEvent('focus'));
|
||||
expect(createMenuButton.focus).toHaveBeenCalledWith();
|
||||
});
|
||||
|
||||
it('should not call focus function on create menu button if handler for focus of window is not fired', () => {
|
||||
spyOn(createMenuButton, 'focus');
|
||||
expect(createMenuButton.focus).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should add cdk-program-focused class to create menu button', () => {
|
||||
window.dispatchEvent(new FocusEvent('focus'));
|
||||
createMenuButton.dispatchEvent(new FocusEvent('focus'));
|
||||
expect(createMenuButton).toHaveClass(focusedClass);
|
||||
});
|
||||
|
||||
it('should not add cdk-program-focused class to create menu button if handler for focus of window is not fired', () => {
|
||||
expect(createMenuButton).not.toHaveClass(focusedClass);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
createMenuButton.remove();
|
||||
});
|
||||
});
|
||||
|
||||
describe('uploadAndUnlock()', () => {
|
||||
it('should not upload and unlock file if param not provided', () => {
|
||||
effects.uploadAndUnlock(null);
|
||||
expect(zone.run).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should upload the file before unlocking', () => {
|
||||
const file: any = {};
|
||||
|
||||
spyOn(uploadService, 'addToQueue').and.stub();
|
||||
spyOn(uploadService, 'uploadFilesInTheQueue').and.stub();
|
||||
|
||||
effects.uploadAndUnlock(file);
|
||||
|
||||
expect(uploadService.addToQueue).toHaveBeenCalled();
|
||||
expect(uploadService.uploadFilesInTheQueue).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should dispatch the unlock write action for a locked file', () => {
|
||||
const file: FileModel = new FileModel({ name: 'file1.png', size: 10 } as File, null, 'file1');
|
||||
|
||||
file.data = {
|
||||
entry: {
|
||||
id: 'file1',
|
||||
properties: {
|
||||
'cm:lockType': 'WRITE_LOCK'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
spyOn(uploadService, 'addToQueue').and.stub();
|
||||
spyOn(uploadService, 'uploadFilesInTheQueue').and.stub();
|
||||
spyOn(store, 'dispatch').and.stub();
|
||||
|
||||
effects.uploadAndUnlock(file);
|
||||
uploadService.fileUploadComplete.next(new FileUploadCompleteEvent(file, 100, file.data));
|
||||
|
||||
expect(store.dispatch).toHaveBeenCalledWith(new UnlockWriteAction(file.data));
|
||||
});
|
||||
|
||||
it('should dispatch only one unlock action for a locked file', () => {
|
||||
const file: FileModel = new FileModel({ name: 'file1.png', size: 10 } as File, null, 'file1');
|
||||
|
||||
file.data = {
|
||||
entry: {
|
||||
id: 'file1',
|
||||
properties: {
|
||||
'cm:lockType': 'WRITE_LOCK'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
spyOn(uploadService, 'addToQueue').and.stub();
|
||||
spyOn(uploadService, 'uploadFilesInTheQueue').and.stub();
|
||||
spyOn(store, 'dispatch').and.stub();
|
||||
|
||||
effects.uploadAndUnlock(file);
|
||||
|
||||
const completeEvent = new FileUploadCompleteEvent(file, 100, file.data);
|
||||
uploadService.fileUploadComplete.next(completeEvent);
|
||||
uploadService.fileUploadComplete.next(completeEvent);
|
||||
uploadService.fileUploadComplete.next(completeEvent);
|
||||
|
||||
expect(store.dispatch).toHaveBeenCalledWith(new UnlockWriteAction(file.data));
|
||||
|
||||
expect(store.dispatch).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should dispatch no actions if file is not locked', () => {
|
||||
const file: FileModel = new FileModel({ name: 'file1.png', size: 10 } as File, null, 'file1');
|
||||
|
||||
file.data = {
|
||||
entry: {
|
||||
id: 'file1',
|
||||
properties: {}
|
||||
}
|
||||
};
|
||||
|
||||
spyOn(uploadService, 'addToQueue').and.stub();
|
||||
spyOn(uploadService, 'uploadFilesInTheQueue').and.stub();
|
||||
spyOn(store, 'dispatch').and.stub();
|
||||
|
||||
effects.uploadAndUnlock(file);
|
||||
uploadService.fileUploadComplete.next(new FileUploadCompleteEvent(file, 100, file.data));
|
||||
|
||||
expect(store.dispatch).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('upload file version', () => {
|
||||
it('should trigger upload file from context menu', () => {
|
||||
store.dispatch({ type: 'UPLOAD_FILE_VERSION' });
|
||||
expect(effects['fileVersionInput'].click).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should upload file from dropping another file', () => {
|
||||
spyOn(contentManagementService, 'versionUpdateDialog');
|
||||
const fakeEvent = new CustomEvent('upload-files', {
|
||||
detail: {
|
||||
files: [
|
||||
{
|
||||
file: new FileModel({
|
||||
name: 'Fake New file',
|
||||
type: 'image/png',
|
||||
lastModified: 1589273450599,
|
||||
size: 1351,
|
||||
slice: null
|
||||
} as File),
|
||||
entry: new FileModel({
|
||||
name: 'Fake New file',
|
||||
type: 'image/png',
|
||||
lastModified: 1589273450599,
|
||||
size: 1351,
|
||||
slice: null
|
||||
} as File)
|
||||
}
|
||||
],
|
||||
data: {
|
||||
node: {
|
||||
entry: {
|
||||
isFile: true,
|
||||
createdByUser: {
|
||||
id: 'admin.adf@alfresco.com',
|
||||
displayName: 'Administrator'
|
||||
},
|
||||
modifiedAt: '2020-06-09T08:13:40.569Z',
|
||||
nodeType: 'cm:content',
|
||||
content: {
|
||||
mimeType: 'image/jpeg',
|
||||
mimeTypeName: 'JPEG Image',
|
||||
sizeInBytes: 175540,
|
||||
encoding: 'UTF-8'
|
||||
},
|
||||
parentId: 'dff2bc1e-d092-42ac-82d1-87c82f6e56cb',
|
||||
createdAt: '2020-05-14T08:52:03.868Z',
|
||||
isFolder: false,
|
||||
name: 'GoqZhm.jpg',
|
||||
id: '1bf8a8f7-18ac-4eef-919d-61d952eaa179',
|
||||
allowableOperations: ['delete', 'update', 'updatePermissions'],
|
||||
isFavorite: false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
store.dispatch(new UploadFileVersionAction(fakeEvent));
|
||||
|
||||
expect(contentManagementService.versionUpdateDialog).toHaveBeenCalledWith(fakeEvent.detail.data.node.entry, fakeEvent.detail.files[0].file);
|
||||
});
|
||||
});
|
||||
});
|
227
projects/aca-content/src/lib/store/effects/upload.effects.ts
Normal file
227
projects/aca-content/src/lib/store/effects/upload.effects.ts
Normal file
@@ -0,0 +1,227 @@
|
||||
/*!
|
||||
* @license
|
||||
* Alfresco Example Content Application
|
||||
*
|
||||
* Copyright (C) 2005 - 2020 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {
|
||||
AppStore,
|
||||
SnackbarErrorAction,
|
||||
UnlockWriteAction,
|
||||
UploadActionTypes,
|
||||
UploadFilesAction,
|
||||
UploadFileVersionAction,
|
||||
UploadFolderAction,
|
||||
getCurrentFolder
|
||||
} from '@alfresco/aca-shared/store';
|
||||
import { FileModel, FileUtils, UploadService } from '@alfresco/adf-core';
|
||||
import { Injectable, NgZone, RendererFactory2 } from '@angular/core';
|
||||
import { Actions, ofType, createEffect } from '@ngrx/effects';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { of } from 'rxjs';
|
||||
import { catchError, map, take } from 'rxjs/operators';
|
||||
import { ContentManagementService } from '../../services/content-management.service';
|
||||
import { MinimalNodeEntryEntity } from '@alfresco/js-api';
|
||||
|
||||
@Injectable()
|
||||
export class UploadEffects {
|
||||
private fileInput: HTMLInputElement;
|
||||
private folderInput: HTMLInputElement;
|
||||
private fileVersionInput: HTMLInputElement;
|
||||
private readonly createMenuButtonSelector = 'app-create-menu button';
|
||||
|
||||
constructor(
|
||||
private store: Store<AppStore>,
|
||||
private actions$: Actions,
|
||||
private ngZone: NgZone,
|
||||
private uploadService: UploadService,
|
||||
rendererFactory: RendererFactory2,
|
||||
private contentService: ContentManagementService
|
||||
) {
|
||||
const renderer = rendererFactory.createRenderer(null, null);
|
||||
|
||||
this.fileInput = renderer.createElement('input') as HTMLInputElement;
|
||||
this.fileInput.id = 'app-upload-files';
|
||||
this.fileInput.type = 'file';
|
||||
this.fileInput.style.display = 'none';
|
||||
this.fileInput.setAttribute('multiple', '');
|
||||
this.fileInput.addEventListener('change', (event) => this.upload(event));
|
||||
renderer.appendChild(document.body, this.fileInput);
|
||||
|
||||
this.fileVersionInput = renderer.createElement('input') as HTMLInputElement;
|
||||
this.fileVersionInput.id = 'app-upload-file-version';
|
||||
this.fileVersionInput.type = 'file';
|
||||
this.fileVersionInput.style.display = 'none';
|
||||
this.fileVersionInput.addEventListener('change', () => {
|
||||
this.uploadVersion();
|
||||
});
|
||||
renderer.appendChild(document.body, this.fileVersionInput);
|
||||
|
||||
this.folderInput = renderer.createElement('input') as HTMLInputElement;
|
||||
this.folderInput.id = 'app-upload-folder';
|
||||
this.folderInput.type = 'file';
|
||||
this.folderInput.style.display = 'none';
|
||||
this.folderInput.setAttribute('directory', '');
|
||||
this.folderInput.setAttribute('webkitdirectory', '');
|
||||
this.folderInput.addEventListener('change', (event) => this.upload(event));
|
||||
renderer.appendChild(document.body, this.folderInput);
|
||||
}
|
||||
|
||||
uploadFiles$ = createEffect(
|
||||
() =>
|
||||
this.actions$.pipe(
|
||||
ofType<UploadFilesAction>(UploadActionTypes.UploadFiles),
|
||||
map(() => {
|
||||
this.registerFocusingElementAfterModalClose(this.fileInput, this.createMenuButtonSelector);
|
||||
this.fileInput.click();
|
||||
})
|
||||
),
|
||||
{ dispatch: false }
|
||||
);
|
||||
|
||||
uploadFolder$ = createEffect(
|
||||
() =>
|
||||
this.actions$.pipe(
|
||||
ofType<UploadFolderAction>(UploadActionTypes.UploadFolder),
|
||||
map(() => {
|
||||
this.registerFocusingElementAfterModalClose(this.folderInput, this.createMenuButtonSelector);
|
||||
this.folderInput.click();
|
||||
})
|
||||
),
|
||||
{ dispatch: false }
|
||||
);
|
||||
|
||||
uploadVersion$ = createEffect(
|
||||
() =>
|
||||
this.actions$.pipe(
|
||||
ofType<UploadFileVersionAction>(UploadActionTypes.UploadFileVersion),
|
||||
map((action) => {
|
||||
if (action?.payload) {
|
||||
const node = action?.payload?.detail?.data?.node?.entry;
|
||||
const file: any = action?.payload?.detail?.files[0]?.file;
|
||||
this.contentService.versionUpdateDialog(node, file);
|
||||
} else if (!action?.payload) {
|
||||
this.registerFocusingElementAfterModalClose(this.fileVersionInput, action.configuration?.focusedElementOnCloseSelector);
|
||||
this.fileVersionInput.click();
|
||||
}
|
||||
})
|
||||
),
|
||||
{ dispatch: false }
|
||||
);
|
||||
|
||||
uploadVersion() {
|
||||
this.contentService
|
||||
.getNodeInfo()
|
||||
.pipe(
|
||||
catchError((_) => {
|
||||
this.store.dispatch(new SnackbarErrorAction('VERSION.ERROR.GENERIC'));
|
||||
return of(null);
|
||||
})
|
||||
)
|
||||
.subscribe((node: MinimalNodeEntryEntity) => {
|
||||
if (node) {
|
||||
this.contentService.versionUpdateDialog(node, this.fileVersionInput.files[0]);
|
||||
this.fileVersionInput.value = '';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private upload(event: any): void {
|
||||
this.store
|
||||
.select(getCurrentFolder)
|
||||
.pipe(take(1))
|
||||
.subscribe((node) => {
|
||||
if (node && node.id) {
|
||||
const input = event.currentTarget as HTMLInputElement;
|
||||
const files = FileUtils.toFileArray(input.files).map(
|
||||
(file: any) =>
|
||||
new FileModel(file, {
|
||||
parentId: node.id,
|
||||
path: (file.webkitRelativePath || '').replace(/\/[^\/]*$/, ''),
|
||||
nodeType: 'cm:content'
|
||||
})
|
||||
);
|
||||
|
||||
this.uploadQueue(files);
|
||||
event.target.value = '';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private uploadQueue(files: FileModel[]) {
|
||||
if (files.length > 0) {
|
||||
this.ngZone.run(() => {
|
||||
this.uploadService.addToQueue(...files);
|
||||
this.uploadService.uploadFilesInTheQueue();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
uploadAndUnlock(file: FileModel | null) {
|
||||
if (!file) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.ngZone.run(() => {
|
||||
this.uploadService.addToQueue(file);
|
||||
this.uploadService.uploadFilesInTheQueue();
|
||||
|
||||
const subscription = this.uploadService.fileUploadComplete.subscribe((completed) => {
|
||||
if (
|
||||
file.data &&
|
||||
file.data.entry &&
|
||||
file.data.entry.properties &&
|
||||
file.data.entry.properties['cm:lockType'] === 'WRITE_LOCK' &&
|
||||
file.data.entry.id === completed.data.entry.id
|
||||
) {
|
||||
this.store.dispatch(new UnlockWriteAction(completed.data));
|
||||
}
|
||||
|
||||
subscription.unsubscribe();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private registerFocusingElementAfterModalClose(input: HTMLInputElement, focusedElementSelector: string): void {
|
||||
input.addEventListener(
|
||||
'click',
|
||||
() => {
|
||||
window.addEventListener(
|
||||
'focus',
|
||||
() => {
|
||||
const elementToFocus = document.querySelector<HTMLElement>(focusedElementSelector);
|
||||
elementToFocus.addEventListener('focus', () => elementToFocus.classList.add('cdk-program-focused'), {
|
||||
once: true
|
||||
});
|
||||
elementToFocus.focus();
|
||||
},
|
||||
{
|
||||
once: true
|
||||
}
|
||||
);
|
||||
},
|
||||
{
|
||||
once: true
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
@@ -0,0 +1,91 @@
|
||||
/*!
|
||||
* @license
|
||||
* Alfresco Example Content Application
|
||||
*
|
||||
* Copyright (C) 2005 - 2020 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { TestBed, fakeAsync, tick } from '@angular/core/testing';
|
||||
import { AppTestingModule } from '../../testing/app-testing.module';
|
||||
import { ViewerEffects } from './viewer.effects';
|
||||
import { EffectsModule } from '@ngrx/effects';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { Router } from '@angular/router';
|
||||
import { ViewFileAction, ViewNodeAction, SetSelectedNodesAction, SetCurrentFolderAction } from '@alfresco/aca-shared/store';
|
||||
|
||||
describe('ViewerEffects', () => {
|
||||
let store: Store<any>;
|
||||
let router: Router;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [AppTestingModule, EffectsModule.forRoot([ViewerEffects])]
|
||||
});
|
||||
|
||||
store = TestBed.inject(Store);
|
||||
router = TestBed.inject(Router);
|
||||
|
||||
spyOn(router, 'navigateByUrl').and.stub();
|
||||
});
|
||||
|
||||
describe('ViewFile', () => {
|
||||
it('should preview file from store selection', fakeAsync(() => {
|
||||
const node: any = { entry: { isFile: true, id: 'someId' } };
|
||||
const folder: any = { isFolder: true, id: 'folder1' };
|
||||
store.dispatch(new SetCurrentFolderAction(folder));
|
||||
store.dispatch(new SetSelectedNodesAction([node]));
|
||||
tick(100);
|
||||
|
||||
store.dispatch(new ViewFileAction());
|
||||
tick(100);
|
||||
expect(router.navigateByUrl).toHaveBeenCalledWith('/folder1/preview/someId');
|
||||
}));
|
||||
it('should preview file from payload', fakeAsync(() => {
|
||||
const node: any = { entry: { isFile: true, id: 'someId' } };
|
||||
store.dispatch(new ViewFileAction(node));
|
||||
tick(100);
|
||||
expect(router.navigateByUrl).toHaveBeenCalledWith('/preview/someId');
|
||||
}));
|
||||
});
|
||||
|
||||
describe('ViewNode', () => {
|
||||
it('should open viewer from file location if', fakeAsync(() => {
|
||||
store.dispatch(new ViewNodeAction('nodeId', { location: 'some-location' }));
|
||||
tick(100);
|
||||
|
||||
expect(router.navigateByUrl['calls'].argsFor(0)[0].toString()).toEqual('/some-location/(viewer:view/nodeId)?location=some-location');
|
||||
}));
|
||||
|
||||
it('should navigate to viewer route if no location is passed', fakeAsync(() => {
|
||||
store.dispatch(new ViewNodeAction('nodeId'));
|
||||
tick(100);
|
||||
|
||||
expect(router.navigateByUrl['calls'].argsFor(0)[0].toString()).toEqual('/view/(viewer:nodeId)');
|
||||
}));
|
||||
|
||||
it('should navigate to viewer route with query param if path is passed', fakeAsync(() => {
|
||||
store.dispatch(new ViewNodeAction('nodeId', { path: 'absolute-path' }));
|
||||
tick(100);
|
||||
|
||||
expect(router.navigateByUrl['calls'].argsFor(0)[0].toString()).toEqual('/view/(viewer:nodeId)?path=absolute-path');
|
||||
}));
|
||||
});
|
||||
});
|
226
projects/aca-content/src/lib/store/effects/viewer.effects.ts
Normal file
226
projects/aca-content/src/lib/store/effects/viewer.effects.ts
Normal file
@@ -0,0 +1,226 @@
|
||||
/*!
|
||||
* @license
|
||||
* Alfresco Example Content Application
|
||||
*
|
||||
* Copyright (C) 2005 - 2020 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Actions, ofType, createEffect } from '@ngrx/effects';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { map, take, tap } from 'rxjs/operators';
|
||||
import {
|
||||
AppStore,
|
||||
ViewerActionTypes,
|
||||
ViewFileAction,
|
||||
ViewNodeAction,
|
||||
getCurrentFolder,
|
||||
getAppSelection,
|
||||
FullscreenViewerAction,
|
||||
ViewNodeVersionAction,
|
||||
PluginPreviewAction
|
||||
} from '@alfresco/aca-shared/store';
|
||||
import { Router, UrlTree, UrlSegmentGroup, PRIMARY_OUTLET, UrlSegment } from '@angular/router';
|
||||
import { Store, createSelector } from '@ngrx/store';
|
||||
import { AppExtensionService } from '@alfresco/aca-shared';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
|
||||
export const fileToPreview = createSelector(getAppSelection, getCurrentFolder, (selection, folder) => ({
|
||||
selection,
|
||||
folder
|
||||
}));
|
||||
|
||||
@Injectable()
|
||||
export class ViewerEffects {
|
||||
constructor(
|
||||
private store: Store<AppStore>,
|
||||
private actions$: Actions,
|
||||
private router: Router,
|
||||
private extensions: AppExtensionService,
|
||||
private dialog: MatDialog
|
||||
) {}
|
||||
|
||||
fullscreenViewer$ = createEffect(
|
||||
() =>
|
||||
this.actions$.pipe(
|
||||
ofType<FullscreenViewerAction>(ViewerActionTypes.FullScreen),
|
||||
map(() => {
|
||||
this.enterFullScreen();
|
||||
})
|
||||
),
|
||||
{ dispatch: false }
|
||||
);
|
||||
|
||||
viewNode$ = createEffect(
|
||||
() =>
|
||||
this.actions$.pipe(
|
||||
ofType<ViewNodeAction>(ViewerActionTypes.ViewNode),
|
||||
map((action) => {
|
||||
if (action.viewNodeExtras) {
|
||||
const { location, path } = action.viewNodeExtras;
|
||||
|
||||
if (location) {
|
||||
const navigation = this.getNavigationCommands(location);
|
||||
|
||||
this.router.navigate([...navigation, { outlets: { viewer: ['view', action.nodeId] } }], {
|
||||
queryParams: { location }
|
||||
});
|
||||
}
|
||||
|
||||
if (path) {
|
||||
this.router.navigate(['view', { outlets: { viewer: [action.nodeId] } }], {
|
||||
queryParams: { path }
|
||||
});
|
||||
}
|
||||
} else {
|
||||
this.router.navigate(['view', { outlets: { viewer: [action.nodeId] } }]);
|
||||
}
|
||||
})
|
||||
),
|
||||
{ dispatch: false }
|
||||
);
|
||||
|
||||
viewFile$ = createEffect(
|
||||
() =>
|
||||
this.actions$.pipe(
|
||||
ofType<ViewFileAction>(ViewerActionTypes.ViewFile),
|
||||
map((action) => {
|
||||
if (action.payload && action.payload.entry) {
|
||||
const { id, nodeId, isFile } = action.payload.entry as any;
|
||||
|
||||
if (this.extensions.canPreviewNode(action.payload) && (isFile || nodeId)) {
|
||||
this.displayPreview(nodeId || id, action.parentId);
|
||||
}
|
||||
} else {
|
||||
this.store
|
||||
.select(fileToPreview)
|
||||
.pipe(take(1))
|
||||
.subscribe((result) => {
|
||||
if (result.selection && result.selection.file) {
|
||||
const { id, nodeId, isFile } = result.selection.file.entry as any;
|
||||
|
||||
if (this.extensions.canPreviewNode(action.payload) && (isFile || nodeId)) {
|
||||
const parentId = result.folder ? result.folder.id : null;
|
||||
this.displayPreview(nodeId || id, parentId);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
),
|
||||
{ dispatch: false }
|
||||
);
|
||||
|
||||
viewNodeVersion$ = createEffect(
|
||||
() =>
|
||||
this.actions$.pipe(
|
||||
ofType<ViewNodeVersionAction>(ViewerActionTypes.ViewNodeVersion),
|
||||
map((action) => {
|
||||
this.dialog.closeAll();
|
||||
if (action.viewNodeExtras) {
|
||||
const { location, path } = action.viewNodeExtras;
|
||||
if (location) {
|
||||
const navigation = this.getNavigationCommands(location);
|
||||
this.router.navigate([...navigation, { outlets: { viewer: ['view', action.nodeId, action.versionId] } }], {
|
||||
queryParams: { location }
|
||||
});
|
||||
}
|
||||
|
||||
if (path) {
|
||||
this.router.navigate(['view', { outlets: { viewer: [action.nodeId, action.versionId] } }], {
|
||||
queryParams: { path }
|
||||
});
|
||||
}
|
||||
} else {
|
||||
this.router.navigate(['view', { outlets: { viewer: [action.nodeId, action.versionId] } }]);
|
||||
}
|
||||
})
|
||||
),
|
||||
{ dispatch: false }
|
||||
);
|
||||
|
||||
pluginPreview$ = createEffect(
|
||||
() =>
|
||||
this.actions$.pipe(
|
||||
ofType<PluginPreviewAction>(ViewerActionTypes.PluginPreview),
|
||||
tap((action) => {
|
||||
this.router.navigate([
|
||||
action.pluginRoute,
|
||||
{
|
||||
outlets: {
|
||||
viewer: ['preview', action.nodeId]
|
||||
}
|
||||
}
|
||||
]);
|
||||
})
|
||||
),
|
||||
{ dispatch: false }
|
||||
);
|
||||
|
||||
private displayPreview(nodeId: string, parentId: string) {
|
||||
if (!nodeId) {
|
||||
return;
|
||||
}
|
||||
|
||||
let previewLocation = this.router.url;
|
||||
if (previewLocation.lastIndexOf('/') > 0) {
|
||||
previewLocation = previewLocation.substr(0, this.router.url.indexOf('/', 1));
|
||||
}
|
||||
previewLocation = previewLocation.replace(/\//g, '');
|
||||
|
||||
const path = [previewLocation];
|
||||
if (parentId) {
|
||||
path.push(parentId);
|
||||
}
|
||||
path.push('preview', nodeId);
|
||||
this.router.navigateByUrl(path.join('/'));
|
||||
}
|
||||
|
||||
enterFullScreen() {
|
||||
const container: any = document.documentElement.querySelector('.adf-viewer__fullscreen-container');
|
||||
if (container) {
|
||||
if (container.requestFullscreen) {
|
||||
container.requestFullscreen();
|
||||
} else if (container.webkitRequestFullscreen) {
|
||||
container.webkitRequestFullscreen();
|
||||
} else if (container.mozRequestFullScreen) {
|
||||
container.mozRequestFullScreen();
|
||||
} else if (container.msRequestFullscreen) {
|
||||
container.msRequestFullscreen();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private getNavigationCommands(url: string): any[] {
|
||||
const urlTree: UrlTree = this.router.parseUrl(url);
|
||||
const urlSegmentGroup: UrlSegmentGroup = urlTree.root.children[PRIMARY_OUTLET];
|
||||
|
||||
if (!urlSegmentGroup) {
|
||||
return [url];
|
||||
}
|
||||
|
||||
const urlSegments: UrlSegment[] = urlSegmentGroup.segments;
|
||||
|
||||
return urlSegments.reduce(function (acc, item) {
|
||||
acc.push(item.path, item.parameters);
|
||||
return acc;
|
||||
}, []);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user