[ACA-1443] prettier formatting and checks (#629)

* intergrate prettier

* update settings

* integrate with travis

* unified formatting across all files
This commit is contained in:
Denys Vuika
2018-09-13 16:47:55 +01:00
committed by GitHub
parent 06402a9c72
commit 883a1971c5
163 changed files with 13571 additions and 12512 deletions

View File

@@ -4,7 +4,7 @@ root = true
[*] [*]
charset = utf-8 charset = utf-8
indent_style = space indent_style = space
indent_size = 4 indent_size = 2
insert_final_newline = true insert_final_newline = true
trim_trailing_whitespace = true trim_trailing_whitespace = true

2
.prettierignore Normal file
View File

@@ -0,0 +1,2 @@
node_modules
src/assets/i18n

3
.prettierrc Normal file
View File

@@ -0,0 +1,3 @@
{
"singleQuote": true
}

View File

@@ -9,7 +9,7 @@ addons:
language: node_js language: node_js
node_js: node_js:
- "8" - '8'
before_script: before_script:
# Disable services enabled by default # Disable services enabled by default
@@ -24,7 +24,10 @@ before_install:
jobs: jobs:
include: include:
- stage: test - stage: test
script: npm run lint && npm run spellcheck script:
- npm run lint
- npm run spellcheck
- npm run format:check
- stage: test - stage: test
script: script:
- npm run test:ci - npm run test:ci

View File

@@ -1,4 +1,5 @@
{ {
"javascript.preferences.quoteStyle": "single", "javascript.preferences.quoteStyle": "single",
"typescript.preferences.quoteStyle": "single" "typescript.preferences.quoteStyle": "single",
"editor.formatOnSave": true
} }

6
package-lock.json generated
View File

@@ -11444,6 +11444,12 @@
"integrity": "sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks=", "integrity": "sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks=",
"dev": true "dev": true
}, },
"prettier": {
"version": "1.14.2",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-1.14.2.tgz",
"integrity": "sha512-McHPg0n1pIke+A/4VcaS2en+pTNjy4xF+Uuq86u/5dyDO59/TtFZtQ708QIRkEZ3qwKz3GVkVa6mpxK/CpB8Rg==",
"dev": true
},
"pretty-error": { "pretty-error": {
"version": "2.1.1", "version": "2.1.1",
"resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-2.1.1.tgz", "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-2.1.1.tgz",

View File

@@ -22,7 +22,8 @@
"stop:docker": "docker-compose stop", "stop:docker": "docker-compose stop",
"e2e:docker": "npm run start:docker && npm run e2e && npm run stop:docker", "e2e:docker": "npm run start:docker && npm run e2e && npm run stop:docker",
"spellcheck": "cspell 'src/**/*.ts' 'e2e/**/*.ts' 'projects/**/*.ts'", "spellcheck": "cspell 'src/**/*.ts' 'e2e/**/*.ts' 'projects/**/*.ts'",
"inspect.bundle": "ng build app --prod --stats-json && npx webpack-bundle-analyzer dist/app/stats.json" "inspect.bundle": "ng build app --prod --stats-json && npx webpack-bundle-analyzer dist/app/stats.json",
"format:check": "prettier --list-different \"src/{app,environments}/**/*{.ts,.js,.json,.css,.scss}\""
}, },
"private": true, "private": true,
"dependencies": { "dependencies": {
@@ -89,6 +90,7 @@
"karma-jasmine": "~1.1.0", "karma-jasmine": "~1.1.0",
"karma-jasmine-html-reporter": "^0.2.2", "karma-jasmine-html-reporter": "^0.2.2",
"ng-packagr": "^4.1.1", "ng-packagr": "^4.1.1",
"prettier": "^1.14.2",
"protractor": "^5.4.0", "protractor": "^5.4.0",
"rimraf": "2.6.2", "rimraf": "2.6.2",
"selenium-webdriver": "4.0.0-alpha.1", "selenium-webdriver": "4.0.0-alpha.1",

View File

@@ -41,7 +41,11 @@ import {
SetCurrentUrlAction, SetCurrentUrlAction,
SetInitialStateAction SetInitialStateAction
} from './store/actions'; } from './store/actions';
import { AppStore, AppState, INITIAL_APP_STATE } from './store/states/app.state'; import {
AppStore,
AppState,
INITIAL_APP_STATE
} from './store/states/app.state';
import { filter } from 'rxjs/operators'; import { filter } from 'rxjs/operators';
import { MatDialog } from '@angular/material'; import { MatDialog } from '@angular/material';
@@ -68,7 +72,10 @@ export class AppComponent implements OnInit {
this.alfrescoApiService.getInstance().on('error', error => { this.alfrescoApiService.getInstance().on('error', error => {
if (error.status === 401) { if (error.status === 401) {
if (!this.authenticationService.isLoggedIn()) { if (!this.authenticationService.isLoggedIn()) {
this.authenticationService.setRedirect({ provider: 'ECM', url: this.router.url }); this.authenticationService.setRedirect({
provider: 'ECM',
url: this.router.url
});
this.router.navigate(['/login']); this.router.navigate(['/login']);
this.dialogRef.closeAll(); this.dialogRef.closeAll();

View File

@@ -28,7 +28,12 @@ import { NgModule } from '@angular/core';
import { RouterModule, RouteReuseStrategy } from '@angular/router'; import { RouterModule, RouteReuseStrategy } from '@angular/router';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { TRANSLATION_PROVIDER, CoreModule, AppConfigService, DebugAppConfigService } from '@alfresco/adf-core'; import {
TRANSLATION_PROVIDER,
CoreModule,
AppConfigService,
DebugAppConfigService
} from '@alfresco/adf-core';
import { ContentModule } from '@alfresco/adf-content-services'; import { ContentModule } from '@alfresco/adf-content-services';
import { AppComponent } from './app.component'; import { AppComponent } from './app.component';

View File

@@ -119,8 +119,6 @@ export class AppRouteReuseStrategy implements RouteReuseStrategy {
} }
private getRouteData(route: ActivatedRouteSnapshot): RouteData { private getRouteData(route: ActivatedRouteSnapshot): RouteData {
return ( return route.routeConfig && (route.routeConfig.data as RouteData);
route.routeConfig && (route.routeConfig.data as RouteData)
);
} }
} }

View File

@@ -52,7 +52,8 @@ export const APP_ROUTES: Routes = [
}, },
{ {
path: 'settings', path: 'settings',
loadChildren: 'src/app/components/settings/settings.module#AppSettingsModule', loadChildren:
'src/app/components/settings/settings.module#AppSettingsModule',
data: { data: {
title: 'Settings' title: 'Settings'
} }
@@ -61,7 +62,7 @@ export const APP_ROUTES: Routes = [
path: 'preview/s/:id', path: 'preview/s/:id',
component: SharedLinkViewComponent, component: SharedLinkViewComponent,
data: { data: {
title: 'APP.PREVIEW.TITLE', title: 'APP.PREVIEW.TITLE'
} }
}, },
{ {
@@ -89,7 +90,8 @@ export const APP_ROUTES: Routes = [
}, },
{ {
path: 'preview/:nodeId', path: 'preview/:nodeId',
loadChildren: 'src/app/components/preview/preview.module#PreviewModule', loadChildren:
'src/app/components/preview/preview.module#PreviewModule',
data: { data: {
title: 'APP.PREVIEW.TITLE', title: 'APP.PREVIEW.TITLE',
navigateMultiple: true, navigateMultiple: true,
@@ -103,13 +105,15 @@ export const APP_ROUTES: Routes = [
data: { data: {
sortingPreferenceKey: 'libraries' sortingPreferenceKey: 'libraries'
}, },
children: [{ children: [
{
path: '', path: '',
component: LibrariesComponent, component: LibrariesComponent,
data: { data: {
title: 'APP.BROWSE.LIBRARIES.TITLE' title: 'APP.BROWSE.LIBRARIES.TITLE'
} }
}, { },
{
path: ':folderId', path: ':folderId',
component: FilesComponent, component: FilesComponent,
data: { data: {
@@ -119,7 +123,8 @@ export const APP_ROUTES: Routes = [
}, },
{ {
path: ':folderId/preview/:nodeId', path: ':folderId/preview/:nodeId',
loadChildren: 'src/app/components/preview/preview.module#PreviewModule', loadChildren:
'src/app/components/preview/preview.module#PreviewModule',
data: { data: {
title: 'APP.PREVIEW.TITLE', title: 'APP.PREVIEW.TITLE',
navigateMultiple: true, navigateMultiple: true,
@@ -151,7 +156,8 @@ export const APP_ROUTES: Routes = [
}, },
{ {
path: 'preview/:nodeId', path: 'preview/:nodeId',
loadChildren: 'src/app/components/preview/preview.module#PreviewModule', loadChildren:
'src/app/components/preview/preview.module#PreviewModule',
data: { data: {
title: 'APP.PREVIEW.TITLE', title: 'APP.PREVIEW.TITLE',
navigateMultiple: true, navigateMultiple: true,
@@ -160,7 +166,8 @@ export const APP_ROUTES: Routes = [
}, },
{ {
path: ':folderId/preview/:nodeId', path: ':folderId/preview/:nodeId',
loadChildren: 'src/app/components/preview/preview.module#PreviewModule', loadChildren:
'src/app/components/preview/preview.module#PreviewModule',
data: { data: {
title: 'APP.PREVIEW.TITLE', title: 'APP.PREVIEW.TITLE',
navigateMultiple: true, navigateMultiple: true,
@@ -184,7 +191,8 @@ export const APP_ROUTES: Routes = [
}, },
{ {
path: 'preview/:nodeId', path: 'preview/:nodeId',
loadChildren: 'src/app/components/preview/preview.module#PreviewModule', loadChildren:
'src/app/components/preview/preview.module#PreviewModule',
data: { data: {
title: 'APP.PREVIEW.TITLE', title: 'APP.PREVIEW.TITLE',
navigateMultiple: true, navigateMultiple: true,
@@ -208,7 +216,8 @@ export const APP_ROUTES: Routes = [
}, },
{ {
path: 'preview/:nodeId', path: 'preview/:nodeId',
loadChildren: 'src/app/components/preview/preview.module#PreviewModule', loadChildren:
'src/app/components/preview/preview.module#PreviewModule',
data: { data: {
title: 'APP.PREVIEW.TITLE', title: 'APP.PREVIEW.TITLE',
navigateMultiple: true, navigateMultiple: true,
@@ -245,7 +254,8 @@ export const APP_ROUTES: Routes = [
}, },
{ {
path: 'preview/:nodeId', path: 'preview/:nodeId',
loadChildren: 'src/app/components/preview/preview.module#PreviewModule', loadChildren:
'src/app/components/preview/preview.module#PreviewModule',
data: { data: {
title: 'APP.PREVIEW.TITLE', title: 'APP.PREVIEW.TITLE',
navigateMultiple: true, navigateMultiple: true,
@@ -263,4 +273,3 @@ export const APP_ROUTES: Routes = [
canActivate: [AuthGuardEcm] canActivate: [AuthGuardEcm]
} }
]; ];

View File

@@ -40,7 +40,8 @@ export class AboutComponent implements OnInit {
status: ObjectDataTableAdapter; status: ObjectDataTableAdapter;
license: ObjectDataTableAdapter; license: ObjectDataTableAdapter;
modules: ObjectDataTableAdapter; modules: ObjectDataTableAdapter;
githubUrlCommitAlpha = 'https://github.com/Alfresco/alfresco-content-app/commits'; githubUrlCommitAlpha =
'https://github.com/Alfresco/alfresco-content-app/commits';
releaseVersion = ''; releaseVersion = '';
constructor( constructor(
@@ -49,7 +50,8 @@ export class AboutComponent implements OnInit {
) {} ) {}
ngOnInit() { ngOnInit() {
this.contentApi.getRepositoryInformation() this.contentApi
.getRepositoryInformation()
.pipe(map(node => node.entry.repository)) .pipe(map(node => node.entry.repository))
.subscribe(repository => { .subscribe(repository => {
this.repository = repository; this.repository = repository;
@@ -57,49 +59,128 @@ export class AboutComponent implements OnInit {
this.modules = new ObjectDataTableAdapter(repository.modules, [ this.modules = new ObjectDataTableAdapter(repository.modules, [
{ type: 'text', key: 'id', title: 'ID', sortable: true }, { type: 'text', key: 'id', title: 'ID', sortable: true },
{ type: 'text', key: 'title', title: 'Title', sortable: true }, { type: 'text', key: 'title', title: 'Title', sortable: true },
{type: 'text', key: 'version', title: 'Description', sortable: true}, {
{type: 'date', key: 'installDate', title: 'Install Date', sortable: true}, type: 'text',
{type: 'text', key: 'installState', title: 'Install State', sortable: true}, key: 'version',
{type: 'text', key: 'versionMin', title: 'Version Minor', sortable: true}, title: 'Description',
{type: 'text', key: 'versionMax', title: 'Version Max', sortable: true} sortable: true
]); },
{
this.status = new ObjectDataTableAdapter([repository.status], [ type: 'date',
{type: 'text', key: 'isReadOnly', title: 'Read Only', sortable: true}, key: 'installDate',
{type: 'text', key: 'isAuditEnabled', title: 'Audit Enable', sortable: true}, title: 'Install Date',
{type: 'text', key: 'isQuickShareEnabled', title: 'Quick Shared Enable', sortable: true}, sortable: true
{type: 'text', key: 'isThumbnailGenerationEnabled', title: 'Thumbnail Generation', sortable: true} },
{
type: 'text',
key: 'installState',
title: 'Install State',
sortable: true
},
{
type: 'text',
key: 'versionMin',
title: 'Version Minor',
sortable: true
},
{
type: 'text',
key: 'versionMax',
title: 'Version Max',
sortable: true
}
]); ]);
this.status = new ObjectDataTableAdapter(
[repository.status],
[
{
type: 'text',
key: 'isReadOnly',
title: 'Read Only',
sortable: true
},
{
type: 'text',
key: 'isAuditEnabled',
title: 'Audit Enable',
sortable: true
},
{
type: 'text',
key: 'isQuickShareEnabled',
title: 'Quick Shared Enable',
sortable: true
},
{
type: 'text',
key: 'isThumbnailGenerationEnabled',
title: 'Thumbnail Generation',
sortable: true
}
]
);
if (repository.license) { if (repository.license) {
this.license = new ObjectDataTableAdapter([repository.license], [ this.license = new ObjectDataTableAdapter(
{type: 'date', key: 'issuedAt', title: 'Issued At', sortable: true}, [repository.license],
{type: 'date', key: 'expiresAt', title: 'Expires At', sortable: true}, [
{type: 'text', key: 'remainingDays', title: 'Remaining Days', sortable: true}, {
type: 'date',
key: 'issuedAt',
title: 'Issued At',
sortable: true
},
{
type: 'date',
key: 'expiresAt',
title: 'Expires At',
sortable: true
},
{
type: 'text',
key: 'remainingDays',
title: 'Remaining Days',
sortable: true
},
{ type: 'text', key: 'holder', title: 'Holder', sortable: true }, { type: 'text', key: 'holder', title: 'Holder', sortable: true },
{ type: 'text', key: 'mode', title: 'Type', sortable: true }, { type: 'text', key: 'mode', title: 'Type', sortable: true },
{type: 'text', key: 'isClusterEnabled', title: 'Cluster Enabled', sortable: true}, {
{type: 'text', key: 'isCryptodocEnabled', title: 'Cryptodoc Enable', sortable: true} type: 'text',
]); key: 'isClusterEnabled',
title: 'Cluster Enabled',
sortable: true
},
{
type: 'text',
key: 'isCryptodocEnabled',
title: 'Cryptodoc Enable',
sortable: true
}
]
);
} }
}); });
this.http.get('/versions.json') this.http.get('/versions.json').subscribe((response: any) => {
.subscribe((response: any) => {
const regexp = new RegExp('^(@alfresco|alfresco-)'); const regexp = new RegExp('^(@alfresco|alfresco-)');
const alfrescoPackagesTableRepresentation = Object.keys(response.dependencies) const alfrescoPackagesTableRepresentation = Object.keys(
.filter((val) => regexp.test(val)) response.dependencies
.map((val) => ({ )
.filter(val => regexp.test(val))
.map(val => ({
name: val, name: val,
version: response.dependencies[val].version version: response.dependencies[val].version
})); }));
this.data = new ObjectDataTableAdapter(alfrescoPackagesTableRepresentation, [ this.data = new ObjectDataTableAdapter(
alfrescoPackagesTableRepresentation,
[
{ type: 'text', key: 'name', title: 'Name', sortable: true }, { type: 'text', key: 'name', title: 'Name', sortable: true },
{ type: 'text', key: 'version', title: 'Version', sortable: true } { type: 'text', key: 'version', title: 'Version', sortable: true }
]); ]
);
this.releaseVersion = response.version; this.releaseVersion = response.version;
}); });

View File

@@ -37,12 +37,7 @@ const routes: Routes = [
]; ];
@NgModule({ @NgModule({
imports: [ imports: [CommonModule, CoreModule.forChild(), RouterModule.forChild(routes)],
CommonModule,
CoreModule.forChild(),
RouterModule.forChild(routes)
],
declarations: [AboutComponent] declarations: [AboutComponent]
}) })
export class AboutModule { export class AboutModule {}
}

View File

@@ -34,19 +34,35 @@ import {
} from '@angular/animations'; } from '@angular/animations';
export const contextMenuAnimation = [ export const contextMenuAnimation = [
state('void', style({ state(
'void',
style({
opacity: 0, opacity: 0,
transform: 'scale(0.01, 0.01)' transform: 'scale(0.01, 0.01)'
})), })
transition('void => *', sequence([ ),
transition(
'void => *',
sequence([
query('.mat-menu-content', style({ opacity: 0 })), query('.mat-menu-content', style({ opacity: 0 })),
animate('100ms linear', style({ opacity: 1, transform: 'scale(1, 0.5)' })), animate(
'100ms linear',
style({ opacity: 1, transform: 'scale(1, 0.5)' })
),
group([ group([
query('.mat-menu-content', animate('400ms cubic-bezier(0.55, 0, 0.55, 0.2)', query(
'.mat-menu-content',
animate(
'400ms cubic-bezier(0.55, 0, 0.55, 0.2)',
style({ opacity: 1 }) style({ opacity: 1 })
)), )
animate('300ms cubic-bezier(0.25, 0.8, 0.25, 1)', style({ transform: 'scale(1, 1)' })), ),
animate(
'300ms cubic-bezier(0.25, 0.8, 0.25, 1)',
style({ transform: 'scale(1, 1)' })
)
]) ])
])), ])
),
transition('* => void', animate('150ms 50ms linear', style({ opacity: 0 }))) transition('* => void', animate('150ms 50ms linear', style({ opacity: 0 })))
]; ];

View File

@@ -27,13 +27,13 @@ import { Directive, ElementRef, OnDestroy } from '@angular/core';
import { FocusableOption, FocusMonitor, FocusOrigin } from '@angular/cdk/a11y'; import { FocusableOption, FocusMonitor, FocusOrigin } from '@angular/cdk/a11y';
@Directive({ @Directive({
selector: '[acaContextMenuItem]', selector: '[acaContextMenuItem]'
}) })
export class ContextMenuItemDirective implements OnDestroy, FocusableOption { export class ContextMenuItemDirective implements OnDestroy, FocusableOption {
constructor( constructor(
private elementRef: ElementRef, private elementRef: ElementRef,
private focusMonitor: FocusMonitor) { private focusMonitor: FocusMonitor
) {
focusMonitor.monitor(this.getHostElement(), false); focusMonitor.monitor(this.getHostElement(), false);
} }

View File

@@ -1,15 +1,21 @@
import { Directive, Output, EventEmitter, OnInit, OnDestroy } from '@angular/core'; import {
Directive,
Output,
EventEmitter,
OnInit,
OnDestroy
} from '@angular/core';
import { fromEvent, Subscription } from 'rxjs'; import { fromEvent, Subscription } from 'rxjs';
import { delay } from 'rxjs/operators'; import { delay } from 'rxjs/operators';
@Directive({ @Directive({
selector: '[acaContextMenuOutsideEvent]' selector: '[acaContextMenuOutsideEvent]'
}) })
export class OutsideEventDirective implements OnInit, OnDestroy { export class OutsideEventDirective implements OnInit, OnDestroy {
private subscriptions: Subscription[] = []; private subscriptions: Subscription[] = [];
@Output() clickOutside: EventEmitter<null> = new EventEmitter(); @Output()
clickOutside: EventEmitter<null> = new EventEmitter();
constructor() {} constructor() {}

View File

@@ -26,7 +26,6 @@
import { OverlayRef } from '@angular/cdk/overlay'; import { OverlayRef } from '@angular/cdk/overlay';
export class ContextMenuOverlayRef { export class ContextMenuOverlayRef {
constructor(private overlayRef: OverlayRef) {} constructor(private overlayRef: OverlayRef) {}
close(): void { close(): void {

View File

@@ -24,8 +24,14 @@
*/ */
import { import {
Component, ViewEncapsulation, OnInit, OnDestroy, HostListener, Component,
ViewChildren, QueryList, AfterViewInit ViewEncapsulation,
OnInit,
OnDestroy,
HostListener,
ViewChildren,
QueryList,
AfterViewInit
} from '@angular/core'; } from '@angular/core';
import { trigger } from '@angular/animations'; import { trigger } from '@angular/animations';
import { FocusKeyManager } from '@angular/cdk/a11y'; import { FocusKeyManager } from '@angular/cdk/a11y';
@@ -55,9 +61,7 @@ import { ContextMenuItemDirective } from './context-menu-item.directive';
class: 'aca-context-menu' class: 'aca-context-menu'
}, },
encapsulation: ViewEncapsulation.None, encapsulation: ViewEncapsulation.None,
animations: [ animations: [trigger('panelAnimation', contextMenuAnimation)]
trigger('panelAnimation', contextMenuAnimation)
]
}) })
export class ContextMenuComponent implements OnInit, OnDestroy, AfterViewInit { export class ContextMenuComponent implements OnInit, OnDestroy, AfterViewInit {
private onDestroy$: Subject<boolean> = new Subject<boolean>(); private onDestroy$: Subject<boolean> = new Subject<boolean>();
@@ -100,7 +104,7 @@ export class ContextMenuComponent implements OnInit, OnDestroy, AfterViewInit {
constructor( constructor(
private contextMenuOverlayRef: ContextMenuOverlayRef, private contextMenuOverlayRef: ContextMenuOverlayRef,
private extensions: AppExtensionService, private extensions: AppExtensionService,
private store: Store<AppStore>, private store: Store<AppStore>
) {} ) {}
onClickOutsideEvent() { onClickOutsideEvent() {
@@ -136,7 +140,9 @@ export class ContextMenuComponent implements OnInit, OnDestroy, AfterViewInit {
} }
ngAfterViewInit() { ngAfterViewInit() {
this._keyManager = new FocusKeyManager<ContextMenuItemDirective>(this.contextMenuItems); this._keyManager = new FocusKeyManager<ContextMenuItemDirective>(
this.contextMenuItems
);
this._keyManager.setFirstItemActive(); this._keyManager.setFirstItemActive();
} }
} }

View File

@@ -40,7 +40,8 @@ export class ContextActionsDirective {
private overlayRef: ContextMenuOverlayRef = null; private overlayRef: ContextMenuOverlayRef = null;
// tslint:disable-next-line:no-input-rename // tslint:disable-next-line:no-input-rename
@Input('acaContextEnable') enabled = true; @Input('acaContextEnable')
enabled = true;
@HostListener('window:resize', ['$event']) @HostListener('window:resize', ['$event'])
onResize(event) { onResize(event) {
@@ -93,7 +94,7 @@ export class ContextActionsDirective {
source: event, source: event,
hasBackdrop: false, hasBackdrop: false,
backdropClass: 'cdk-overlay-transparent-backdrop', backdropClass: 'cdk-overlay-transparent-backdrop',
panelClass: 'cdk-overlay-pane', panelClass: 'cdk-overlay-pane'
}); });
} }
@@ -104,22 +105,27 @@ export class ContextActionsDirective {
} }
private isInSelection(row: DataRow): MinimalNodeEntity { private isInSelection(row: DataRow): MinimalNodeEntity {
return this.documentList.selection.find((selected) => return this.documentList.selection.find(
row.getValue('name') === selected.entry.name); selected => row.getValue('name') === selected.entry.name
);
} }
private getSelectedRow(event): DataRow { private getSelectedRow(event): DataRow {
const rowElement = this.findAncestor(<HTMLElement>event.target, 'adf-datatable-row'); const rowElement = this.findAncestor(
<HTMLElement>event.target,
'adf-datatable-row'
);
if (!rowElement) { if (!rowElement) {
return null; return null;
} }
const rowName = rowElement.querySelector('.adf-data-table-cell--text .adf-datatable-cell') const rowName = rowElement
.textContent .querySelector('.adf-data-table-cell--text .adf-datatable-cell')
.trim(); .textContent.trim();
return this.documentList.data.getRows() return this.documentList.data
.getRows()
.find((row: DataRow) => row.getValue('name') === rowName); .find((row: DataRow) => row.getValue('name') === rowName);
} }

View File

@@ -24,7 +24,12 @@
*/ */
import { NgModule, ModuleWithProviders } from '@angular/core'; import { NgModule, ModuleWithProviders } from '@angular/core';
import { MatMenuModule, MatListModule, MatIconModule, MatButtonModule } from '@angular/material'; import {
MatMenuModule,
MatListModule,
MatIconModule,
MatButtonModule
} from '@angular/material';
import { BrowserModule } from '@angular/platform-browser'; import { BrowserModule } from '@angular/platform-browser';
import { CoreModule } from '@alfresco/adf-core'; import { CoreModule } from '@alfresco/adf-core';
import { CoreExtensionsModule } from '../../extensions/core.extensions.module'; import { CoreExtensionsModule } from '../../extensions/core.extensions.module';
@@ -58,17 +63,13 @@ import { OutsideEventDirective } from './context-menu-outside-event.directive';
ContextActionsDirective, ContextActionsDirective,
ContextMenuComponent ContextMenuComponent
], ],
entryComponents: [ entryComponents: [ContextMenuComponent]
ContextMenuComponent
]
}) })
export class ContextMenuModule { export class ContextMenuModule {
static forRoot(): ModuleWithProviders { static forRoot(): ModuleWithProviders {
return { return {
ngModule: ContextMenuModule, ngModule: ContextMenuModule,
providers: [ providers: [ContextMenuService]
ContextMenuService
]
}; };
} }

View File

@@ -7,12 +7,9 @@ import { ContextmenuOverlayConfig } from './interfaces';
@Injectable() @Injectable()
export class ContextMenuService { export class ContextMenuService {
constructor( constructor(private injector: Injector, private overlay: Overlay) {}
private injector: Injector,
private overlay: Overlay) { }
open(config: ContextmenuOverlayConfig) { open(config: ContextmenuOverlayConfig) {
const overlay = this.createOverlay(config); const overlay = this.createOverlay(config);
const overlayRef = new ContextMenuOverlayRef(overlay); const overlayRef = new ContextMenuOverlayRef(overlay);
@@ -23,11 +20,14 @@ export class ContextMenuService {
// prevent native contextmenu on overlay element if config.hasBackdrop is true // prevent native contextmenu on overlay element if config.hasBackdrop is true
if (config.hasBackdrop) { if (config.hasBackdrop) {
(<any>overlay)._backdropElement (<any>overlay)._backdropElement.addEventListener(
.addEventListener('contextmenu', () => { 'contextmenu',
() => {
event.preventDefault(); event.preventDefault();
(<any>overlay)._backdropClick.next(null); (<any>overlay)._backdropClick.next(null);
}, true); },
true
);
} }
return overlayRef; return overlayRef;
@@ -38,16 +38,29 @@ export class ContextMenuService {
return this.overlay.create(overlayConfig); return this.overlay.create(overlayConfig);
} }
private attachDialogContainer(overlay: OverlayRef, config: ContextmenuOverlayConfig, contextmenuOverlayRef: ContextMenuOverlayRef) { private attachDialogContainer(
overlay: OverlayRef,
config: ContextmenuOverlayConfig,
contextmenuOverlayRef: ContextMenuOverlayRef
) {
const injector = this.createInjector(config, contextmenuOverlayRef); const injector = this.createInjector(config, contextmenuOverlayRef);
const containerPortal = new ComponentPortal(ContextMenuComponent, null, injector); const containerPortal = new ComponentPortal(
const containerRef: ComponentRef<ContextMenuComponent> = overlay.attach(containerPortal); ContextMenuComponent,
null,
injector
);
const containerRef: ComponentRef<ContextMenuComponent> = overlay.attach(
containerPortal
);
return containerRef.instance; return containerRef.instance;
} }
private createInjector(config: ContextmenuOverlayConfig, contextmenuOverlayRef: ContextMenuOverlayRef): PortalInjector { private createInjector(
config: ContextmenuOverlayConfig,
contextmenuOverlayRef: ContextMenuOverlayRef
): PortalInjector {
const injectionTokens = new WeakMap(); const injectionTokens = new WeakMap();
injectionTokens.set(ContextMenuOverlayRef, contextmenuOverlayRef); injectionTokens.set(ContextMenuOverlayRef, contextmenuOverlayRef);
@@ -67,23 +80,29 @@ export class ContextMenuService {
}) })
}; };
const positionStrategy = this.overlay.position() const positionStrategy = this.overlay
.position()
.connectedTo( .connectedTo(
new ElementRef(fakeElement), new ElementRef(fakeElement),
{ originX: 'start', originY: 'bottom' }, { originX: 'start', originY: 'bottom' },
{ overlayX: 'start', overlayY: 'top' }) { overlayX: 'start', overlayY: 'top' }
)
.withFallbackPosition( .withFallbackPosition(
{ originX: 'start', originY: 'top' }, { originX: 'start', originY: 'top' },
{ overlayX: 'start', overlayY: 'bottom' }) { overlayX: 'start', overlayY: 'bottom' }
)
.withFallbackPosition( .withFallbackPosition(
{ originX: 'end', originY: 'top' }, { originX: 'end', originY: 'top' },
{ overlayX: 'start', overlayY: 'top' }) { overlayX: 'start', overlayY: 'top' }
)
.withFallbackPosition( .withFallbackPosition(
{ originX: 'start', originY: 'top' }, { originX: 'start', originY: 'top' },
{ overlayX: 'end', overlayY: 'top' }) { overlayX: 'end', overlayY: 'top' }
)
.withFallbackPosition( .withFallbackPosition(
{ originX: 'end', originY: 'center' }, { originX: 'end', originY: 'center' },
{ overlayX: 'start', overlayY: 'center' }) { overlayX: 'start', overlayY: 'center' }
)
.withFallbackPosition( .withFallbackPosition(
{ originX: 'start', originY: 'center' }, { originX: 'start', originY: 'center' },
{ overlayX: 'end', overlayY: 'center' } { overlayX: 'end', overlayY: 'center' }

View File

@@ -18,7 +18,7 @@
text-align: center; text-align: center;
color: inherit; color: inherit;
border-radius: 100%; border-radius: 100%;
background-color: mat-color($background, card, .15); background-color: mat-color($background, card, 0.15);
} }
.current-user__full-name { .current-user__full-name {

View File

@@ -26,7 +26,10 @@
import { Component, ViewEncapsulation } from '@angular/core'; import { Component, ViewEncapsulation } from '@angular/core';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { selectUser, appLanguagePicker } from '../../store/selectors/app.selectors'; import {
selectUser,
appLanguagePicker
} from '../../store/selectors/app.selectors';
import { AppStore } from '../../store/states'; import { AppStore } from '../../store/states';
import { ProfileState } from '@alfresco/adf-extensions'; import { ProfileState } from '@alfresco/adf-extensions';
import { SetSelectedNodesAction } from '../../store/actions'; import { SetSelectedNodesAction } from '../../store/actions';

View File

@@ -28,8 +28,11 @@ import { Router } from '@angular/router';
import { TestBed, ComponentFixture } from '@angular/core/testing'; import { TestBed, ComponentFixture } from '@angular/core/testing';
import { import {
AlfrescoApiService, AlfrescoApiService,
TimeAgoPipe, NodeNameTooltipPipe, TimeAgoPipe,
NodeFavoriteDirective, DataTableComponent, AppConfigPipe NodeNameTooltipPipe,
NodeFavoriteDirective,
DataTableComponent,
AppConfigPipe
} from '@alfresco/adf-core'; } from '@alfresco/adf-core';
import { DocumentListComponent } from '@alfresco/adf-content-services'; import { DocumentListComponent } from '@alfresco/adf-content-services';
import { ContentManagementService } from '../../services/content-management.service'; import { ContentManagementService } from '../../services/content-management.service';
@@ -91,7 +94,9 @@ describe('FavoritesComponent', () => {
alfrescoApi = TestBed.get(AlfrescoApiService); alfrescoApi = TestBed.get(AlfrescoApiService);
alfrescoApi.reset(); alfrescoApi.reset();
spyOn(alfrescoApi.favoritesApi, 'getFavorites').and.returnValue(Promise.resolve(page)); spyOn(alfrescoApi.favoritesApi, 'getFavorites').and.returnValue(
Promise.resolve(page)
);
contentApi = TestBed.get(ContentApiService); contentApi = TestBed.get(ContentApiService);
@@ -142,7 +147,10 @@ describe('FavoritesComponent', () => {
component.navigate(node); component.navigate(node);
expect(router.navigate).toHaveBeenCalledWith([ '/libraries', 'folder-node' ]); expect(router.navigate).toHaveBeenCalledWith([
'/libraries',
'folder-node'
]);
}); });
it('navigates to `/personal-files` if node path has no `Sites`', () => { it('navigates to `/personal-files` if node path has no `Sites`', () => {
@@ -150,7 +158,10 @@ describe('FavoritesComponent', () => {
component.navigate(node); component.navigate(node);
expect(router.navigate).toHaveBeenCalledWith([ '/personal-files', 'folder-node' ]); expect(router.navigate).toHaveBeenCalledWith([
'/personal-files',
'folder-node'
]);
}); });
it('does not navigate when node is not folder', () => { it('does not navigate when node is not folder', () => {

View File

@@ -69,10 +69,7 @@ export class FavoritesComponent extends PageComponent implements OnInit {
this.content.favoriteToggle.subscribe(() => this.reload()), this.content.favoriteToggle.subscribe(() => this.reload()),
this.breakpointObserver this.breakpointObserver
.observe([ .observe([Breakpoints.HandsetPortrait, Breakpoints.HandsetLandscape])
Breakpoints.HandsetPortrait,
Breakpoints.HandsetLandscape
])
.subscribe(result => { .subscribe(result => {
this.isSmallScreen = result.matches; this.isSmallScreen = result.matches;
}) })
@@ -94,9 +91,7 @@ export class FavoritesComponent extends PageComponent implements OnInit {
.getNode(id) .getNode(id)
.pipe(map(node => node.entry)) .pipe(map(node => node.entry))
.subscribe(({ path }: MinimalNodeEntryEntity) => { .subscribe(({ path }: MinimalNodeEntryEntity) => {
const routeUrl = isSitePath(path) const routeUrl = isSitePath(path) ? '/libraries' : '/personal-files';
? '/libraries'
: '/personal-files';
this.router.navigate([routeUrl, id]); this.router.navigate([routeUrl, id]);
}); });
} }

View File

@@ -23,11 +23,19 @@
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>. * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/ */
import { TestBed, fakeAsync, tick, ComponentFixture } from '@angular/core/testing'; import {
TestBed,
fakeAsync,
tick,
ComponentFixture
} from '@angular/core/testing';
import { NO_ERRORS_SCHEMA } from '@angular/core'; import { NO_ERRORS_SCHEMA } from '@angular/core';
import { Router, ActivatedRoute } from '@angular/router'; import { Router, ActivatedRoute } from '@angular/router';
import { import {
TimeAgoPipe, NodeNameTooltipPipe, FileSizePipe, NodeFavoriteDirective, TimeAgoPipe,
NodeNameTooltipPipe,
FileSizePipe,
NodeFavoriteDirective,
DataTableComponent, DataTableComponent,
UploadService, UploadService,
AppConfigPipe AppConfigPipe
@@ -66,10 +74,13 @@ describe('FilesComponent', () => {
ExperimentalDirective ExperimentalDirective
], ],
providers: [ providers: [
{ provide: ActivatedRoute, useValue: { {
provide: ActivatedRoute,
useValue: {
snapshot: { data: { preferencePrefix: 'prefix' } }, snapshot: { data: { preferencePrefix: 'prefix' } },
params: of({ folderId: 'someId' }) params: of({ folderId: 'someId' })
} } }
}
], ],
schemas: [NO_ERRORS_SCHEMA] schemas: [NO_ERRORS_SCHEMA]
}); });
@@ -126,7 +137,10 @@ describe('FilesComponent', () => {
fixture.detectChanges(); fixture.detectChanges();
expect(router.navigate['calls'].argsFor(0)[0]).toEqual(['/personal-files', 'parent-id']); expect(router.navigate['calls'].argsFor(0)[0]).toEqual([
'/personal-files',
'parent-id'
]);
}); });
}); });
@@ -239,7 +253,6 @@ describe('FilesComponent', () => {
})); }));
}); });
describe('onBreadcrumbNavigate()', () => { describe('onBreadcrumbNavigate()', () => {
beforeEach(() => { beforeEach(() => {
spyOn(contentApi, 'getNode').and.returnValue(of({ entry: node })); spyOn(contentApi, 'getNode').and.returnValue(of({ entry: node }));
@@ -267,7 +280,10 @@ describe('FilesComponent', () => {
it('should navigates to node when id provided', () => { it('should navigates to node when id provided', () => {
component.navigate(node.id); component.navigate(node.id);
expect(router.navigate).toHaveBeenCalledWith(['./', node.id], jasmine.any(Object)); expect(router.navigate).toHaveBeenCalledWith(
['./', node.id],
jasmine.any(Object)
);
}); });
it('should navigates to home when id not provided', () => { it('should navigates to home when id not provided', () => {

View File

@@ -27,7 +27,12 @@ import { FileUploadEvent, UploadService } from '@alfresco/adf-core';
import { Component, OnDestroy, OnInit } from '@angular/core'; import { Component, OnDestroy, OnInit } from '@angular/core';
import { ActivatedRoute, Params, Router } from '@angular/router'; import { ActivatedRoute, Params, Router } from '@angular/router';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
import { MinimalNodeEntity, MinimalNodeEntryEntity, PathElement, PathElementEntity } from 'alfresco-js-api'; import {
MinimalNodeEntity,
MinimalNodeEntryEntity,
PathElement,
PathElementEntity
} from 'alfresco-js-api';
import { ContentManagementService } from '../../services/content-management.service'; import { ContentManagementService } from '../../services/content-management.service';
import { NodeActionsService } from '../../services/node-actions.service'; import { NodeActionsService } from '../../services/node-actions.service';
import { AppStore } from '../../store/states/app.state'; import { AppStore } from '../../store/states/app.state';
@@ -42,13 +47,13 @@ import { debounceTime } from 'rxjs/operators';
templateUrl: './files.component.html' templateUrl: './files.component.html'
}) })
export class FilesComponent extends PageComponent implements OnInit, OnDestroy { export class FilesComponent extends PageComponent implements OnInit, OnDestroy {
isValidPath = true; isValidPath = true;
isSmallScreen = false; isSmallScreen = false;
private nodePath: PathElement[]; private nodePath: PathElement[];
constructor(private router: Router, constructor(
private router: Router,
private route: ActivatedRoute, private route: ActivatedRoute,
private contentApi: ContentApiService, private contentApi: ContentApiService,
store: Store<AppStore>, store: Store<AppStore>,
@@ -56,7 +61,8 @@ export class FilesComponent extends PageComponent implements OnInit, OnDestroy {
private uploadService: UploadService, private uploadService: UploadService,
content: ContentManagementService, content: ContentManagementService,
extensions: AppExtensionService, extensions: AppExtensionService,
private breakpointObserver: BreakpointObserver) { private breakpointObserver: BreakpointObserver
) {
super(store, extensions, content); super(store, extensions, content);
} }
@@ -71,40 +77,40 @@ export class FilesComponent extends PageComponent implements OnInit, OnDestroy {
route.params.subscribe(({ folderId }: Params) => { route.params.subscribe(({ folderId }: Params) => {
const nodeId = folderId || data.defaultNodeId; const nodeId = folderId || data.defaultNodeId;
this.contentApi this.contentApi.getNode(nodeId).subscribe(
.getNode(nodeId)
.subscribe(
node => { node => {
this.isValidPath = true; this.isValidPath = true;
if (node.entry && node.entry.isFolder) { if (node.entry && node.entry.isFolder) {
this.updateCurrentNode(node.entry); this.updateCurrentNode(node.entry);
} else { } else {
this.router.navigate( this.router.navigate(['/personal-files', node.entry.parentId], {
['/personal-files', node.entry.parentId], replaceUrl: true
{ replaceUrl: true } });
);
} }
}, },
() => this.isValidPath = false () => (this.isValidPath = false)
); );
}); });
this.subscriptions = this.subscriptions.concat([ this.subscriptions = this.subscriptions.concat([
nodeActionsService.contentCopied.subscribe((nodes) => this.onContentCopied(nodes)), nodeActionsService.contentCopied.subscribe(nodes =>
this.onContentCopied(nodes)
),
content.folderCreated.subscribe(() => this.documentList.reload()), content.folderCreated.subscribe(() => this.documentList.reload()),
content.folderEdited.subscribe(() => this.documentList.reload()), content.folderEdited.subscribe(() => this.documentList.reload()),
content.nodesDeleted.subscribe(() => this.documentList.reload()), content.nodesDeleted.subscribe(() => this.documentList.reload()),
content.nodesMoved.subscribe(() => this.documentList.reload()), content.nodesMoved.subscribe(() => this.documentList.reload()),
content.nodesRestored.subscribe(() => this.documentList.reload()), content.nodesRestored.subscribe(() => this.documentList.reload()),
uploadService.fileUploadComplete.pipe(debounceTime(300)).subscribe(file => this.onFileUploadedEvent(file)), uploadService.fileUploadComplete
uploadService.fileUploadDeleted.pipe(debounceTime(300)).subscribe((file) => this.onFileUploadedEvent(file)), .pipe(debounceTime(300))
.subscribe(file => this.onFileUploadedEvent(file)),
uploadService.fileUploadDeleted
.pipe(debounceTime(300))
.subscribe(file => this.onFileUploadedEvent(file)),
this.breakpointObserver this.breakpointObserver
.observe([ .observe([Breakpoints.HandsetPortrait, Breakpoints.HandsetLandscape])
Breakpoints.HandsetPortrait,
Breakpoints.HandsetLandscape
])
.subscribe(result => { .subscribe(result => {
this.isSmallScreen = result.matches; this.isSmallScreen = result.matches;
}) })
@@ -149,7 +155,10 @@ export class FilesComponent extends PageComponent implements OnInit, OnDestroy {
onBreadcrumbNavigate(route: PathElementEntity) { onBreadcrumbNavigate(route: PathElementEntity) {
// todo: review this approach once 5.2.3 is out // todo: review this approach once 5.2.3 is out
if (this.nodePath && this.nodePath.length > 2) { if (this.nodePath && this.nodePath.length > 2) {
if (this.nodePath[1].name === 'Sites' && this.nodePath[2].id === route.id) { if (
this.nodePath[1].name === 'Sites' &&
this.nodePath[2].id === route.id
) {
return this.navigate(this.nodePath[3].id); return this.navigate(this.nodePath[3].id);
} }
} }
@@ -173,11 +182,14 @@ export class FilesComponent extends PageComponent implements OnInit, OnDestroy {
if (event && event.file.options.parentId) { if (event && event.file.options.parentId) {
if (this.nodePath) { if (this.nodePath) {
const correspondingNodePath = this.nodePath.find(pathItem => pathItem.id === event.file.options.parentId); const correspondingNodePath = this.nodePath.find(
pathItem => pathItem.id === event.file.options.parentId
);
// check if the current folder has the 'trigger-upload-folder' as one of its parents // check if the current folder has the 'trigger-upload-folder' as one of its parents
if (correspondingNodePath) { if (correspondingNodePath) {
const correspondingIndex = this.nodePath.length - this.nodePath.indexOf(correspondingNodePath); const correspondingIndex =
this.nodePath.length - this.nodePath.indexOf(correspondingNodePath);
this.displayFolderParent(event.file.options.path, correspondingIndex); this.displayFolderParent(event.file.options.path, correspondingIndex);
} }
} }
@@ -189,7 +201,8 @@ export class FilesComponent extends PageComponent implements OnInit, OnDestroy {
const currentFoldersDisplayed: any = this.documentList.data.getRows() || []; const currentFoldersDisplayed: any = this.documentList.data.getRows() || [];
const alreadyDisplayedParentFolder = currentFoldersDisplayed.find( const alreadyDisplayedParentFolder = currentFoldersDisplayed.find(
row => row.node.entry.isFolder && row.node.entry.name === parentName); row => row.node.entry.isFolder && row.node.entry.name === parentName
);
if (alreadyDisplayedParentFolder) { if (alreadyDisplayedParentFolder) {
return; return;
@@ -198,9 +211,10 @@ export class FilesComponent extends PageComponent implements OnInit, OnDestroy {
} }
onContentCopied(nodes: MinimalNodeEntity[]) { onContentCopied(nodes: MinimalNodeEntity[]) {
const newNode = nodes const newNode = nodes.find(node => {
.find((node) => { return (
return node && node.entry && node.entry.parentId === this.getParentNodeId(); node && node.entry && node.entry.parentId === this.getParentNodeId()
);
}); });
if (newNode) { if (newNode) {
this.documentList.reload(); this.documentList.reload();
@@ -241,7 +255,9 @@ export class FilesComponent extends PageComponent implements OnInit, OnDestroy {
if (this.isSiteContainer(node)) { if (this.isSiteContainer(node)) {
// rename 'documentLibrary' entry to the target site display name // rename 'documentLibrary' entry to the target site display name
// clicking on the breadcrumb entry loads the site content // clicking on the breadcrumb entry loads the site content
const parentNode = await this.contentApi.getNodeInfo(node.parentId).toPromise(); const parentNode = await this.contentApi
.getNodeInfo(node.parentId)
.toPromise();
node.name = parentNode.properties['cm:title'] || parentNode.name; node.name = parentNode.properties['cm:title'] || parentNode.name;
// remove the site entry // remove the site entry
@@ -251,7 +267,9 @@ export class FilesComponent extends PageComponent implements OnInit, OnDestroy {
const docLib = elements.findIndex(el => el.name === 'documentLibrary'); const docLib = elements.findIndex(el => el.name === 'documentLibrary');
if (docLib > -1) { if (docLib > -1) {
const siteFragment = elements[docLib - 1]; const siteFragment = elements[docLib - 1];
const siteNode = await this.contentApi.getNodeInfo(siteFragment.id).toPromise(); const siteNode = await this.contentApi
.getNodeInfo(siteFragment.id)
.toPromise();
// apply Site Name to the parent fragment // apply Site Name to the parent fragment
siteFragment.name = siteNode.properties['cm:title'] || siteNode.name; siteFragment.name = siteNode.properties['cm:title'] || siteNode.name;
@@ -268,7 +286,12 @@ export class FilesComponent extends PageComponent implements OnInit, OnDestroy {
} }
isRootNode(nodeId: string): boolean { isRootNode(nodeId: string): boolean {
if (this.node && this.node.path && this.node.path.elements && this.node.path.elements.length > 0) { if (
this.node &&
this.node.path &&
this.node.path.elements &&
this.node.path.elements.length > 0
) {
return this.node.path.elements[0].id === nodeId; return this.node.path.elements[0].id === nodeId;
} }
return false; return false;

View File

@@ -23,7 +23,11 @@
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>. * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/ */
import { Component, ViewEncapsulation, ChangeDetectionStrategy } from '@angular/core'; import {
Component,
ViewEncapsulation,
ChangeDetectionStrategy
} from '@angular/core';
@Component({ @Component({
selector: 'aca-generic-error', selector: 'aca-generic-error',
@@ -33,4 +37,3 @@ import { Component, ViewEncapsulation, ChangeDetectionStrategy } from '@angular/
host: { class: 'aca-generic-error' } host: { class: 'aca-generic-error' }
}) })
export class GenericErrorComponent {} export class GenericErrorComponent {}

View File

@@ -34,8 +34,10 @@ import { SidebarTabRef } from '@alfresco/adf-extensions';
templateUrl: './info-drawer.component.html' templateUrl: './info-drawer.component.html'
}) })
export class InfoDrawerComponent implements OnChanges, OnInit { export class InfoDrawerComponent implements OnChanges, OnInit {
@Input() nodeId: string; @Input()
@Input() node: MinimalNodeEntity; nodeId: string;
@Input()
node: MinimalNodeEntity;
isLoading = false; isLoading = false;
displayNode: MinimalNodeEntryEntity; displayNode: MinimalNodeEntryEntity;
@@ -89,7 +91,7 @@ export class InfoDrawerComponent implements OnChanges, OnInit {
this.setDisplayNode(entity); this.setDisplayNode(entity);
this.isLoading = false; this.isLoading = false;
}, },
() => this.isLoading = false () => (this.isLoading = false)
); );
} }
} }

View File

@@ -38,7 +38,7 @@ import { NodePermissionService } from '../../../services/node-permission.service
</adf-content-metadata-card> </adf-content-metadata-card>
`, `,
encapsulation: ViewEncapsulation.None, encapsulation: ViewEncapsulation.None,
host: { 'class': 'app-metadata-tab' } host: { class: 'app-metadata-tab' }
}) })
export class MetadataTabComponent { export class MetadataTabComponent {
@Input() @Input()

View File

@@ -39,10 +39,7 @@ describe('LayoutComponent', () => {
beforeEach(() => { beforeEach(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
imports: [AppTestingModule], imports: [AppTestingModule],
declarations: [ declarations: [LayoutComponent, SidenavViewsManagerDirective],
LayoutComponent,
SidenavViewsManagerDirective
],
schemas: [NO_ERRORS_SCHEMA] schemas: [NO_ERRORS_SCHEMA]
}); });

View File

@@ -91,15 +91,11 @@ export class LayoutComponent implements OnInit, OnDestroy {
.pipe(takeUntil(this.onDestroy$)) .pipe(takeUntil(this.onDestroy$))
.subscribe(node => { .subscribe(node => {
this.node = node; this.node = node;
this.canUpload = this.canUpload = node && this.permission.check(node, ['create']);
node && this.permission.check(node, ['create']);
}); });
this.breakpointObserver this.breakpointObserver
.observe([ .observe([Breakpoints.HandsetPortrait, Breakpoints.HandsetLandscape])
Breakpoints.HandsetPortrait,
Breakpoints.HandsetLandscape
])
.subscribe(result => { .subscribe(result => {
this.isSmallScreen = result.matches; this.isSmallScreen = result.matches;
}); });

View File

@@ -12,7 +12,8 @@ import { filter } from 'rxjs/operators';
exportAs: 'acaSidenavManager' exportAs: 'acaSidenavManager'
}) })
export class SidenavViewsManagerDirective { export class SidenavViewsManagerDirective {
@ContentChild(SidenavLayoutComponent) sidenavLayout: SidenavLayoutComponent; @ContentChild(SidenavLayoutComponent)
sidenavLayout: SidenavLayoutComponent;
minimizeSidenav = false; minimizeSidenav = false;
hideSidenav = false; hideSidenav = false;
@@ -81,10 +82,8 @@ export class SidenavViewsManagerDirective {
if (preserveState) { if (preserveState) {
return ( return (
this.userPreferenceService.get( this.userPreferenceService.get('expandedSidenav', expand.toString()) ===
'expandedSidenav', 'true'
expand.toString()
) === 'true'
); );
} }

View File

@@ -29,7 +29,11 @@ import { NO_ERRORS_SCHEMA } from '@angular/core';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { import {
AlfrescoApiService, AlfrescoApiService,
TimeAgoPipe, NodeNameTooltipPipe, NodeFavoriteDirective, DataTableComponent, AppConfigPipe TimeAgoPipe,
NodeNameTooltipPipe,
NodeFavoriteDirective,
DataTableComponent,
AppConfigPipe
} from '@alfresco/adf-core'; } from '@alfresco/adf-core';
import { DocumentListComponent } from '@alfresco/adf-content-services'; import { DocumentListComponent } from '@alfresco/adf-content-services';
import { ShareDataTableAdapter } from '@alfresco/adf-content-services'; import { ShareDataTableAdapter } from '@alfresco/adf-content-services';
@@ -86,8 +90,12 @@ describe('LibrariesComponent', () => {
alfrescoApi.reset(); alfrescoApi.reset();
router = TestBed.get(Router); router = TestBed.get(Router);
spyOn(alfrescoApi.sitesApi, 'getSites').and.returnValue((Promise.resolve(page))); spyOn(alfrescoApi.sitesApi, 'getSites').and.returnValue(
spyOn(alfrescoApi.peopleApi, 'getSiteMembership').and.returnValue((Promise.resolve({}))); Promise.resolve(page)
);
spyOn(alfrescoApi.peopleApi, 'getSiteMembership').and.returnValue(
Promise.resolve({})
);
contentApi = TestBed.get(ContentApiService); contentApi = TestBed.get(ContentApiService);
}); });
@@ -119,7 +127,9 @@ describe('LibrariesComponent', () => {
node.title = 'title'; node.title = 'title';
const data = new ShareDataTableAdapter(null, null); const data = new ShareDataTableAdapter(null, null);
data.setRows([<any>{ node: { entry: { id: 'some-id', title: 'title' } } }]); data.setRows([
<any>{ node: { entry: { id: 'some-id', title: 'title' } } }
]);
component.documentList.data = data; component.documentList.data = data;
@@ -131,7 +141,9 @@ describe('LibrariesComponent', () => {
node.title = 'title'; node.title = 'title';
const data = new ShareDataTableAdapter(null, null); const data = new ShareDataTableAdapter(null, null);
data.setRows([<any>{ node: { entry: { id: 'some-id', title: 'title-some-id' } } }]); data.setRows([
<any>{ node: { entry: { id: 'some-id', title: 'title-some-id' } } }
]);
component.documentList.data = data; component.documentList.data = data;
const title = component.makeLibraryTitle(node); const title = component.makeLibraryTitle(node);

View File

@@ -41,16 +41,17 @@ import { map } from 'rxjs/operators';
templateUrl: './libraries.component.html' templateUrl: './libraries.component.html'
}) })
export class LibrariesComponent extends PageComponent implements OnInit { export class LibrariesComponent extends PageComponent implements OnInit {
isSmallScreen = false; isSmallScreen = false;
constructor(private route: ActivatedRoute, constructor(
private route: ActivatedRoute,
content: ContentManagementService, content: ContentManagementService,
private contentApi: ContentApiService, private contentApi: ContentApiService,
store: Store<AppStore>, store: Store<AppStore>,
extensions: AppExtensionService, extensions: AppExtensionService,
private router: Router, private router: Router,
private breakpointObserver: BreakpointObserver) { private breakpointObserver: BreakpointObserver
) {
super(store, extensions, content); super(store, extensions, content);
} }
@@ -64,10 +65,7 @@ export class LibrariesComponent extends PageComponent implements OnInit {
}), }),
this.breakpointObserver this.breakpointObserver
.observe([ .observe([Breakpoints.HandsetPortrait, Breakpoints.HandsetLandscape])
Breakpoints.HandsetPortrait,
Breakpoints.HandsetLandscape
])
.subscribe(result => { .subscribe(result => {
this.isSmallScreen = result.matches; this.isSmallScreen = result.matches;
}) })
@@ -88,9 +86,8 @@ export class LibrariesComponent extends PageComponent implements OnInit {
let isDuplicate = false; let isDuplicate = false;
if (entries) { if (entries) {
isDuplicate = entries isDuplicate = entries.some((entry: any) => {
.some((entry: any) => { return entry.id !== id && entry.title === title;
return (entry.id !== id && entry.title === title);
}); });
} }
@@ -109,7 +106,9 @@ export class LibrariesComponent extends PageComponent implements OnInit {
.getNode(libraryId, { relativePath: '/documentLibrary' }) .getNode(libraryId, { relativePath: '/documentLibrary' })
.pipe(map(node => node.entry)) .pipe(map(node => node.entry))
.subscribe(documentLibrary => { .subscribe(documentLibrary => {
this.router.navigate([ './', documentLibrary.id ], { relativeTo: this.route }); this.router.navigate(['./', documentLibrary.id], {
relativeTo: this.route
});
}); });
} }
} }

View File

@@ -23,7 +23,14 @@
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>. * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/ */
import { Component, Input, ChangeDetectionStrategy, OnInit, ViewEncapsulation, HostListener } from '@angular/core'; import {
Component,
Input,
ChangeDetectionStrategy,
OnInit,
ViewEncapsulation,
HostListener
} from '@angular/core';
import { PathInfo, MinimalNodeEntity } from 'alfresco-js-api'; import { PathInfo, MinimalNodeEntity } from 'alfresco-js-api';
import { Observable, BehaviorSubject, of } from 'rxjs'; import { Observable, BehaviorSubject, of } from 'rxjs';
@@ -41,7 +48,7 @@ import { ContentApiService } from '../../services/content-api.service';
`, `,
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
encapsulation: ViewEncapsulation.None, encapsulation: ViewEncapsulation.None,
host: { 'class': 'aca-location-link adf-location-cell' } host: { class: 'aca-location-link adf-location-cell' }
}) })
export class LocationLinkComponent implements OnInit { export class LocationLinkComponent implements OnInit {
private _path: PathInfo; private _path: PathInfo;
@@ -60,14 +67,15 @@ export class LocationLinkComponent implements OnInit {
@Input() @Input()
tooltip: Observable<string>; tooltip: Observable<string>;
@HostListener('mouseenter') onMouseEnter() { @HostListener('mouseenter')
onMouseEnter() {
this.getTooltip(this._path); this.getTooltip(this._path);
} }
constructor( constructor(
private store: Store<AppStore>, private store: Store<AppStore>,
private contentApi: ContentApiService) { private contentApi: ContentApiService
} ) {}
goToLocation() { goToLocation() {
if (this.context) { if (this.context) {
@@ -100,7 +108,11 @@ export class LocationLinkComponent implements OnInit {
} }
// for non-admin users // for non-admin users
if (elements.length === 3 && elements[0] === 'Company Home' && elements[1] === 'User Homes') { if (
elements.length === 3 &&
elements[0] === 'Company Home' &&
elements[1] === 'User Homes'
) {
return of('Personal Files'); return of('Personal Files');
} }
@@ -112,7 +124,9 @@ export class LocationLinkComponent implements OnInit {
return new Observable<string>(observer => { return new Observable<string>(observer => {
this.contentApi.getNodeInfo(fragment.id).subscribe( this.contentApi.getNodeInfo(fragment.id).subscribe(
node => { node => {
observer.next(node.properties['cm:title'] || node.name || fragment.name); observer.next(
node.properties['cm:title'] || node.name || fragment.name
);
observer.complete(); observer.complete();
}, },
() => { () => {
@@ -141,7 +155,8 @@ export class LocationLinkComponent implements OnInit {
this.contentApi.getNodeInfo(fragment.id).subscribe( this.contentApi.getNodeInfo(fragment.id).subscribe(
node => { node => {
elements.splice(0, 2); elements.splice(0, 2);
elements[0].name = node.properties['cm:title'] || node.name || fragment.name; elements[0].name =
node.properties['cm:title'] || node.name || fragment.name;
elements.splice(1, 1); elements.splice(1, 1);
elements.unshift({ id: null, name: 'File Libraries' }); elements.unshift({ id: null, name: 'File Libraries' });

View File

@@ -27,7 +27,11 @@ import { NO_ERRORS_SCHEMA } from '@angular/core';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { Location } from '@angular/common'; import { Location } from '@angular/common';
import { TestBed, ComponentFixture } from '@angular/core/testing'; import { TestBed, ComponentFixture } from '@angular/core/testing';
import { AuthenticationService, UserPreferencesService, AppConfigPipe } from '@alfresco/adf-core'; import {
AuthenticationService,
UserPreferencesService,
AppConfigPipe
} from '@alfresco/adf-core';
import { LoginComponent } from './login.component'; import { LoginComponent } from './login.component';
import { AppTestingModule } from '../../testing/app-testing.module'; import { AppTestingModule } from '../../testing/app-testing.module';
@@ -41,13 +45,8 @@ describe('LoginComponent', () => {
beforeEach(() => { beforeEach(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
imports: [AppTestingModule], imports: [AppTestingModule],
declarations: [ declarations: [LoginComponent, AppConfigPipe],
LoginComponent, providers: [Location],
AppConfigPipe
],
providers: [
Location
],
schemas: [NO_ERRORS_SCHEMA] schemas: [NO_ERRORS_SCHEMA]
}); });

View File

@@ -33,7 +33,7 @@ import { AuthenticationService } from '@alfresco/adf-core';
export class LoginComponent implements OnInit { export class LoginComponent implements OnInit {
constructor( constructor(
private location: Location, private location: Location,
private auth: AuthenticationService, private auth: AuthenticationService
) {} ) {}
ngOnInit() { ngOnInit() {

View File

@@ -23,7 +23,10 @@
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>. * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/ */
import { DocumentListComponent, ShareDataRow } from '@alfresco/adf-content-services'; import {
DocumentListComponent,
ShareDataRow
} from '@alfresco/adf-content-services';
import { ContentActionRef, SelectionState } from '@alfresco/adf-extensions'; import { ContentActionRef, SelectionState } from '@alfresco/adf-extensions';
import { OnDestroy, OnInit, ViewChild } from '@angular/core'; import { OnDestroy, OnInit, ViewChild } from '@angular/core';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
@@ -33,11 +36,16 @@ import { takeUntil } from 'rxjs/operators';
import { AppExtensionService } from '../extensions/extension.service'; import { AppExtensionService } from '../extensions/extension.service';
import { ContentManagementService } from '../services/content-management.service'; import { ContentManagementService } from '../services/content-management.service';
import { SetSelectedNodesAction, ViewFileAction } from '../store/actions'; import { SetSelectedNodesAction, ViewFileAction } from '../store/actions';
import { appSelection, currentFolder, documentDisplayMode, infoDrawerOpened, sharedUrl } from '../store/selectors/app.selectors'; import {
appSelection,
currentFolder,
documentDisplayMode,
infoDrawerOpened,
sharedUrl
} from '../store/selectors/app.selectors';
import { AppStore } from '../store/states/app.state'; import { AppStore } from '../store/states/app.state';
export abstract class PageComponent implements OnInit, OnDestroy { export abstract class PageComponent implements OnInit, OnDestroy {
onDestroy$: Subject<boolean> = new Subject<boolean>(); onDestroy$: Subject<boolean> = new Subject<boolean>();
@ViewChild(DocumentListComponent) @ViewChild(DocumentListComponent)
@@ -57,13 +65,17 @@ export abstract class PageComponent implements OnInit, OnDestroy {
protected subscriptions: Subscription[] = []; protected subscriptions: Subscription[] = [];
static isLockedNode(node) { static isLockedNode(node) {
return node.isLocked || (node.properties && node.properties['cm:lockType'] === 'READ_ONLY_LOCK'); return (
node.isLocked ||
(node.properties && node.properties['cm:lockType'] === 'READ_ONLY_LOCK')
);
} }
constructor( constructor(
protected store: Store<AppStore>, protected store: Store<AppStore>,
protected extensions: AppExtensionService, protected extensions: AppExtensionService,
protected content: ContentManagementService) {} protected content: ContentManagementService
) {}
ngOnInit() { ngOnInit() {
this.sharedPreviewUrl$ = this.store.select(sharedUrl); this.sharedPreviewUrl$ = this.store.select(sharedUrl);
@@ -77,10 +89,13 @@ export abstract class PageComponent implements OnInit, OnDestroy {
this.selection = selection; this.selection = selection;
this.actions = this.extensions.getAllowedToolbarActions(); this.actions = this.extensions.getAllowedToolbarActions();
this.viewerToolbarActions = this.extensions.getViewerToolbarActions(); this.viewerToolbarActions = this.extensions.getViewerToolbarActions();
this.canUpdateNode = this.selection.count === 1 && this.content.canUpdateNode(selection.first); this.canUpdateNode =
this.selection.count === 1 &&
this.content.canUpdateNode(selection.first);
}); });
this.store.select(currentFolder) this.store
.select(currentFolder)
.pipe(takeUntil(this.onDestroy$)) .pipe(takeUntil(this.onDestroy$))
.subscribe(node => { .subscribe(node => {
this.canUpload = node && this.content.canUploadContent(node); this.canUpload = node && this.content.canUploadContent(node);

View File

@@ -24,7 +24,10 @@
*/ */
import { Component, Input, OnInit, ViewChild } from '@angular/core'; import { Component, Input, OnInit, ViewChild } from '@angular/core';
import { NodePermissionDialogService, PermissionListComponent } from '@alfresco/adf-content-services'; import {
NodePermissionDialogService,
PermissionListComponent
} from '@alfresco/adf-content-services';
import { MinimalNodeEntryEntity } from 'alfresco-js-api'; import { MinimalNodeEntryEntity } from 'alfresco-js-api';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
import { AppStore } from '../../store/states/app.state'; import { AppStore } from '../../store/states/app.state';
@@ -51,11 +54,12 @@ export class PermissionsManagerComponent implements OnInit {
private dialog: MatDialog, private dialog: MatDialog,
private contentApi: ContentApiService, private contentApi: ContentApiService,
private nodePermissionDialogService: NodePermissionDialogService private nodePermissionDialogService: NodePermissionDialogService
) { ) {}
}
ngOnInit() { ngOnInit() {
this.contentApi.getNodeInfo(this.nodeId, {include: ['permissions'] }).subscribe( (currentNode: MinimalNodeEntryEntity) => { this.contentApi
.getNodeInfo(this.nodeId, { include: ['permissions'] })
.subscribe((currentNode: MinimalNodeEntryEntity) => {
this.toggleStatus = currentNode.permissions.isInheritanceEnabled; this.toggleStatus = currentNode.permissions.isInheritanceEnabled;
}); });
} }
@@ -74,16 +78,17 @@ export class PermissionsManagerComponent implements OnInit {
} }
openAddPermissionDialog(event: Event) { openAddPermissionDialog(event: Event) {
this.nodePermissionDialogService.updateNodePermissionByDialog(this.nodeId) this.nodePermissionDialogService
.subscribe(() => { .updateNodePermissionByDialog(this.nodeId)
.subscribe(
() => {
this.dialog.open(NodePermissionsDialogComponent, { this.dialog.open(NodePermissionsDialogComponent, {
data: { nodeId: this.nodeId }, data: { nodeId: this.nodeId },
panelClass: 'aca-permissions-dialog-panel', panelClass: 'aca-permissions-dialog-panel',
width: '800px' width: '800px'
} });
);
}, },
(error) => { error => {
this.store.dispatch(new SnackbarErrorAction(error)); this.store.dispatch(new SnackbarErrorAction(error));
} }
); );

View File

@@ -26,7 +26,11 @@
import { NO_ERRORS_SCHEMA } from '@angular/core'; import { NO_ERRORS_SCHEMA } from '@angular/core';
import { Router, ActivatedRoute } from '@angular/router'; import { Router, ActivatedRoute } from '@angular/router';
import { TestBed, ComponentFixture } from '@angular/core/testing'; import { TestBed, ComponentFixture } from '@angular/core/testing';
import { UserPreferencesService, AppConfigPipe, NodeFavoriteDirective } from '@alfresco/adf-core'; import {
UserPreferencesService,
AppConfigPipe,
NodeFavoriteDirective
} from '@alfresco/adf-core';
import { PreviewComponent } from './preview.component'; import { PreviewComponent } from './preview.component';
import { of, throwError } from 'rxjs'; import { of, throwError } from 'rxjs';
import { EffectsModule } from '@ngrx/effects'; import { EffectsModule } from '@ngrx/effects';
@@ -36,7 +40,6 @@ import { AppTestingModule } from '../../testing/app-testing.module';
import { ContentApiService } from '../../services/content-api.service'; import { ContentApiService } from '../../services/content-api.service';
describe('PreviewComponent', () => { describe('PreviewComponent', () => {
let fixture: ComponentFixture<PreviewComponent>; let fixture: ComponentFixture<PreviewComponent>;
let component: PreviewComponent; let component: PreviewComponent;
let router: Router; let router: Router;
@@ -46,10 +49,7 @@ describe('PreviewComponent', () => {
beforeEach(() => { beforeEach(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
imports: [ imports: [AppTestingModule, EffectsModule.forRoot([NodeEffects])],
AppTestingModule,
EffectsModule.forRoot([NodeEffects])
],
declarations: [ declarations: [
AppConfigPipe, AppConfigPipe,
PreviewComponent, PreviewComponent,
@@ -83,9 +83,12 @@ describe('PreviewComponent', () => {
component.previousNodeId = 'previous1'; component.previousNodeId = 'previous1';
component.onNavigateBefore(); component.onNavigateBefore();
expect(router.navigate).toHaveBeenCalledWith( expect(router.navigate).toHaveBeenCalledWith([
['personal-files', 'folder1', 'preview', 'previous1'] 'personal-files',
); 'folder1',
'preview',
'previous1'
]);
}); });
it('should navigate back to previous node in the root path', () => { it('should navigate back to previous node in the root path', () => {
@@ -96,9 +99,11 @@ describe('PreviewComponent', () => {
component.previousNodeId = 'previous1'; component.previousNodeId = 'previous1';
component.onNavigateBefore(); component.onNavigateBefore();
expect(router.navigate).toHaveBeenCalledWith( expect(router.navigate).toHaveBeenCalledWith([
['personal-files', 'preview', 'previous1'] 'personal-files',
); 'preview',
'previous1'
]);
}); });
it('should not navigate back if node unset', () => { it('should not navigate back if node unset', () => {
@@ -118,9 +123,12 @@ describe('PreviewComponent', () => {
component.nextNodeId = 'next1'; component.nextNodeId = 'next1';
component.onNavigateNext(); component.onNavigateNext();
expect(router.navigate).toHaveBeenCalledWith( expect(router.navigate).toHaveBeenCalledWith([
['personal-files', 'folder1', 'preview', 'next1'] 'personal-files',
); 'folder1',
'preview',
'next1'
]);
}); });
it('should navigate to next node in the root path', () => { it('should navigate to next node in the root path', () => {
@@ -131,9 +139,11 @@ describe('PreviewComponent', () => {
component.nextNodeId = 'next1'; component.nextNodeId = 'next1';
component.onNavigateNext(); component.onNavigateNext();
expect(router.navigate).toHaveBeenCalledWith( expect(router.navigate).toHaveBeenCalledWith([
['personal-files', 'preview', 'next1'] 'personal-files',
); 'preview',
'next1'
]);
}); });
it('should not navigate back if node unset', () => { it('should not navigate back if node unset', () => {
@@ -148,33 +158,37 @@ describe('PreviewComponent', () => {
it('should generate preview path for a folder only', () => { it('should generate preview path for a folder only', () => {
component.previewLocation = 'personal-files'; component.previewLocation = 'personal-files';
expect(component.getPreviewPath('folder1', null)).toEqual( expect(component.getPreviewPath('folder1', null)).toEqual([
['personal-files', 'folder1'] 'personal-files',
); 'folder1'
]);
}); });
it('should generate preview path for a folder and a node', () => { it('should generate preview path for a folder and a node', () => {
component.previewLocation = 'personal-files'; component.previewLocation = 'personal-files';
expect(component.getPreviewPath('folder1', 'node1')).toEqual( expect(component.getPreviewPath('folder1', 'node1')).toEqual([
['personal-files', 'folder1', 'preview', 'node1'] 'personal-files',
); 'folder1',
'preview',
'node1'
]);
}); });
it('should generate preview path for a node only', () => { it('should generate preview path for a node only', () => {
component.previewLocation = 'personal-files'; component.previewLocation = 'personal-files';
expect(component.getPreviewPath(null, 'node1')).toEqual( expect(component.getPreviewPath(null, 'node1')).toEqual([
['personal-files', 'preview', 'node1'] 'personal-files',
); 'preview',
'node1'
]);
}); });
it('should generate preview for the location only', () => { it('should generate preview for the location only', () => {
component.previewLocation = 'personal-files'; component.previewLocation = 'personal-files';
expect(component.getPreviewPath(null, null)).toEqual( expect(component.getPreviewPath(null, null)).toEqual(['personal-files']);
['personal-files']
);
}); });
it('should navigate back to root path upon close', () => { it('should navigate back to root path upon close', () => {
@@ -186,9 +200,7 @@ describe('PreviewComponent', () => {
component.onVisibilityChanged(false); component.onVisibilityChanged(false);
expect(router.navigate).toHaveBeenCalledWith( expect(router.navigate).toHaveBeenCalledWith(['libraries', {}]);
['libraries', {}]
);
}); });
it('should navigate back to folder path upon close', () => { it('should navigate back to folder path upon close', () => {
@@ -200,9 +212,7 @@ describe('PreviewComponent', () => {
component.onVisibilityChanged(false); component.onVisibilityChanged(false);
expect(router.navigate).toHaveBeenCalledWith( expect(router.navigate).toHaveBeenCalledWith(['libraries', {}, 'site1']);
['libraries', {}, 'site1']
);
}); });
it('should not navigate to root path for certain routes upon close', () => { it('should not navigate to root path for certain routes upon close', () => {
@@ -214,9 +224,7 @@ describe('PreviewComponent', () => {
component.onVisibilityChanged(false); component.onVisibilityChanged(false);
expect(router.navigate).toHaveBeenCalledWith( expect(router.navigate).toHaveBeenCalledWith(['shared', {}]);
['shared', {}]
);
}); });
it('should not navigate back if viewer is still visible', () => { it('should not navigate back if viewer is still visible', () => {
@@ -315,9 +323,9 @@ describe('PreviewComponent', () => {
}); });
it('should return nearest nodes', async () => { it('should return nearest nodes', async () => {
spyOn(component, 'getFileIds').and.returnValue(Promise.resolve( spyOn(component, 'getFileIds').and.returnValue(
[ 'node1', 'node2', 'node3', 'node4', 'node5' ] Promise.resolve(['node1', 'node2', 'node3', 'node4', 'node5'])
)); );
let nearest = await component.getNearestNodes('node1', 'folder1'); let nearest = await component.getNearestNodes('node1', 'folder1');
expect(nearest).toEqual({ left: null, right: 'node2' }); expect(nearest).toEqual({ left: null, right: 'node2' });
@@ -330,9 +338,9 @@ describe('PreviewComponent', () => {
}); });
it('should return empty nearest nodes if node is already deleted', async () => { it('should return empty nearest nodes if node is already deleted', async () => {
spyOn(component, 'getFileIds').and.returnValue(Promise.resolve( spyOn(component, 'getFileIds').and.returnValue(
[ 'node1', 'node2', 'node3', 'node4', 'node5' ] Promise.resolve(['node1', 'node2', 'node3', 'node4', 'node5'])
)); );
const nearest = await component.getNearestNodes('node9', 'folder1'); const nearest = await component.getNearestNodes('node9', 'folder1');
expect(nearest).toEqual({ left: null, right: null }); expect(nearest).toEqual({ left: null, right: null });
@@ -340,9 +348,7 @@ describe('PreviewComponent', () => {
it('should not display node when id is missing', async () => { it('should not display node when id is missing', async () => {
spyOn(router, 'navigate').and.stub(); spyOn(router, 'navigate').and.stub();
spyOn(contentApi, 'getNodeInfo').and.returnValue( spyOn(contentApi, 'getNodeInfo').and.returnValue(of(null));
of(null)
);
await component.displayNode(null); await component.displayNode(null);
@@ -352,9 +358,7 @@ describe('PreviewComponent', () => {
it('should navigate to original location if node not found', async () => { it('should navigate to original location if node not found', async () => {
spyOn(router, 'navigate').and.stub(); spyOn(router, 'navigate').and.stub();
spyOn(contentApi, 'getNodeInfo').and.returnValue( spyOn(contentApi, 'getNodeInfo').and.returnValue(of(null));
of(null)
);
component.previewLocation = 'personal-files'; component.previewLocation = 'personal-files';
await component.displayNode('folder1'); await component.displayNode('folder1');
@@ -380,9 +384,7 @@ describe('PreviewComponent', () => {
it('should navigate to original location in case of Alfresco API errors', async () => { it('should navigate to original location in case of Alfresco API errors', async () => {
spyOn(router, 'navigate').and.stub(); spyOn(router, 'navigate').and.stub();
spyOn(contentApi, 'getNodeInfo').and.returnValue( spyOn(contentApi, 'getNodeInfo').and.returnValue(throwError('error'));
throwError('error')
);
component.previewLocation = 'personal-files'; component.previewLocation = 'personal-files';
await component.displayNode('folder1'); await component.displayNode('folder1');
@@ -412,7 +414,10 @@ describe('PreviewComponent', () => {
it('should setup node for displaying', async () => { it('should setup node for displaying', async () => {
spyOn(router, 'navigate').and.stub(); spyOn(router, 'navigate').and.stub();
spyOn(component, 'getNearestNodes').and.returnValue({ left: 'node1', right: 'node3' }); spyOn(component, 'getNearestNodes').and.returnValue({
left: 'node1',
right: 'node3'
});
spyOn(contentApi, 'getNodeInfo').and.returnValue( spyOn(contentApi, 'getNodeInfo').and.returnValue(
of({ of({
id: 'node2', id: 'node2',
@@ -559,9 +564,21 @@ describe('PreviewComponent', () => {
of({ of({
list: { list: {
entries: [ entries: [
{ entry: { target: { file: { id: 'file3', modifiedAt: new Date(3) } } } }, {
{ entry: { target: { file: { id: 'file1', modifiedAt: new Date(1) } } } }, entry: {
{ entry: { target: { file: { id: 'file2', modifiedAt: new Date(2) } } } } target: { file: { id: 'file3', modifiedAt: new Date(3) } }
}
},
{
entry: {
target: { file: { id: 'file1', modifiedAt: new Date(1) } }
}
},
{
entry: {
target: { file: { id: 'file2', modifiedAt: new Date(2) } }
}
}
] ]
} }
}) })
@@ -579,8 +596,20 @@ describe('PreviewComponent', () => {
of({ of({
list: { list: {
entries: [ entries: [
{ entry: { nodeId: 'node2', name: 'node 2', modifiedAt: new Date(2) } }, {
{ entry: { nodeId: 'node1', name: 'node 1', modifiedAt: new Date(1) } } entry: {
nodeId: 'node2',
name: 'node 2',
modifiedAt: new Date(2)
}
},
{
entry: {
nodeId: 'node1',
name: 'node 1',
modifiedAt: new Date(1)
}
}
] ]
} }
}) })
@@ -597,8 +626,20 @@ describe('PreviewComponent', () => {
of({ of({
list: { list: {
entries: [ entries: [
{ entry: { nodeId: 'node2', name: 'node 2', modifiedAt: new Date(2) } }, {
{ entry: { nodeId: 'node1', name: 'node 1', modifiedAt: new Date(1) } } entry: {
nodeId: 'node2',
name: 'node 2',
modifiedAt: new Date(2)
}
},
{
entry: {
nodeId: 'node1',
name: 'node 1',
modifiedAt: new Date(1)
}
}
] ]
} }
}) })

View File

@@ -24,7 +24,14 @@
*/ */
import { Component, OnInit, ViewEncapsulation } from '@angular/core'; import { Component, OnInit, ViewEncapsulation } from '@angular/core';
import { ActivatedRoute, Router, UrlTree, UrlSegmentGroup, UrlSegment, PRIMARY_OUTLET } from '@angular/router'; import {
ActivatedRoute,
Router,
UrlTree,
UrlSegmentGroup,
UrlSegment,
PRIMARY_OUTLET
} from '@angular/router';
import { UserPreferencesService, ObjectUtils } from '@alfresco/adf-core'; import { UserPreferencesService, ObjectUtils } from '@alfresco/adf-core';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
import { AppStore } from '../../store/states/app.state'; import { AppStore } from '../../store/states/app.state';
@@ -41,14 +48,19 @@ import { ViewUtilService } from './view-util.service';
templateUrl: 'preview.component.html', templateUrl: 'preview.component.html',
styleUrls: ['preview.component.scss'], styleUrls: ['preview.component.scss'],
encapsulation: ViewEncapsulation.None, encapsulation: ViewEncapsulation.None,
host: { 'class': 'app-preview' } host: { class: 'app-preview' }
}) })
export class PreviewComponent extends PageComponent implements OnInit { export class PreviewComponent extends PageComponent implements OnInit {
previewLocation: string = null; previewLocation: string = null;
routesSkipNavigation = ['shared', 'recent-files', 'favorites']; routesSkipNavigation = ['shared', 'recent-files', 'favorites'];
navigateSource: string = null; navigateSource: string = null;
navigationSources = ['favorites', 'libraries', 'personal-files', 'recent-files', 'shared']; navigationSources = [
'favorites',
'libraries',
'personal-files',
'recent-files',
'shared'
];
folderId: string = null; folderId: string = null;
nodeId: string = null; nodeId: string = null;
previousNodeId: string; previousNodeId: string;
@@ -65,7 +77,8 @@ export class PreviewComponent extends PageComponent implements OnInit {
private viewUtils: ViewUtilService, private viewUtils: ViewUtilService,
store: Store<AppStore>, store: Store<AppStore>,
extensions: AppExtensionService, extensions: AppExtensionService,
content: ContentManagementService) { content: ContentManagementService
) {
super(store, extensions, content); super(store, extensions, content);
} }
@@ -112,7 +125,10 @@ export class PreviewComponent extends PageComponent implements OnInit {
this.store.dispatch(new SetSelectedNodesAction([{ entry: this.node }])); this.store.dispatch(new SetSelectedNodesAction([{ entry: this.node }]));
if (this.node && this.node.isFile) { if (this.node && this.node.isFile) {
const nearest = await this.getNearestNodes(this.node.id, this.node.parentId); const nearest = await this.getNearestNodes(
this.node.id,
this.node.parentId
);
this.previousNodeId = nearest.left; this.previousNodeId = nearest.left;
this.nextNodeId = nearest.right; this.nextNodeId = nearest.right;
@@ -133,7 +149,9 @@ export class PreviewComponent extends PageComponent implements OnInit {
* @param isVisible Indicator whether Viewer is visible or hidden. * @param isVisible Indicator whether Viewer is visible or hidden.
*/ */
onVisibilityChanged(isVisible: boolean): void { onVisibilityChanged(isVisible: boolean): void {
const shouldSkipNavigation = this.routesSkipNavigation.includes(this.previewLocation); const shouldSkipNavigation = this.routesSkipNavigation.includes(
this.previewLocation
);
if (!isVisible) { if (!isVisible) {
const route = this.getNavigationCommands(this.previewLocation); const route = this.getNavigationCommands(this.previewLocation);
@@ -158,9 +176,7 @@ export class PreviewComponent extends PageComponent implements OnInit {
/** Handles navigation to a next document */ /** Handles navigation to a next document */
onNavigateNext(): void { onNavigateNext(): void {
if (this.nextNodeId) { if (this.nextNodeId) {
this.router.navigate( this.router.navigate(this.getPreviewPath(this.folderId, this.nextNodeId));
this.getPreviewPath(this.folderId, this.nextNodeId)
);
} }
} }
@@ -183,13 +199,15 @@ export class PreviewComponent extends PageComponent implements OnInit {
return route; return route;
} }
/** /**
* Retrieves nearest node information for the given node and folder. * Retrieves nearest node information for the given node and folder.
* @param nodeId Unique identifier of the document node * @param nodeId Unique identifier of the document node
* @param folderId Unique identifier of the containing folder node. * @param folderId Unique identifier of the containing folder node.
*/ */
async getNearestNodes(nodeId: string, folderId: string): Promise<{ left: string, right: string }> { async getNearestNodes(
nodeId: string,
folderId: string
): Promise<{ left: string; right: string }> {
const empty = { const empty = {
left: null, left: null,
right: null right: null
@@ -223,13 +241,17 @@ export class PreviewComponent extends PageComponent implements OnInit {
*/ */
async getFileIds(source: string, folderId?: string): Promise<string[]> { async getFileIds(source: string, folderId?: string): Promise<string[]> {
if ((source === 'personal-files' || source === 'libraries') && folderId) { if ((source === 'personal-files' || source === 'libraries') && folderId) {
const sortKey = this.preferences.get('personal-files.sorting.key') || 'modifiedAt'; const sortKey =
const sortDirection = this.preferences.get('personal-files.sorting.direction') || 'desc'; this.preferences.get('personal-files.sorting.key') || 'modifiedAt';
const nodes = await this.contentApi.getNodeChildren(folderId, { const sortDirection =
this.preferences.get('personal-files.sorting.direction') || 'desc';
const nodes = await this.contentApi
.getNodeChildren(folderId, {
// orderBy: `${sortKey} ${sortDirection}`, // orderBy: `${sortKey} ${sortDirection}`,
fields: ['id', this.getRootField(sortKey)], fields: ['id', this.getRootField(sortKey)],
where: '(isFile=true)' where: '(isFile=true)'
}).toPromise(); })
.toPromise();
const entries = nodes.list.entries.map(obj => obj.entry); const entries = nodes.list.entries.map(obj => obj.entry);
this.sort(entries, sortKey, sortDirection); this.sort(entries, sortKey, sortDirection);
@@ -238,13 +260,17 @@ export class PreviewComponent extends PageComponent implements OnInit {
} }
if (source === 'favorites') { if (source === 'favorites') {
const nodes = await this.contentApi.getFavorites('-me-', { const nodes = await this.contentApi
.getFavorites('-me-', {
where: '(EXISTS(target/file))', where: '(EXISTS(target/file))',
fields: ['target'] fields: ['target']
}).toPromise(); })
.toPromise();
const sortKey = this.preferences.get('favorites.sorting.key') || 'modifiedAt'; const sortKey =
const sortDirection = this.preferences.get('favorites.sorting.direction') || 'desc'; this.preferences.get('favorites.sorting.key') || 'modifiedAt';
const sortDirection =
this.preferences.get('favorites.sorting.direction') || 'desc';
const files = nodes.list.entries.map(obj => obj.entry.target.file); const files = nodes.list.entries.map(obj => obj.entry.target.file);
this.sort(files, sortKey, sortDirection); this.sort(files, sortKey, sortDirection);
@@ -252,12 +278,16 @@ export class PreviewComponent extends PageComponent implements OnInit {
} }
if (source === 'shared') { if (source === 'shared') {
const sortingKey = this.preferences.get('shared.sorting.key') || 'modifiedAt'; const sortingKey =
const sortingDirection = this.preferences.get('shared.sorting.direction') || 'desc'; this.preferences.get('shared.sorting.key') || 'modifiedAt';
const sortingDirection =
this.preferences.get('shared.sorting.direction') || 'desc';
const nodes = await this.contentApi.findSharedLinks({ const nodes = await this.contentApi
.findSharedLinks({
fields: ['nodeId', this.getRootField(sortingKey)] fields: ['nodeId', this.getRootField(sortingKey)]
}).toPromise(); })
.toPromise();
const entries = nodes.list.entries.map(obj => obj.entry); const entries = nodes.list.entries.map(obj => obj.entry);
this.sort(entries, sortingKey, sortingDirection); this.sort(entries, sortingKey, sortingDirection);
@@ -268,10 +298,13 @@ export class PreviewComponent extends PageComponent implements OnInit {
if (source === 'recent-files') { if (source === 'recent-files') {
const person = await this.contentApi.getPerson('-me-').toPromise(); const person = await this.contentApi.getPerson('-me-').toPromise();
const username = person.entry.id; const username = person.entry.id;
const sortingKey = this.preferences.get('recent-files.sorting.key') || 'modifiedAt'; const sortingKey =
const sortingDirection = this.preferences.get('recent-files.sorting.direction') || 'desc'; this.preferences.get('recent-files.sorting.key') || 'modifiedAt';
const sortingDirection =
this.preferences.get('recent-files.sorting.direction') || 'desc';
const nodes = await this.contentApi.search({ const nodes = await this.contentApi
.search({
query: { query: {
query: '*', query: '*',
language: 'afts' language: 'afts'
@@ -279,15 +312,20 @@ export class PreviewComponent extends PageComponent implements OnInit {
filterQueries: [ filterQueries: [
{ query: `cm:modified:[NOW/DAY-30DAYS TO NOW/DAY+1DAY]` }, { query: `cm:modified:[NOW/DAY-30DAYS TO NOW/DAY+1DAY]` },
{ query: `cm:modifier:${username} OR cm:creator:${username}` }, { query: `cm:modifier:${username} OR cm:creator:${username}` },
{ query: `TYPE:"content" AND -TYPE:"app:filelink" AND -TYPE:"fm:post"` } {
query: `TYPE:"content" AND -TYPE:"app:filelink" AND -TYPE:"fm:post"`
}
], ],
fields: ['id', this.getRootField(sortingKey)], fields: ['id', this.getRootField(sortingKey)],
sort: [{ sort: [
{
type: 'FIELD', type: 'FIELD',
field: 'cm:modified', field: 'cm:modified',
ascending: false ascending: false
}] }
}).toPromise(); ]
})
.toPromise();
const entries = nodes.list.entries.map(obj => obj.entry); const entries = nodes.list.entries.map(obj => obj.entry);
this.sort(entries, sortingKey, sortingDirection); this.sort(entries, sortingKey, sortingDirection);
@@ -308,14 +346,16 @@ export class PreviewComponent extends PageComponent implements OnInit {
items.sort((a: any, b: any) => { items.sort((a: any, b: any) => {
let left = ObjectUtils.getValue(a, key); let left = ObjectUtils.getValue(a, key);
if (left) { if (left) {
left = (left instanceof Date) ? left.valueOf().toString() : left.toString(); left =
left instanceof Date ? left.valueOf().toString() : left.toString();
} else { } else {
left = ''; left = '';
} }
let right = ObjectUtils.getValue(b, key); let right = ObjectUtils.getValue(b, key);
if (right) { if (right) {
right = (right instanceof Date) ? right.valueOf().toString() : right.toString(); right =
right instanceof Date ? right.valueOf().toString() : right.toString();
} else { } else {
right = ''; right = '';
} }
@@ -344,7 +384,8 @@ export class PreviewComponent extends PageComponent implements OnInit {
private getNavigationCommands(url: string): any[] { private getNavigationCommands(url: string): any[] {
const urlTree: UrlTree = this.router.parseUrl(url); const urlTree: UrlTree = this.router.parseUrl(url);
const urlSegmentGroup: UrlSegmentGroup = urlTree.root.children[PRIMARY_OUTLET]; const urlSegmentGroup: UrlSegmentGroup =
urlTree.root.children[PRIMARY_OUTLET];
if (!urlSegmentGroup) { if (!urlSegmentGroup) {
return [url]; return [url];

View File

@@ -54,15 +54,8 @@ const routes: Routes = [
CoreExtensionsModule.forChild(), CoreExtensionsModule.forChild(),
AppToolbarModule AppToolbarModule
], ],
declarations: [ declarations: [PreviewComponent, PreviewExtensionComponent],
PreviewComponent, providers: [ViewUtilService],
PreviewExtensionComponent exports: [PreviewComponent]
],
providers: [
ViewUtilService
],
exports: [
PreviewComponent
]
}) })
export class PreviewModule {} export class PreviewModule {}

View File

@@ -29,16 +29,36 @@ export class ViewUtilService {
* Mime-type grouping based on the ViewerComponent. * Mime-type grouping based on the ViewerComponent.
*/ */
private mimeTypes = { private mimeTypes = {
text: ['text/plain', 'text/csv', 'text/xml', 'text/html', 'application/x-javascript'], text: [
'text/plain',
'text/csv',
'text/xml',
'text/html',
'application/x-javascript'
],
pdf: ['application/pdf'], pdf: ['application/pdf'],
image: ['image/png', 'image/jpeg', 'image/gif', 'image/bmp', 'image/svg+xml'], image: [
media: ['video/mp4', 'video/webm', 'video/ogg', 'audio/mpeg', 'audio/ogg', 'audio/wav'] 'image/png',
'image/jpeg',
'image/gif',
'image/bmp',
'image/svg+xml'
],
media: [
'video/mp4',
'video/webm',
'video/ogg',
'audio/mpeg',
'audio/ogg',
'audio/wav'
]
}; };
constructor(private apiService: AlfrescoApiService, constructor(
private apiService: AlfrescoApiService,
private contentApi: ContentApiService, private contentApi: ContentApiService,
private logService: LogService) { private logService: LogService
} ) {}
/** /**
* This method takes a url to trigger the print dialog against, and the type of artifact that it * This method takes a url to trigger the print dialog against, and the type of artifact that it
@@ -86,10 +106,16 @@ export class ViewUtilService {
this.getRendition(nodeId, ViewUtilService.ContentGroup.PDF) this.getRendition(nodeId, ViewUtilService.ContentGroup.PDF)
.then(value => { .then(value => {
const url: string = this.getRenditionUrl(nodeId, type, (value ? true : false)); const url: string = this.getRenditionUrl(
const printType = (type === ViewUtilService.ContentGroup.PDF nodeId,
|| type === ViewUtilService.ContentGroup.TEXT) type,
? ViewUtilService.ContentGroup.PDF : type; value ? true : false
);
const printType =
type === ViewUtilService.ContentGroup.PDF ||
type === ViewUtilService.ContentGroup.TEXT
? ViewUtilService.ContentGroup.PDF
: type;
this.printFile(url, printType); this.printFile(url, printType);
}) })
.catch(err => { .catch(err => {
@@ -98,10 +124,17 @@ export class ViewUtilService {
}); });
} }
public getRenditionUrl(nodeId: string, type: string, renditionExists: boolean): string { public getRenditionUrl(
return (renditionExists && type !== ViewUtilService.ContentGroup.IMAGE) ? nodeId: string,
this.apiService.contentApi.getRenditionUrl(nodeId, ViewUtilService.ContentGroup.PDF) : type: string,
this.contentApi.getContentUrl(nodeId, false); renditionExists: boolean
): string {
return renditionExists && type !== ViewUtilService.ContentGroup.IMAGE
? this.apiService.contentApi.getRenditionUrl(
nodeId,
ViewUtilService.ContentGroup.PDF
)
: this.contentApi.getContentUrl(nodeId, false);
} }
/** /**
@@ -111,8 +144,15 @@ export class ViewUtilService {
* @param {number} retries * @param {number} retries
* @returns {Promise<AlfrescoApi.RenditionEntry>} * @returns {Promise<AlfrescoApi.RenditionEntry>}
*/ */
private async waitRendition(nodeId: string, renditionId: string, retries: number): Promise<RenditionEntry> { private async waitRendition(
const rendition = await this.apiService.renditionsApi.getRendition(nodeId, renditionId); nodeId: string,
renditionId: string,
retries: number
): Promise<RenditionEntry> {
const rendition = await this.apiService.renditionsApi.getRendition(
nodeId,
renditionId
);
if (this.maxRetries < retries) { if (this.maxRetries < retries) {
const status = rendition.entry.status.toString(); const status = rendition.entry.status.toString();
@@ -127,7 +167,6 @@ export class ViewUtilService {
} }
} }
/** /**
* From ViewerComponent * From ViewerComponent
* @param {string} mimeType * @param {string} mimeType
@@ -161,16 +200,23 @@ export class ViewUtilService {
* @param {string} nodeId * @param {string} nodeId
* @returns {string} * @returns {string}
*/ */
public async getRendition(nodeId: string, renditionId: string): Promise<RenditionEntry> { public async getRendition(
nodeId: string,
renditionId: string
): Promise<RenditionEntry> {
const supported = await this.apiService.renditionsApi.getRenditions(nodeId); const supported = await this.apiService.renditionsApi.getRenditions(nodeId);
let rendition = supported.list.entries.find(obj => obj.entry.id.toLowerCase() === renditionId); let rendition = supported.list.entries.find(
obj => obj.entry.id.toLowerCase() === renditionId
);
if (rendition) { if (rendition) {
const status = rendition.entry.status.toString(); const status = rendition.entry.status.toString();
if (status === 'NOT_CREATED') { if (status === 'NOT_CREATED') {
try { try {
await this.apiService.renditionsApi.createRendition(nodeId, {id: renditionId}); await this.apiService.renditionsApi.createRendition(nodeId, {
id: renditionId
});
rendition = await this.waitRendition(nodeId, renditionId, 0); rendition = await this.waitRendition(nodeId, renditionId, 0);
} catch (err) { } catch (err) {
this.logService.error(err); this.logService.error(err);
@@ -179,5 +225,4 @@ export class ViewUtilService {
} }
return new Promise(resolve => resolve(rendition)); return new Promise(resolve => resolve(rendition));
} }
} }

View File

@@ -27,7 +27,11 @@ import { TestBed, ComponentFixture } from '@angular/core/testing';
import { NO_ERRORS_SCHEMA } from '@angular/core'; import { NO_ERRORS_SCHEMA } from '@angular/core';
import { import {
AlfrescoApiService, AlfrescoApiService,
TimeAgoPipe, NodeNameTooltipPipe, NodeFavoriteDirective, DataTableComponent, AppConfigPipe TimeAgoPipe,
NodeNameTooltipPipe,
NodeFavoriteDirective,
DataTableComponent,
AppConfigPipe
} from '@alfresco/adf-core'; } from '@alfresco/adf-core';
import { DocumentListComponent } from '@alfresco/adf-content-services'; import { DocumentListComponent } from '@alfresco/adf-content-services';
import { ContentManagementService } from '../../services/content-management.service'; import { ContentManagementService } from '../../services/content-management.service';
@@ -54,9 +58,7 @@ describe('RecentFilesComponent', () => {
beforeEach(() => { beforeEach(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
imports: [ imports: [AppTestingModule],
AppTestingModule
],
declarations: [ declarations: [
DataTableComponent, DataTableComponent,
TimeAgoPipe, TimeAgoPipe,
@@ -77,11 +79,15 @@ describe('RecentFilesComponent', () => {
alfrescoApi = TestBed.get(AlfrescoApiService); alfrescoApi = TestBed.get(AlfrescoApiService);
alfrescoApi.reset(); alfrescoApi.reset();
spyOn(alfrescoApi.peopleApi, 'getPerson').and.returnValue(Promise.resolve({ spyOn(alfrescoApi.peopleApi, 'getPerson').and.returnValue(
Promise.resolve({
entry: { id: 'personId' } entry: { id: 'personId' }
})); })
);
spyOn(alfrescoApi.searchApi, 'search').and.returnValue(Promise.resolve(page)); spyOn(alfrescoApi.searchApi, 'search').and.returnValue(
Promise.resolve(page)
);
}); });
describe('OnInit()', () => { describe('OnInit()', () => {

View File

@@ -56,10 +56,7 @@ export class RecentFilesComponent extends PageComponent implements OnInit {
this.content.nodesRestored.subscribe(() => this.reload()), this.content.nodesRestored.subscribe(() => this.reload()),
this.breakpointObserver this.breakpointObserver
.observe([ .observe([Breakpoints.HandsetPortrait, Breakpoints.HandsetLandscape])
Breakpoints.HandsetPortrait,
Breakpoints.HandsetLandscape
])
.subscribe(result => { .subscribe(result => {
this.isSmallScreen = result.matches; this.isSmallScreen = result.matches;
}) })

View File

@@ -24,14 +24,36 @@
*/ */
import { ThumbnailService } from '@alfresco/adf-core'; import { ThumbnailService } from '@alfresco/adf-core';
import { animate, state, style, transition, trigger } from '@angular/animations'; import {
import { Component, EventEmitter, Input, OnDestroy, OnInit, Output, animate,
QueryList, ViewEncapsulation, ViewChild, ViewChildren, ElementRef, TemplateRef, ContentChild } from '@angular/core'; state,
style,
transition,
trigger
} from '@angular/animations';
import {
Component,
EventEmitter,
Input,
OnDestroy,
OnInit,
Output,
QueryList,
ViewEncapsulation,
ViewChild,
ViewChildren,
ElementRef,
TemplateRef,
ContentChild
} from '@angular/core';
import { MinimalNodeEntity, QueryBody } from 'alfresco-js-api'; import { MinimalNodeEntity, QueryBody } from 'alfresco-js-api';
import { Subject } from 'rxjs'; import { Subject } from 'rxjs';
import { MatListItem } from '@angular/material'; import { MatListItem } from '@angular/material';
import { debounceTime, filter } from 'rxjs/operators'; import { debounceTime, filter } from 'rxjs/operators';
import { EmptySearchResultComponent, SearchComponent } from '@alfresco/adf-content-services'; import {
EmptySearchResultComponent,
SearchComponent
} from '@alfresco/adf-content-services';
@Component({ @Component({
selector: 'app-search-input-control', selector: 'app-search-input-control',
@@ -39,20 +61,29 @@ import { EmptySearchResultComponent, SearchComponent } from '@alfresco/adf-conte
styleUrls: ['./search-input-control.component.scss'], styleUrls: ['./search-input-control.component.scss'],
animations: [ animations: [
trigger('transitionMessages', [ trigger('transitionMessages', [
state('active', style({ transform: 'translateX(0%)', 'margin-left': '13px' })), state(
'active',
style({ transform: 'translateX(0%)', 'margin-left': '13px' })
),
state('inactive', style({ transform: 'translateX(81%)' })), state('inactive', style({ transform: 'translateX(81%)' })),
state('no-animation', style({ transform: 'translateX(0%)', width: '100%' })), state(
transition('inactive => active', 'no-animation',
animate('300ms cubic-bezier(0.55, 0, 0.55, 0.2)')), style({ transform: 'translateX(0%)', width: '100%' })
transition('active => inactive', ),
animate('300ms cubic-bezier(0.55, 0, 0.55, 0.2)')) transition(
'inactive => active',
animate('300ms cubic-bezier(0.55, 0, 0.55, 0.2)')
),
transition(
'active => inactive',
animate('300ms cubic-bezier(0.55, 0, 0.55, 0.2)')
)
]) ])
], ],
encapsulation: ViewEncapsulation.None, encapsulation: ViewEncapsulation.None,
host: { class: 'adf-search-control' } host: { class: 'adf-search-control' }
}) })
export class SearchInputControlComponent implements OnInit, OnDestroy { export class SearchInputControlComponent implements OnInit, OnDestroy {
/** Toggles whether to use an expanding search control. If false /** Toggles whether to use an expanding search control. If false
* then a regular input is used. * then a regular input is used.
*/ */
@@ -123,15 +154,20 @@ export class SearchInputControlComponent implements OnInit, OnDestroy {
private focusSubject = new Subject<FocusEvent>(); private focusSubject = new Subject<FocusEvent>();
constructor(private thumbnailService: ThumbnailService) { constructor(private thumbnailService: ThumbnailService) {
this.toggleSearch
this.toggleSearch.asObservable().pipe(debounceTime(this.toggleDebounceTime)).subscribe(() => { .asObservable()
.pipe(debounceTime(this.toggleDebounceTime))
.subscribe(() => {
if (this.expandable && !this.skipToggle) { if (this.expandable && !this.skipToggle) {
this.subscriptAnimationState = this.subscriptAnimationState === 'inactive' ? 'active' : 'inactive'; this.subscriptAnimationState =
this.subscriptAnimationState === 'inactive' ? 'active' : 'inactive';
if (this.subscriptAnimationState === 'inactive') { if (this.subscriptAnimationState === 'inactive') {
this.searchTerm = ''; this.searchTerm = '';
this.searchAutocomplete.resetResults(); this.searchAutocomplete.resetResults();
if ( document.activeElement.id === this.searchInput.nativeElement.id) { if (
document.activeElement.id === this.searchInput.nativeElement.id
) {
this.searchInput.nativeElement.blur(); this.searchInput.nativeElement.blur();
} }
} }
@@ -147,7 +183,9 @@ export class SearchInputControlComponent implements OnInit, OnDestroy {
} }
ngOnInit() { ngOnInit() {
this.subscriptAnimationState = this.expandable ? 'inactive' : 'no-animation'; this.subscriptAnimationState = this.expandable
? 'inactive'
: 'no-animation';
this.setupFocusEventHandlers(); this.setupFocusEventHandlers();
} }
@@ -226,7 +264,9 @@ export class SearchInputControlComponent implements OnInit, OnDestroy {
selectFirstResult() { selectFirstResult() {
if (this.listResultElement && this.listResultElement.length > 0) { if (this.listResultElement && this.listResultElement.length > 0) {
const firstElement: MatListItem = <MatListItem> this.listResultElement.first; const firstElement: MatListItem = <MatListItem>(
this.listResultElement.first
);
firstElement._getHostElement().focus(); firstElement._getHostElement().focus();
} }
} }
@@ -239,7 +279,9 @@ export class SearchInputControlComponent implements OnInit, OnDestroy {
} }
onRowArrowUp($event: KeyboardEvent): void { onRowArrowUp($event: KeyboardEvent): void {
const previousElement: any = this.getPreviousElementSibling(<Element> $event.target); const previousElement: any = this.getPreviousElementSibling(<Element>(
$event.target
));
if (previousElement) { if (previousElement) {
previousElement.focus(); previousElement.focus();
} else { } else {
@@ -249,12 +291,17 @@ export class SearchInputControlComponent implements OnInit, OnDestroy {
} }
private setupFocusEventHandlers() { private setupFocusEventHandlers() {
this.focusSubject.pipe( this.focusSubject
.pipe(
debounceTime(50), debounceTime(50),
filter(($event: any) => { filter(($event: any) => {
return this.isSearchBarActive() && ($event.type === 'blur' || $event.type === 'focusout'); return (
this.isSearchBarActive() &&
($event.type === 'blur' || $event.type === 'focusout')
);
}) })
).subscribe(() => { )
.subscribe(() => {
this.toggleSearchBar(); this.toggleSearchBar();
}); });
} }
@@ -272,5 +319,4 @@ export class SearchInputControlComponent implements OnInit, OnDestroy {
private getPreviousElementSibling(node: Element): Element { private getPreviousElementSibling(node: Element): Element {
return node.previousElementSibling; return node.previousElementSibling;
} }
} }

View File

@@ -24,12 +24,23 @@
*/ */
import { NO_ERRORS_SCHEMA } from '@angular/core'; import { NO_ERRORS_SCHEMA } from '@angular/core';
import { TestBed, async, ComponentFixture, fakeAsync, tick } from '@angular/core/testing'; import {
TestBed,
async,
ComponentFixture,
fakeAsync,
tick
} from '@angular/core/testing';
import { SearchInputComponent } from './search-input.component'; import { SearchInputComponent } from './search-input.component';
import { AppTestingModule } from '../../../testing/app-testing.module'; import { AppTestingModule } from '../../../testing/app-testing.module';
import { Actions, ofType } from '@ngrx/effects'; import { Actions, ofType } from '@ngrx/effects';
import { NAVIGATE_FOLDER, NavigateToFolder, VIEW_FILE, ViewFileAction } from '../../../store/actions'; import {
NAVIGATE_FOLDER,
NavigateToFolder,
VIEW_FILE,
ViewFileAction
} from '../../../store/actions';
import { map } from 'rxjs/operators'; import { map } from 'rxjs/operators';
describe('SearchInputComponent', () => { describe('SearchInputComponent', () => {
@@ -39,12 +50,8 @@ describe('SearchInputComponent', () => {
beforeEach(async(() => { beforeEach(async(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
imports: [ imports: [AppTestingModule],
AppTestingModule declarations: [SearchInputComponent],
],
declarations: [
SearchInputComponent
],
schemas: [NO_ERRORS_SCHEMA] schemas: [NO_ERRORS_SCHEMA]
}) })
.compileComponents() .compileComponents()
@@ -66,7 +73,9 @@ describe('SearchInputComponent', () => {
}) })
); );
const node = { entry: { isFile: true, id: 'node-id', parentId: 'parent-id' } }; const node = {
entry: { isFile: true, id: 'node-id', parentId: 'parent-id' }
};
component.onItemClicked(node); component.onItemClicked(node);
tick(); tick();

View File

@@ -25,14 +25,23 @@
import { Component, OnInit, ViewChild, ViewEncapsulation } from '@angular/core'; import { Component, OnInit, ViewChild, ViewEncapsulation } from '@angular/core';
import { import {
NavigationEnd, PRIMARY_OUTLET, Router, RouterEvent, UrlSegment, UrlSegmentGroup, NavigationEnd,
PRIMARY_OUTLET,
Router,
RouterEvent,
UrlSegment,
UrlSegmentGroup,
UrlTree UrlTree
} from '@angular/router'; } from '@angular/router';
import { MinimalNodeEntity } from 'alfresco-js-api'; import { MinimalNodeEntity } from 'alfresco-js-api';
import { SearchInputControlComponent } from '../search-input-control/search-input-control.component'; import { SearchInputControlComponent } from '../search-input-control/search-input-control.component';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
import { AppStore } from '../../../store/states/app.state'; import { AppStore } from '../../../store/states/app.state';
import { SearchByTermAction, NavigateToFolder, ViewFileAction } from '../../../store/actions'; import {
SearchByTermAction,
NavigateToFolder,
ViewFileAction
} from '../../../store/actions';
import { filter } from 'rxjs/operators'; import { filter } from 'rxjs/operators';
@Component({ @Component({
@@ -42,7 +51,6 @@ import { filter } from 'rxjs/operators';
host: { class: 'aca-search-input' } host: { class: 'aca-search-input' }
}) })
export class SearchInputComponent implements OnInit { export class SearchInputComponent implements OnInit {
hasOneChange = false; hasOneChange = false;
hasNewChange = false; hasNewChange = false;
navigationTimer: any; navigationTimer: any;
@@ -51,8 +59,7 @@ export class SearchInputComponent implements OnInit {
@ViewChild('searchInputControl') @ViewChild('searchInputControl')
searchInputControl: SearchInputControlComponent; searchInputControl: SearchInputControlComponent;
constructor(private router: Router, private store: Store<AppStore>) { constructor(private router: Router, private store: Store<AppStore>) {}
}
ngOnInit() { ngOnInit() {
this.showInputValue(); this.showInputValue();
@@ -68,10 +75,10 @@ export class SearchInputComponent implements OnInit {
showInputValue() { showInputValue() {
if (this.onSearchResults) { if (this.onSearchResults) {
let searchedWord = null; let searchedWord = null;
const urlTree: UrlTree = this.router.parseUrl(this.router.url); const urlTree: UrlTree = this.router.parseUrl(this.router.url);
const urlSegmentGroup: UrlSegmentGroup = urlTree.root.children[PRIMARY_OUTLET]; const urlSegmentGroup: UrlSegmentGroup =
urlTree.root.children[PRIMARY_OUTLET];
if (urlSegmentGroup) { if (urlSegmentGroup) {
const urlSegments: UrlSegment[] = urlSegmentGroup.segments; const urlSegments: UrlSegment[] = urlSegmentGroup.segments;
@@ -83,7 +90,6 @@ export class SearchInputComponent implements OnInit {
this.searchInputControl.searchTerm = searchedWord; this.searchInputControl.searchTerm = searchedWord;
this.searchInputControl.subscriptAnimationState = 'no-animation'; this.searchInputControl.subscriptAnimationState = 'no-animation';
} }
} else { } else {
if (this.searchInputControl.subscriptAnimationState === 'no-animation') { if (this.searchInputControl.subscriptAnimationState === 'no-animation') {
this.searchInputControl.subscriptAnimationState = 'active'; this.searchInputControl.subscriptAnimationState = 'active';
@@ -124,7 +130,6 @@ export class SearchInputComponent implements OnInit {
onSearchChange(searchTerm: string) { onSearchChange(searchTerm: string) {
if (this.onSearchResults) { if (this.onSearchResults) {
if (this.hasOneChange) { if (this.hasOneChange) {
this.hasNewChange = true; this.hasNewChange = true;
} else { } else {

View File

@@ -19,6 +19,6 @@
} }
.link:hover { .link:hover {
color: #2196F3; color: #2196f3;
text-decoration: underline; text-decoration: underline;
} }

View File

@@ -23,7 +23,13 @@
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>. * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/ */
import { Component, Input, OnInit, ViewEncapsulation, ChangeDetectionStrategy } from '@angular/core'; import {
Component,
Input,
OnInit,
ViewEncapsulation,
ChangeDetectionStrategy
} from '@angular/core';
import { MinimalNodeEntity } from 'alfresco-js-api'; import { MinimalNodeEntity } from 'alfresco-js-api';
import { ViewFileAction } from '../../../store/actions'; import { ViewFileAction } from '../../../store/actions';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
@@ -41,7 +47,8 @@ import { NavigateToFolder } from '../../../store/actions';
export class SearchResultsRowComponent implements OnInit { export class SearchResultsRowComponent implements OnInit {
private node: MinimalNodeEntity; private node: MinimalNodeEntity;
@Input() context: any; @Input()
context: any;
constructor(private store: Store<AppStore>) {} constructor(private store: Store<AppStore>) {}
@@ -94,9 +101,7 @@ export class SearchResultsRowComponent implements OnInit {
} }
showPreview() { showPreview() {
this.store.dispatch( this.store.dispatch(new ViewFileAction(this.node));
new ViewFileAction(this.node)
);
} }
navigate() { navigate() {

View File

@@ -9,8 +9,7 @@ describe('SearchComponent', () => {
beforeEach(async(() => { beforeEach(async(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
declarations: [SearchResultsComponent] declarations: [SearchResultsComponent]
}) }).compileComponents();
.compileComponents();
})); }));
beforeEach(() => { beforeEach(() => {

View File

@@ -26,7 +26,11 @@
import { Component, OnInit, ViewChild } from '@angular/core'; import { Component, OnInit, ViewChild } from '@angular/core';
import { NodePaging, Pagination, MinimalNodeEntity } from 'alfresco-js-api'; import { NodePaging, Pagination, MinimalNodeEntity } from 'alfresco-js-api';
import { ActivatedRoute, Params } from '@angular/router'; import { ActivatedRoute, Params } from '@angular/router';
import { SearchQueryBuilderService, SearchComponent as AdfSearchComponent, SearchFilterComponent } from '@alfresco/adf-content-services'; import {
SearchQueryBuilderService,
SearchComponent as AdfSearchComponent,
SearchFilterComponent
} from '@alfresco/adf-content-services';
import { PageComponent } from '../../page.component'; import { PageComponent } from '../../page.component';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
import { AppStore } from '../../../store/states/app.state'; import { AppStore } from '../../../store/states/app.state';
@@ -41,7 +45,6 @@ import { ContentManagementService } from '../../../services/content-management.s
providers: [SearchQueryBuilderService] providers: [SearchQueryBuilderService]
}) })
export class SearchResultsComponent extends PageComponent implements OnInit { export class SearchResultsComponent extends PageComponent implements OnInit {
@ViewChild('search') @ViewChild('search')
search: AdfSearchComponent; search: AdfSearchComponent;
@@ -90,7 +93,9 @@ export class SearchResultsComponent extends PageComponent implements OnInit {
if (this.route) { if (this.route) {
this.route.params.forEach((params: Params) => { this.route.params.forEach((params: Params) => {
this.searchedWord = params.hasOwnProperty(this.queryParamName) ? params[this.queryParamName] : null; this.searchedWord = params.hasOwnProperty(this.queryParamName)
? params[this.queryParamName]
: null;
const query = this.formatSearchQuery(this.searchedWord); const query = this.formatSearchQuery(this.searchedWord);
if (query) { if (query) {
@@ -98,7 +103,9 @@ export class SearchResultsComponent extends PageComponent implements OnInit {
this.queryBuilder.update(); this.queryBuilder.update();
} else { } else {
this.queryBuilder.userQuery = null; this.queryBuilder.userQuery = null;
this.queryBuilder.executed.next( {list: { pagination: { totalItems: 0 }, entries: []}} ); this.queryBuilder.executed.next({
list: { pagination: { totalItems: 0 }, entries: [] }
});
} }
}); });
} }
@@ -129,14 +136,17 @@ export class SearchResultsComponent extends PageComponent implements OnInit {
} }
isFiltered(): boolean { isFiltered(): boolean {
return this.searchFilter.selectedFacetQueries.length > 0 return (
|| this.searchFilter.selectedBuckets.length > 0 this.searchFilter.selectedFacetQueries.length > 0 ||
|| this.hasCheckedCategories(); this.searchFilter.selectedBuckets.length > 0 ||
this.hasCheckedCategories()
);
} }
hasCheckedCategories() { hasCheckedCategories() {
const checkedCategory = this.queryBuilder.categories const checkedCategory = this.queryBuilder.categories.find(
.find(category => !!this.queryBuilder.queryFragments[category.id]); category => !!this.queryBuilder.queryFragments[category.id]
);
return !!checkedCategory; return !!checkedCategory;
} }

View File

@@ -24,12 +24,21 @@
*/ */
import { Component, ViewEncapsulation, OnInit } from '@angular/core'; import { Component, ViewEncapsulation, OnInit } from '@angular/core';
import { AppConfigService, StorageService, SettingsService } from '@alfresco/adf-core'; import {
AppConfigService,
StorageService,
SettingsService
} from '@alfresco/adf-core';
import { Validators, FormGroup, FormBuilder } from '@angular/forms'; import { Validators, FormGroup, FormBuilder } from '@angular/forms';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
import { AppStore } from '../../store/states'; import { AppStore } from '../../store/states';
import { appLanguagePicker, selectHeaderColor, selectAppName, selectUser } from '../../store/selectors/app.selectors'; import {
appLanguagePicker,
selectHeaderColor,
selectAppName,
selectUser
} from '../../store/selectors/app.selectors';
import { MatCheckboxChange } from '@angular/material'; import { MatCheckboxChange } from '@angular/material';
import { SetLanguagePickerAction } from '../../store/actions'; import { SetLanguagePickerAction } from '../../store/actions';
import { ProfileState } from '@alfresco/adf-extensions'; import { ProfileState } from '@alfresco/adf-extensions';
@@ -41,7 +50,6 @@ import { ProfileState } from '@alfresco/adf-extensions';
host: { class: 'aca-settings' } host: { class: 'aca-settings' }
}) })
export class SettingsComponent implements OnInit { export class SettingsComponent implements OnInit {
private defaultPath = '/assets/images/alfresco-logo-white.svg'; private defaultPath = '/assets/images/alfresco-logo-white.svg';
form: FormGroup; form: FormGroup;
@@ -50,14 +58,15 @@ export class SettingsComponent implements OnInit {
appName$: Observable<string>; appName$: Observable<string>;
headerColor$: Observable<string>; headerColor$: Observable<string>;
languagePicker$: Observable<boolean>; languagePicker$: Observable<boolean>;
experimental: Array<{ key: string, value: boolean }> = []; experimental: Array<{ key: string; value: boolean }> = [];
constructor( constructor(
private store: Store<AppStore>, private store: Store<AppStore>,
private appConfig: AppConfigService, private appConfig: AppConfigService,
private settingsService: SettingsService, private settingsService: SettingsService,
private storage: StorageService, private storage: StorageService,
private fb: FormBuilder) { private fb: FormBuilder
) {
this.profile$ = store.select(selectUser); this.profile$ = store.select(selectUser);
this.appName$ = store.select(selectAppName); this.appName$ = store.select(selectAppName);
this.languagePicker$ = store.select(appLanguagePicker); this.languagePicker$ = store.select(appLanguagePicker);
@@ -70,7 +79,10 @@ export class SettingsComponent implements OnInit {
ngOnInit() { ngOnInit() {
this.form = this.fb.group({ this.form = this.fb.group({
ecmHost: ['', [Validators.required, Validators.pattern('^(http|https):\/\/.*[^/]$')]] ecmHost: [
'',
[Validators.required, Validators.pattern('^(http|https)://.*[^/]$')]
]
}); });
this.reset(); this.reset();
@@ -80,7 +92,7 @@ export class SettingsComponent implements OnInit {
const value = this.appConfig.get(`experimental.${key}`); const value = this.appConfig.get(`experimental.${key}`);
return { return {
key, key,
value: (value === true || value === 'true') value: value === true || value === 'true'
}; };
}); });
} }

View File

@@ -37,11 +37,7 @@ const routes: Routes = [
]; ];
@NgModule({ @NgModule({
imports: [ imports: [CommonModule, CoreModule.forChild(), RouterModule.forChild(routes)],
CommonModule,
CoreModule.forChild(),
RouterModule.forChild(routes)
],
declarations: [SettingsComponent] declarations: [SettingsComponent]
}) })
export class AppSettingsModule {} export class AppSettingsModule {}

View File

@@ -27,7 +27,11 @@ import { TestBed, ComponentFixture } from '@angular/core/testing';
import { NO_ERRORS_SCHEMA } from '@angular/core'; import { NO_ERRORS_SCHEMA } from '@angular/core';
import { import {
AlfrescoApiService, AlfrescoApiService,
TimeAgoPipe, NodeNameTooltipPipe, NodeFavoriteDirective, DataTableComponent, AppConfigPipe TimeAgoPipe,
NodeNameTooltipPipe,
NodeFavoriteDirective,
DataTableComponent,
AppConfigPipe
} from '@alfresco/adf-core'; } from '@alfresco/adf-core';
import { DocumentListComponent } from '@alfresco/adf-content-services'; import { DocumentListComponent } from '@alfresco/adf-content-services';
import { ContentManagementService } from '../../services/content-management.service'; import { ContentManagementService } from '../../services/content-management.service';
@@ -52,8 +56,7 @@ describe('SharedFilesComponent', () => {
}); });
beforeEach(() => { beforeEach(() => {
TestBed TestBed.configureTestingModule({
.configureTestingModule({
imports: [AppTestingModule], imports: [AppTestingModule],
declarations: [ declarations: [
DataTableComponent, DataTableComponent,
@@ -75,7 +78,9 @@ describe('SharedFilesComponent', () => {
alfrescoApi = TestBed.get(AlfrescoApiService); alfrescoApi = TestBed.get(AlfrescoApiService);
alfrescoApi.reset(); alfrescoApi.reset();
spyOn(alfrescoApi.sharedLinksApi, 'findSharedLinks').and.returnValue(Promise.resolve(page)); spyOn(alfrescoApi.sharedLinksApi, 'findSharedLinks').and.returnValue(
Promise.resolve(page)
);
}); });
describe('OnInit', () => { describe('OnInit', () => {

View File

@@ -56,10 +56,7 @@ export class SharedFilesComponent extends PageComponent implements OnInit {
this.content.linksUnshared.subscribe(() => this.reload()), this.content.linksUnshared.subscribe(() => this.reload()),
this.breakpointObserver this.breakpointObserver
.observe([ .observe([Breakpoints.HandsetPortrait, Breakpoints.HandsetLandscape])
Breakpoints.HandsetPortrait,
Breakpoints.HandsetLandscape
])
.subscribe(result => { .subscribe(result => {
this.isSmallScreen = result.matches; this.isSmallScreen = result.matches;
}) })

View File

@@ -7,10 +7,9 @@ import { ActivatedRoute } from '@angular/router';
styleUrls: ['shared-link-view.component.scss'], styleUrls: ['shared-link-view.component.scss'],
encapsulation: ViewEncapsulation.None, encapsulation: ViewEncapsulation.None,
// tslint:disable-next-line:use-host-property-decorator // tslint:disable-next-line:use-host-property-decorator
host: { 'class': 'app-shared-link-view' } host: { class: 'app-shared-link-view' }
}) })
export class SharedLinkViewComponent implements OnInit { export class SharedLinkViewComponent implements OnInit {
sharedLinkId: string = null; sharedLinkId: string = null;
constructor(private route: ActivatedRoute) {} constructor(private route: ActivatedRoute) {}
@@ -20,5 +19,4 @@ export class SharedLinkViewComponent implements OnInit {
this.sharedLinkId = params.id; this.sharedLinkId = params.id;
}); });
} }
} }

View File

@@ -37,14 +37,8 @@ describe('SidenavComponent', () => {
beforeEach(async(() => { beforeEach(async(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
imports: [ imports: [AppTestingModule, EffectsModule.forRoot([NodeEffects])],
AppTestingModule, declarations: [SidenavComponent, ExperimentalDirective],
EffectsModule.forRoot([NodeEffects])
],
declarations: [
SidenavComponent,
ExperimentalDirective
],
schemas: [NO_ERRORS_SCHEMA] schemas: [NO_ERRORS_SCHEMA]
}) })
.compileComponents() .compileComponents()

View File

@@ -4,7 +4,7 @@
$foreground: map-get($theme, foreground); $foreground: map-get($theme, foreground);
$background: map-get($theme, background); $background: map-get($theme, background);
$border: 1px solid mat-color($foreground, divider, .07); $border: 1px solid mat-color($foreground, divider, 0.07);
.sidenav { .sidenav {
@include angular-material-theme($theme); @include angular-material-theme($theme);
@@ -28,8 +28,7 @@
} }
.menu__item--default { .menu__item--default {
color: mat-color($primary, .87); color: mat-color($primary, 0.87);
} }
} }
} }

View File

@@ -38,7 +38,8 @@ import { ContentActionRef, NavBarGroupRef } from '@alfresco/adf-extensions';
styleUrls: ['./sidenav.component.scss'] styleUrls: ['./sidenav.component.scss']
}) })
export class SidenavComponent implements OnInit, OnDestroy { export class SidenavComponent implements OnInit, OnDestroy {
@Input() showLabel: boolean; @Input()
showLabel: boolean;
groups: Array<NavBarGroupRef> = []; groups: Array<NavBarGroupRef> = [];
createActions: Array<ContentActionRef> = []; createActions: Array<ContentActionRef> = [];

View File

@@ -43,7 +43,6 @@ import { ToggleDocumentDisplayMode } from '../../../store/actions';
` `
}) })
export class DocumentDisplayModeComponent { export class DocumentDisplayModeComponent {
displayMode$: Observable<string>; displayMode$: Observable<string>;
constructor(private store: Store<AppStore>) { constructor(private store: Store<AppStore>) {

View File

@@ -46,12 +46,12 @@ import { ContentManagementService } from '../../../services/content-management.s
` `
}) })
export class ToggleFavoriteComponent { export class ToggleFavoriteComponent {
selection$: Observable<SelectionState>; selection$: Observable<SelectionState>;
constructor( constructor(
private store: Store<AppStore>, private store: Store<AppStore>,
private content: ContentManagementService) { private content: ContentManagementService
) {
this.selection$ = this.store.select(appSelection); this.selection$ = this.store.select(appSelection);
} }

View File

@@ -42,8 +42,10 @@ import { AppExtensionService } from '../../../extensions/extension.service';
host: { class: 'aca-toolbar-action' } host: { class: 'aca-toolbar-action' }
}) })
export class ToolbarActionComponent { export class ToolbarActionComponent {
@Input() type = 'icon-button'; @Input()
@Input() entry: ContentActionRef; type = 'icon-button';
@Input()
entry: ContentActionRef;
constructor( constructor(
protected store: Store<AppStore>, protected store: Store<AppStore>,

View File

@@ -41,8 +41,10 @@ export enum ToolbarButtonType {
templateUrl: 'toolbar-button.component.html' templateUrl: 'toolbar-button.component.html'
}) })
export class ToolbarButtonComponent { export class ToolbarButtonComponent {
@Input() type: ToolbarButtonType = ToolbarButtonType.ICON_BUTTON; @Input()
@Input() actionRef: ContentActionRef; type: ToolbarButtonType = ToolbarButtonType.ICON_BUTTON;
@Input()
actionRef: ContentActionRef;
constructor( constructor(
protected store: Store<AppStore>, protected store: Store<AppStore>,

View File

@@ -44,11 +44,7 @@ export function components() {
} }
@NgModule({ @NgModule({
imports: [ imports: [CommonModule, CoreModule.forChild(), ExtensionsModule.forChild()],
CommonModule,
CoreModule.forChild(),
ExtensionsModule.forChild()
],
declarations: components(), declarations: components(),
exports: components(), exports: components(),
entryComponents: components() entryComponents: components()

View File

@@ -26,8 +26,11 @@ import { NO_ERRORS_SCHEMA } from '@angular/core';
import { TestBed, ComponentFixture } from '@angular/core/testing'; import { TestBed, ComponentFixture } from '@angular/core/testing';
import { import {
AlfrescoApiService, AlfrescoApiService,
TimeAgoPipe, NodeNameTooltipPipe, TimeAgoPipe,
NodeFavoriteDirective, DataTableComponent, AppConfigPipe NodeNameTooltipPipe,
NodeFavoriteDirective,
DataTableComponent,
AppConfigPipe
} from '@alfresco/adf-core'; } from '@alfresco/adf-core';
import { DocumentListComponent } from '@alfresco/adf-content-services'; import { DocumentListComponent } from '@alfresco/adf-content-services';
import { ContentManagementService } from '../../services/content-management.service'; import { ContentManagementService } from '../../services/content-management.service';
@@ -81,7 +84,9 @@ describe('TrashcanComponent', () => {
}); });
beforeEach(() => { beforeEach(() => {
spyOn(alfrescoApi.nodesApi, 'getDeletedNodes').and.returnValue(Promise.resolve(page)); spyOn(alfrescoApi.nodesApi, 'getDeletedNodes').and.returnValue(
Promise.resolve(page)
);
}); });
describe('onRestoreNode()', () => { describe('onRestoreNode()', () => {

View File

@@ -41,10 +41,12 @@ export class TrashcanComponent extends PageComponent implements OnInit {
isSmallScreen = false; isSmallScreen = false;
user$: Observable<ProfileState>; user$: Observable<ProfileState>;
constructor(content: ContentManagementService, constructor(
content: ContentManagementService,
extensions: AppExtensionService, extensions: AppExtensionService,
store: Store<AppStore>, store: Store<AppStore>,
private breakpointObserver: BreakpointObserver) { private breakpointObserver: BreakpointObserver
) {
super(store, extensions, content); super(store, extensions, content);
this.user$ = this.store.select(selectUser); this.user$ = this.store.select(selectUser);
} }
@@ -58,10 +60,7 @@ export class TrashcanComponent extends PageComponent implements OnInit {
this.content.nodesRestored.subscribe(() => this.reload()), this.content.nodesRestored.subscribe(() => this.reload()),
this.breakpointObserver this.breakpointObserver
.observe([ .observe([Breakpoints.HandsetPortrait, Breakpoints.HandsetLandscape])
Breakpoints.HandsetPortrait,
Breakpoints.HandsetLandscape
])
.subscribe(result => { .subscribe(result => {
this.isSmallScreen = result.matches; this.isSmallScreen = result.matches;
}) })

View File

@@ -45,7 +45,9 @@ export function forbidSpecialCharacters({ value }: FormControl) {
const validCharacters: RegExp = /[^A-Za-z0-9-]/; const validCharacters: RegExp = /[^A-Za-z0-9-]/;
const isValid: boolean = !validCharacters.test(value); const isValid: boolean = !validCharacters.test(value);
return (isValid) ? null : { return isValid
? null
: {
message: 'LIBRARY.ERRORS.ILLEGAL_CHARACTERS' message: 'LIBRARY.ERRORS.ILLEGAL_CHARACTERS'
}; };
} }

View File

@@ -1,4 +1,3 @@
.mat-radio-group { .mat-radio-group {
display: flex; display: flex;
flex-direction: column; flex-direction: column;

View File

@@ -24,7 +24,6 @@ import { ContentApiService } from '../../services/content-api.service';
import { SiteIdValidator, forbidSpecialCharacters } from './form.validators'; import { SiteIdValidator, forbidSpecialCharacters } from './form.validators';
import { debounceTime } from 'rxjs/operators'; import { debounceTime } from 'rxjs/operators';
@Component({ @Component({
selector: 'app-library-dialog', selector: 'app-library-dialog',
styleUrls: ['./library.dialog.scss'], styleUrls: ['./library.dialog.scss'],
@@ -43,7 +42,11 @@ export class LibraryDialogComponent implements OnInit {
visibilityOptions = [ visibilityOptions = [
{ value: 'PUBLIC', label: 'LIBRARY.VISIBILITY.PUBLIC', disabled: false }, { value: 'PUBLIC', label: 'LIBRARY.VISIBILITY.PUBLIC', disabled: false },
{ value: 'PRIVATE', label: 'LIBRARY.VISIBILITY.PRIVATE', disabled: false }, { value: 'PRIVATE', label: 'LIBRARY.VISIBILITY.PRIVATE', disabled: false },
{ value: 'MODERATED', label: 'LIBRARY.VISIBILITY.MODERATED', disabled: false } {
value: 'MODERATED',
label: 'LIBRARY.VISIBILITY.MODERATED',
disabled: false
}
]; ];
constructor( constructor(
@@ -54,7 +57,11 @@ export class LibraryDialogComponent implements OnInit {
ngOnInit() { ngOnInit() {
const validators = { const validators = {
id: [ Validators.required, Validators.maxLength(72), forbidSpecialCharacters ], id: [
Validators.required,
Validators.maxLength(72),
forbidSpecialCharacters
],
title: [Validators.required, Validators.maxLength(256)], title: [Validators.required, Validators.maxLength(256)],
description: [Validators.maxLength(512)] description: [Validators.maxLength(512)]
}; };
@@ -62,7 +69,7 @@ export class LibraryDialogComponent implements OnInit {
this.form = this.formBuilder.group({ this.form = this.formBuilder.group({
title: ['', validators.title], title: ['', validators.title],
id: ['', validators.id, SiteIdValidator.createValidator(this.contentApi)], id: ['', validators.id, SiteIdValidator.createValidator(this.contentApi)],
description: [ '', validators.description ], description: ['', validators.description]
}); });
this.visibilityOption = this.visibilityOptions[0].value; this.visibilityOption = this.visibilityOptions[0].value;
@@ -106,15 +113,16 @@ export class LibraryDialogComponent implements OnInit {
submit() { submit() {
const { form, dialog } = this; const { form, dialog } = this;
if (!form.valid) { return; } if (!form.valid) {
return;
}
this.create().subscribe( this.create().subscribe(
(node: SiteEntry) => { (node: SiteEntry) => {
this.success.emit(node); this.success.emit(node);
dialog.close(node); dialog.close(node);
}, },
(error) => this.handleError(error) error => this.handleError(error)
); );
} }
@@ -135,16 +143,18 @@ export class LibraryDialogComponent implements OnInit {
} }
private sanitize(input: string) { private sanitize(input: string) {
return input return input.replace(/[\s]/g, '-').replace(/[^A-Za-z0-9-]/g, '');
.replace(/[\s]/g, '-')
.replace(/[^A-Za-z0-9-]/g, '');
} }
private handleError(error: any): any { private handleError(error: any): any {
const { error: { statusCode } } = JSON.parse(error.message); const {
error: { statusCode }
} = JSON.parse(error.message);
if (statusCode === 409) { if (statusCode === 409) {
this.form.controls['id'].setErrors({ message: 'LIBRARY.ERRORS.CONFLICT' }); this.form.controls['id'].setErrors({
message: 'LIBRARY.ERRORS.CONFLICT'
});
} }
return error; return error;

View File

@@ -34,9 +34,7 @@ import { MAT_DIALOG_DATA } from '@angular/material';
export class NodePermissionsDialogComponent { export class NodePermissionsDialogComponent {
nodeId: string; nodeId: string;
constructor( constructor(@Inject(MAT_DIALOG_DATA) data: any) {
@Inject(MAT_DIALOG_DATA) data: any,
) {
this.nodeId = data.nodeId; this.nodeId = data.nodeId;
} }
} }

View File

@@ -21,7 +21,6 @@
flex: 0 0 auto; flex: 0 0 auto;
} }
.mat-dialog-title { .mat-dialog-title {
font-size: 20px; font-size: 20px;
font-weight: 600; font-weight: 600;
@@ -33,7 +32,6 @@
color: mat-color($foreground, text, 0.87); color: mat-color($foreground, text, 0.87);
} }
.mat-dialog-actions { .mat-dialog-actions {
padding: 8px 8px 24px 8px; padding: 8px 8px 24px 8px;
display: -webkit-box; display: -webkit-box;

View File

@@ -34,10 +34,6 @@ import { PaginationDirective } from './pagination.directive';
DocumentListDirective, DocumentListDirective,
PaginationDirective PaginationDirective
], ],
exports: [ exports: [ExperimentalDirective, DocumentListDirective, PaginationDirective]
ExperimentalDirective,
DocumentListDirective,
PaginationDirective
]
}) })
export class DirectivesModule {} export class DirectivesModule {}

View File

@@ -124,16 +124,13 @@ export class DocumentListDirective implements OnInit, OnDestroy {
return entry; return entry;
}); });
this.store.dispatch( this.store.dispatch(new SetSelectedNodesAction(selection));
new SetSelectedNodesAction(selection)
);
} }
private isLockedNode(node): boolean { private isLockedNode(node): boolean {
return ( return (
node.isLocked || node.isLocked ||
(node.properties && (node.properties && node.properties['cm:lockType'] === 'READ_ONLY_LOCK')
node.properties['cm:lockType'] === 'READ_ONLY_LOCK')
); );
} }

View File

@@ -23,7 +23,13 @@
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>. * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/ */
import { Directive, TemplateRef, ViewContainerRef, Input, EmbeddedViewRef } from '@angular/core'; import {
Directive,
TemplateRef,
ViewContainerRef,
Input,
EmbeddedViewRef
} from '@angular/core';
import { AppConfigService, StorageService } from '@alfresco/adf-core'; import { AppConfigService, StorageService } from '@alfresco/adf-core';
import { environment } from '../../environments/environment'; import { environment } from '../../environments/environment';
@@ -43,7 +49,8 @@ export class ExperimentalDirective {
private config: AppConfigService private config: AppConfigService
) {} ) {}
@Input() set ifExperimental(featureKey: string) { @Input()
set ifExperimental(featureKey: string) {
const key = `experimental.${featureKey}`; const key = `experimental.${featureKey}`;
const override = this.storage.getItem(key); const override = this.storage.getItem(key);
@@ -66,7 +73,8 @@ export class ExperimentalDirective {
this.updateView(); this.updateView();
} }
@Input() set ifExperimentalElse(templateRef: TemplateRef<any>) { @Input()
set ifExperimentalElse(templateRef: TemplateRef<any>) {
this.elseTemplateRef = templateRef; this.elseTemplateRef = templateRef;
this.elseViewRef = null; this.elseViewRef = null;
this.updateView(); this.updateView();
@@ -88,7 +96,9 @@ export class ExperimentalDirective {
this.viewContainerRef.clear(); this.viewContainerRef.clear();
if (this.elseTemplateRef) { if (this.elseTemplateRef) {
this.elseViewRef = this.viewContainerRef.createEmbeddedView(this.elseTemplateRef); this.elseViewRef = this.viewContainerRef.createEmbeddedView(
this.elseTemplateRef
);
} }
} }
} }

View File

@@ -50,11 +50,9 @@ export class PaginationDirective implements OnInit, OnDestroy {
); );
this.subscriptions.push( this.subscriptions.push(
this.pagination.changePageSize.subscribe( this.pagination.changePageSize.subscribe((event: PaginationModel) => {
(event: PaginationModel) => {
this.preferences.paginationSize = event.maxItems; this.preferences.paginationSize = event.maxItems;
} })
)
); );
} }

View File

@@ -43,11 +43,7 @@ export function setupExtensions(service: AppExtensionService): Function {
} }
@NgModule({ @NgModule({
imports: [ imports: [CommonModule, CoreModule.forChild(), ExtensionsModule.forChild()]
CommonModule,
CoreModule.forChild(),
ExtensionsModule.forChild()
]
}) })
export class CoreExtensionsModule { export class CoreExtensionsModule {
static forRoot(): ModuleWithProviders { static forRoot(): ModuleWithProviders {

View File

@@ -96,18 +96,14 @@ export function canDeleteSelection(
} }
if (isPreview(context, ...args)) { if (isPreview(context, ...args)) {
return context.permissions.check(context.selection.nodes, [ return context.permissions.check(context.selection.nodes, ['delete']);
'delete'
]);
} }
// workaround for Shared Files // workaround for Shared Files
if (isSharedFiles(context, ...args)) { if (isSharedFiles(context, ...args)) {
return context.permissions.check( return context.permissions.check(context.selection.nodes, ['delete'], {
context.selection.nodes, target: 'allowableOperationsOnTarget'
['delete'], });
{ target: 'allowableOperationsOnTarget' }
);
} }
return context.permissions.check(context.selection.nodes, ['delete']); return context.permissions.check(context.selection.nodes, ['delete']);
@@ -164,9 +160,7 @@ export function canDownloadSelection(
return context.selection.nodes.every(node => { return context.selection.nodes.every(node => {
return ( return (
node.entry && node.entry &&
(node.entry.isFile || (node.entry.isFile || node.entry.isFolder || !!node.entry.nodeId)
node.entry.isFolder ||
!!node.entry.nodeId)
); );
}); });
} }

View File

@@ -28,8 +28,16 @@ import { AppTestingModule } from '../testing/app-testing.module';
import { AppExtensionService } from './extension.service'; import { AppExtensionService } from './extension.service';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
import { AppStore } from '../store/states'; import { AppStore } from '../store/states';
import { ContentActionType, mergeArrays, import {
sortByOrder, filterEnabled, reduceSeparators, reduceEmptyMenus, ExtensionService, ExtensionConfig } from '@alfresco/adf-extensions'; ContentActionType,
mergeArrays,
sortByOrder,
filterEnabled,
reduceSeparators,
reduceEmptyMenus,
ExtensionService,
ExtensionConfig
} from '@alfresco/adf-extensions';
describe('AppExtensionService', () => { describe('AppExtensionService', () => {
let service: AppExtensionService; let service: AppExtensionService;
@@ -38,9 +46,7 @@ describe('AppExtensionService', () => {
beforeEach(() => { beforeEach(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
imports: [ imports: [AppTestingModule]
AppTestingModule
]
}); });
store = TestBed.get(Store); store = TestBed.get(Store);
@@ -96,7 +102,7 @@ describe('AppExtensionService', () => {
}, },
{ {
name: 'extra-1' name: 'extra-1'
}, }
]); ]);
}); });
}); });
@@ -121,9 +127,7 @@ describe('AppExtensionService', () => {
}); });
it('should find action by id', () => { it('should find action by id', () => {
const action = extensions.getActionById( const action = extensions.getActionById('aca:actions/create-folder');
'aca:actions/create-folder'
);
expect(action).toBeTruthy(); expect(action).toBeTruthy();
expect(action.type).toBe('CREATE_FOLDER'); expect(action.type).toBe('CREATE_FOLDER');
expect(action.payload).toBe('folder-name'); expect(action.payload).toBe('folder-name');
@@ -305,7 +309,7 @@ describe('AppExtensionService', () => {
id: 'aca:toolbar/separator-1', id: 'aca:toolbar/separator-1',
order: 1, order: 1,
type: ContentActionType.separator, type: ContentActionType.separator,
title: 'action1', title: 'action1'
}, },
{ {
id: 'aca:toolbar/separator-2', id: 'aca:toolbar/separator-2',
@@ -343,12 +347,8 @@ describe('AppExtensionService', () => {
}); });
expect(service.toolbarActions.length).toBe(2); expect(service.toolbarActions.length).toBe(2);
expect(service.toolbarActions[0].id).toBe( expect(service.toolbarActions[0].id).toBe('aca:toolbar/separator-1');
'aca:toolbar/separator-1' expect(service.toolbarActions[1].id).toBe('aca:toolbar/separator-2');
);
expect(service.toolbarActions[1].id).toBe(
'aca:toolbar/separator-2'
);
}); });
}); });
@@ -519,11 +519,9 @@ describe('AppExtensionService', () => {
}); });
it('should use implicit order', () => { it('should use implicit order', () => {
const sorted = [ const sorted = [{ id: '3' }, { id: '2' }, { id: '1', order: 1 }].sort(
{ id: '3'}, sortByOrder
{ id: '2' }, );
{ id: '1', order: 1 }
].sort(sortByOrder);
expect(sorted[0].id).toBe('1'); expect(sorted[0].id).toBe('1');
expect(sorted[1].id).toBe('3'); expect(sorted[1].id).toBe('3');

View File

@@ -31,12 +31,20 @@ import { ruleContext } from '../store/selectors/app.selectors';
import { NodePermissionService } from '../services/node-permission.service'; import { NodePermissionService } from '../services/node-permission.service';
import { ProfileResolver } from '../services/profile.resolver'; import { ProfileResolver } from '../services/profile.resolver';
import { import {
SelectionState, NavigationState, ExtensionConfig, SelectionState,
RuleContext, RuleEvaluator, ViewerExtensionRef, NavigationState,
ContentActionRef, ContentActionType, ExtensionConfig,
RuleContext,
RuleEvaluator,
ViewerExtensionRef,
ContentActionRef,
ContentActionType,
ExtensionLoaderService, ExtensionLoaderService,
SidebarTabRef, NavBarGroupRef, SidebarTabRef,
sortByOrder, reduceSeparators, reduceEmptyMenus, NavBarGroupRef,
sortByOrder,
reduceSeparators,
reduceEmptyMenus,
ExtensionService, ExtensionService,
ProfileState ProfileState
} from '@alfresco/adf-extensions'; } from '@alfresco/adf-extensions';
@@ -65,8 +73,8 @@ export class AppExtensionService implements RuleContext {
private store: Store<AppStore>, private store: Store<AppStore>,
private loader: ExtensionLoaderService, private loader: ExtensionLoaderService,
private extensions: ExtensionService, private extensions: ExtensionService,
public permissions: NodePermissionService) { public permissions: NodePermissionService
) {
this.store.select(ruleContext).subscribe(result => { this.store.select(ruleContext).subscribe(result => {
this.selection = result.selection; this.selection = result.selection;
this.navigation = result.navigation; this.navigation = result.navigation;
@@ -85,18 +93,42 @@ export class AppExtensionService implements RuleContext {
return; return;
} }
this.toolbarActions = this.loader.getContentActions(config, 'features.toolbar'); this.toolbarActions = this.loader.getContentActions(
this.viewerToolbarActions = this.loader.getContentActions(config, 'features.viewer.toolbar'); config,
this.viewerContentExtensions = this.loader.getElements<ViewerExtensionRef>(config, 'features.viewer.content'); 'features.toolbar'
this.contextMenuActions = this.loader.getContentActions(config, 'features.contextMenu'); );
this.openWithActions = this.loader.getContentActions(config, 'features.viewer.openWith'); this.viewerToolbarActions = this.loader.getContentActions(
this.createActions = this.loader.getElements<ContentActionRef>(config, 'features.create'); config,
'features.viewer.toolbar'
);
this.viewerContentExtensions = this.loader.getElements<ViewerExtensionRef>(
config,
'features.viewer.content'
);
this.contextMenuActions = this.loader.getContentActions(
config,
'features.contextMenu'
);
this.openWithActions = this.loader.getContentActions(
config,
'features.viewer.openWith'
);
this.createActions = this.loader.getElements<ContentActionRef>(
config,
'features.create'
);
this.navbar = this.loadNavBar(config); this.navbar = this.loadNavBar(config);
this.sidebar = this.loader.getElements<SidebarTabRef>(config, 'features.sidebar'); this.sidebar = this.loader.getElements<SidebarTabRef>(
config,
'features.sidebar'
);
} }
protected loadNavBar(config: ExtensionConfig): Array<NavBarGroupRef> { protected loadNavBar(config: ExtensionConfig): Array<NavBarGroupRef> {
const elements = this.loader.getElements<NavBarGroupRef>(config, 'features.navbar'); const elements = this.loader.getElements<NavBarGroupRef>(
config,
'features.navbar'
);
return elements.map(group => { return elements.map(group => {
return { return {
@@ -131,9 +163,7 @@ export class AppExtensionService implements RuleContext {
getApplicationRoutes(): Array<Route> { getApplicationRoutes(): Array<Route> {
return this.extensions.routes.map(route => { return this.extensions.routes.map(route => {
const guards = this.extensions.getAuthGuards( const guards = this.extensions.getAuthGuards(
route.auth && route.auth.length > 0 route.auth && route.auth.length > 0 ? route.auth : this.defaults.auth
? route.auth
: this.defaults.auth
); );
return { return {
@@ -179,9 +209,7 @@ export class AppExtensionService implements RuleContext {
const copy = this.copyAction(action); const copy = this.copyAction(action);
if (copy.children && copy.children.length > 0) { if (copy.children && copy.children.length > 0) {
copy.children = copy.children copy.children = copy.children
.filter(childAction => .filter(childAction => this.filterByRules(childAction))
this.filterByRules(childAction)
)
.reduce(reduceSeparators, []); .reduce(reduceSeparators, []);
} }
return copy; return copy;
@@ -193,21 +221,19 @@ export class AppExtensionService implements RuleContext {
} }
getViewerToolbarActions(): Array<ContentActionRef> { getViewerToolbarActions(): Array<ContentActionRef> {
return this.viewerToolbarActions return this.viewerToolbarActions.filter(action =>
.filter(action => this.filterByRules(action)); this.filterByRules(action)
);
} }
getAllowedContextMenuActions(): Array<ContentActionRef> { getAllowedContextMenuActions(): Array<ContentActionRef> {
return this.contextMenuActions return this.contextMenuActions.filter(action => this.filterByRules(action));
.filter(action => this.filterByRules(action));
} }
copyAction(action: ContentActionRef): ContentActionRef { copyAction(action: ContentActionRef): ContentActionRef {
return { return {
...action, ...action,
children: (action.children || []).map(child => children: (action.children || []).map(child => this.copyAction(child))
this.copyAction(child)
)
}; };
} }

View File

@@ -71,12 +71,7 @@ export class ContentApiService {
*/ */
getNode(nodeId: string, options: any = {}): Observable<MinimalNodeEntity> { getNode(nodeId: string, options: any = {}): Observable<MinimalNodeEntity> {
const defaults = { const defaults = {
include: [ include: ['path', 'properties', 'allowableOperations', 'permissions']
'path',
'properties',
'allowableOperations',
'permissions'
]
}; };
const queryOptions = Object.assign(defaults, options); const queryOptions = Object.assign(defaults, options);
@@ -172,9 +167,7 @@ export class ContentApiService {
*/ */
getRepositoryInformation(): Observable<DiscoveryEntry> { getRepositoryInformation(): Observable<DiscoveryEntry> {
return from( return from(
this.api this.api.getInstance().discovery.discoveryApi.getRepositoryInformation()
.getInstance()
.discovery.discoveryApi.getRepositoryInformation()
); );
} }
@@ -202,10 +195,7 @@ export class ContentApiService {
return this.api.contentApi.getContentUrl(nodeId, attachment); return this.api.contentApi.getContentUrl(nodeId, attachment);
} }
deleteSite( deleteSite(siteId?: string, opts?: { permanent?: boolean }): Observable<any> {
siteId?: string,
opts?: { permanent?: boolean }
): Observable<any> {
return from(this.api.sitesApi.deleteSite(siteId, opts)); return from(this.api.sitesApi.deleteSite(siteId, opts));
} }

View File

@@ -23,15 +23,24 @@
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>. * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/ */
import { TestBed, fakeAsync } from '@angular/core/testing'; import { TestBed, fakeAsync } from '@angular/core/testing';
import { of, throwError } from 'rxjs'; import { of, throwError } from 'rxjs';
import { MatDialog, MatSnackBar } from '@angular/material'; import { MatDialog, MatSnackBar } from '@angular/material';
import { Actions, ofType, EffectsModule } from '@ngrx/effects'; import { Actions, ofType, EffectsModule } from '@ngrx/effects';
import { import {
SNACKBAR_INFO, SnackbarWarningAction, SnackbarInfoAction, SNACKBAR_INFO,
SnackbarErrorAction, SNACKBAR_ERROR, SNACKBAR_WARNING, PurgeDeletedNodesAction, SnackbarWarningAction,
RestoreDeletedNodesAction, NavigateRouteAction, NAVIGATE_ROUTE, DeleteNodesAction, MoveNodesAction, CopyNodesAction SnackbarInfoAction,
SnackbarErrorAction,
SNACKBAR_ERROR,
SNACKBAR_WARNING,
PurgeDeletedNodesAction,
RestoreDeletedNodesAction,
NavigateRouteAction,
NAVIGATE_ROUTE,
DeleteNodesAction,
MoveNodesAction,
CopyNodesAction
} from '../store/actions'; } from '../store/actions';
import { map } from 'rxjs/operators'; import { map } from 'rxjs/operators';
import { NodeEffects } from '../store/effects/node.effects'; import { NodeEffects } from '../store/effects/node.effects';
@@ -44,7 +53,6 @@ import { NodeActionsService } from './node-actions.service';
import { TranslationService } from '@alfresco/adf-core'; import { TranslationService } from '@alfresco/adf-core';
describe('ContentManagementService', () => { describe('ContentManagementService', () => {
let dialog: MatDialog; let dialog: MatDialog;
let actions$: Actions; let actions$: Actions;
let contentApi: ContentApiService; let contentApi: ContentApiService;
@@ -56,10 +64,7 @@ describe('ContentManagementService', () => {
beforeEach(() => { beforeEach(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
imports: [ imports: [AppTestingModule, EffectsModule.forRoot([NodeEffects])]
AppTestingModule,
EffectsModule.forRoot([NodeEffects])
]
}); });
contentApi = TestBed.get(ContentApiService); contentApi = TestBed.get(ContentApiService);
@@ -84,7 +89,9 @@ describe('ContentManagementService', () => {
}); });
it('notifies successful copy of a node', () => { it('notifies successful copy of a node', () => {
spyOn(nodeActions, 'copyNodes').and.returnValue(of('OPERATION.SUCCES.CONTENT.COPY')); spyOn(nodeActions, 'copyNodes').and.returnValue(
of('OPERATION.SUCCES.CONTENT.COPY')
);
const selection = [{ entry: { id: 'node-to-copy-id', name: 'name' } }]; const selection = [{ entry: { id: 'node-to-copy-id', name: 'name' } }];
const createdItems = [{ entry: { id: 'copy-id', name: 'name' } }]; const createdItems = [{ entry: { id: 'copy-id', name: 'name' } }];
@@ -93,88 +100,114 @@ describe('ContentManagementService', () => {
nodeActions.contentCopied.next(<any>createdItems); nodeActions.contentCopied.next(<any>createdItems);
expect(nodeActions.copyNodes).toHaveBeenCalled(); expect(nodeActions.copyNodes).toHaveBeenCalled();
expect(snackBar.open['calls'].argsFor(0)[0]).toBe('APP.MESSAGES.INFO.NODE_COPY.SINGULAR'); expect(snackBar.open['calls'].argsFor(0)[0]).toBe(
'APP.MESSAGES.INFO.NODE_COPY.SINGULAR'
);
}); });
it('notifies successful copy of multiple nodes', () => { it('notifies successful copy of multiple nodes', () => {
spyOn(nodeActions, 'copyNodes').and.returnValue(of('OPERATION.SUCCES.CONTENT.COPY')); spyOn(nodeActions, 'copyNodes').and.returnValue(
of('OPERATION.SUCCES.CONTENT.COPY')
);
const selection = [ const selection = [
{ entry: { id: 'node-to-copy-1', name: 'name1' } }, { entry: { id: 'node-to-copy-1', name: 'name1' } },
{ entry: { id: 'node-to-copy-2', name: 'name2' } }]; { entry: { id: 'node-to-copy-2', name: 'name2' } }
];
const createdItems = [ const createdItems = [
{ entry: { id: 'copy-of-node-1', name: 'name1' } }, { entry: { id: 'copy-of-node-1', name: 'name1' } },
{ entry: { id: 'copy-of-node-2', name: 'name2' } }]; { entry: { id: 'copy-of-node-2', name: 'name2' } }
];
store.dispatch(new CopyNodesAction(selection)); store.dispatch(new CopyNodesAction(selection));
nodeActions.contentCopied.next(<any>createdItems); nodeActions.contentCopied.next(<any>createdItems);
expect(nodeActions.copyNodes).toHaveBeenCalled(); expect(nodeActions.copyNodes).toHaveBeenCalled();
expect(snackBar.open['calls'].argsFor(0)[0]).toBe('APP.MESSAGES.INFO.NODE_COPY.PLURAL'); expect(snackBar.open['calls'].argsFor(0)[0]).toBe(
'APP.MESSAGES.INFO.NODE_COPY.PLURAL'
);
}); });
it('notifies partially copy of one node out of a multiple selection of nodes', () => { it('notifies partially copy of one node out of a multiple selection of nodes', () => {
spyOn(nodeActions, 'copyNodes').and.returnValue(of('OPERATION.SUCCES.CONTENT.COPY')); spyOn(nodeActions, 'copyNodes').and.returnValue(
of('OPERATION.SUCCES.CONTENT.COPY')
);
const selection = [ const selection = [
{ entry: { id: 'node-to-copy-1', name: 'name1' } }, { entry: { id: 'node-to-copy-1', name: 'name1' } },
{ entry: { id: 'node-to-copy-2', name: 'name2' } }]; { entry: { id: 'node-to-copy-2', name: 'name2' } }
const createdItems = [ ];
{ entry: { id: 'copy-of-node-1', name: 'name1' } }]; const createdItems = [{ entry: { id: 'copy-of-node-1', name: 'name1' } }];
store.dispatch(new CopyNodesAction(selection)); store.dispatch(new CopyNodesAction(selection));
nodeActions.contentCopied.next(<any>createdItems); nodeActions.contentCopied.next(<any>createdItems);
expect(nodeActions.copyNodes).toHaveBeenCalled(); expect(nodeActions.copyNodes).toHaveBeenCalled();
expect(snackBar.open['calls'].argsFor(0)[0]).toBe('APP.MESSAGES.INFO.NODE_COPY.PARTIAL_SINGULAR'); expect(snackBar.open['calls'].argsFor(0)[0]).toBe(
'APP.MESSAGES.INFO.NODE_COPY.PARTIAL_SINGULAR'
);
}); });
it('notifies partially copy of more nodes out of a multiple selection of nodes', () => { it('notifies partially copy of more nodes out of a multiple selection of nodes', () => {
spyOn(nodeActions, 'copyNodes').and.returnValue(of('OPERATION.SUCCES.CONTENT.COPY')); spyOn(nodeActions, 'copyNodes').and.returnValue(
of('OPERATION.SUCCES.CONTENT.COPY')
);
const selection = [ const selection = [
{ entry: { id: 'node-to-copy-0', name: 'name0' } }, { entry: { id: 'node-to-copy-0', name: 'name0' } },
{ entry: { id: 'node-to-copy-1', name: 'name1' } }, { entry: { id: 'node-to-copy-1', name: 'name1' } },
{ entry: { id: 'node-to-copy-2', name: 'name2' } }]; { entry: { id: 'node-to-copy-2', name: 'name2' } }
];
const createdItems = [ const createdItems = [
{ entry: { id: 'copy-of-node-0', name: 'name0' } }, { entry: { id: 'copy-of-node-0', name: 'name0' } },
{ entry: { id: 'copy-of-node-1', name: 'name1' } }]; { entry: { id: 'copy-of-node-1', name: 'name1' } }
];
store.dispatch(new CopyNodesAction(selection)); store.dispatch(new CopyNodesAction(selection));
nodeActions.contentCopied.next(<any>createdItems); nodeActions.contentCopied.next(<any>createdItems);
expect(nodeActions.copyNodes).toHaveBeenCalled(); expect(nodeActions.copyNodes).toHaveBeenCalled();
expect(snackBar.open['calls'].argsFor(0)[0]).toBe('APP.MESSAGES.INFO.NODE_COPY.PARTIAL_PLURAL'); expect(snackBar.open['calls'].argsFor(0)[0]).toBe(
'APP.MESSAGES.INFO.NODE_COPY.PARTIAL_PLURAL'
);
}); });
it('notifies of failed copy of multiple nodes', () => { it('notifies of failed copy of multiple nodes', () => {
spyOn(nodeActions, 'copyNodes').and.returnValue(of('OPERATION.SUCCES.CONTENT.COPY')); spyOn(nodeActions, 'copyNodes').and.returnValue(
of('OPERATION.SUCCES.CONTENT.COPY')
);
const selection = [ const selection = [
{ entry: { id: 'node-to-copy-0', name: 'name0' } }, { entry: { id: 'node-to-copy-0', name: 'name0' } },
{ entry: { id: 'node-to-copy-1', name: 'name1' } }, { entry: { id: 'node-to-copy-1', name: 'name1' } },
{ entry: { id: 'node-to-copy-2', name: 'name2' } }]; { entry: { id: 'node-to-copy-2', name: 'name2' } }
];
const createdItems = []; const createdItems = [];
store.dispatch(new CopyNodesAction(selection)); store.dispatch(new CopyNodesAction(selection));
nodeActions.contentCopied.next(<any>createdItems); nodeActions.contentCopied.next(<any>createdItems);
expect(nodeActions.copyNodes).toHaveBeenCalled(); expect(nodeActions.copyNodes).toHaveBeenCalled();
expect(snackBar.open['calls'].argsFor(0)[0]).toBe('APP.MESSAGES.INFO.NODE_COPY.FAIL_PLURAL'); expect(snackBar.open['calls'].argsFor(0)[0]).toBe(
'APP.MESSAGES.INFO.NODE_COPY.FAIL_PLURAL'
);
}); });
it('notifies of failed copy of one node', () => { it('notifies of failed copy of one node', () => {
spyOn(nodeActions, 'copyNodes').and.returnValue(of('OPERATION.SUCCES.CONTENT.COPY')); spyOn(nodeActions, 'copyNodes').and.returnValue(
of('OPERATION.SUCCES.CONTENT.COPY')
);
const selection = [ const selection = [{ entry: { id: 'node-to-copy', name: 'name' } }];
{ entry: { id: 'node-to-copy', name: 'name' } }];
const createdItems = []; const createdItems = [];
store.dispatch(new CopyNodesAction(selection)); store.dispatch(new CopyNodesAction(selection));
nodeActions.contentCopied.next(<any>createdItems); nodeActions.contentCopied.next(<any>createdItems);
expect(nodeActions.copyNodes).toHaveBeenCalled(); expect(nodeActions.copyNodes).toHaveBeenCalled();
expect(snackBar.open['calls'].argsFor(0)[0]).toBe('APP.MESSAGES.INFO.NODE_COPY.FAIL_SINGULAR'); expect(snackBar.open['calls'].argsFor(0)[0]).toBe(
'APP.MESSAGES.INFO.NODE_COPY.FAIL_SINGULAR'
);
}); });
it('notifies error if success message was not emitted', () => { it('notifies error if success message was not emitted', () => {
@@ -186,34 +219,46 @@ describe('ContentManagementService', () => {
nodeActions.contentCopied.next(); nodeActions.contentCopied.next();
expect(nodeActions.copyNodes).toHaveBeenCalled(); expect(nodeActions.copyNodes).toHaveBeenCalled();
expect(snackBar.open['calls'].argsFor(0)[0]).toBe('APP.MESSAGES.ERRORS.GENERIC'); expect(snackBar.open['calls'].argsFor(0)[0]).toBe(
'APP.MESSAGES.ERRORS.GENERIC'
);
}); });
it('notifies permission error on copy of node', () => { it('notifies permission error on copy of node', () => {
spyOn(nodeActions, 'copyNodes').and.returnValue(throwError(new Error(JSON.stringify({error: {statusCode: 403}})))); spyOn(nodeActions, 'copyNodes').and.returnValue(
throwError(new Error(JSON.stringify({ error: { statusCode: 403 } })))
);
const selection = [{ entry: { id: '1', name: 'name' } }]; const selection = [{ entry: { id: '1', name: 'name' } }];
store.dispatch(new CopyNodesAction(selection)); store.dispatch(new CopyNodesAction(selection));
expect(nodeActions.copyNodes).toHaveBeenCalled(); expect(nodeActions.copyNodes).toHaveBeenCalled();
expect(snackBar.open['calls'].argsFor(0)[0]).toBe('APP.MESSAGES.ERRORS.PERMISSION'); expect(snackBar.open['calls'].argsFor(0)[0]).toBe(
'APP.MESSAGES.ERRORS.PERMISSION'
);
}); });
it('notifies generic error message on all errors, but 403', () => { it('notifies generic error message on all errors, but 403', () => {
spyOn(nodeActions, 'copyNodes').and.returnValue(throwError(new Error(JSON.stringify({error: {statusCode: 404}})))); spyOn(nodeActions, 'copyNodes').and.returnValue(
throwError(new Error(JSON.stringify({ error: { statusCode: 404 } })))
);
const selection = [{ entry: { id: '1', name: 'name' } }]; const selection = [{ entry: { id: '1', name: 'name' } }];
store.dispatch(new CopyNodesAction(selection)); store.dispatch(new CopyNodesAction(selection));
expect(nodeActions.copyNodes).toHaveBeenCalled(); expect(nodeActions.copyNodes).toHaveBeenCalled();
expect(snackBar.open['calls'].argsFor(0)[0]).toBe('APP.MESSAGES.ERRORS.GENERIC'); expect(snackBar.open['calls'].argsFor(0)[0]).toBe(
'APP.MESSAGES.ERRORS.GENERIC'
);
}); });
}); });
describe('Undo Copy action', () => { describe('Undo Copy action', () => {
beforeEach(() => { beforeEach(() => {
spyOn(nodeActions, 'copyNodes').and.returnValue(of('OPERATION.SUCCES.CONTENT.COPY')); spyOn(nodeActions, 'copyNodes').and.returnValue(
of('OPERATION.SUCCES.CONTENT.COPY')
);
spyOn(snackBar, 'open').and.returnValue({ spyOn(snackBar, 'open').and.returnValue({
onAction: () => of({}) onAction: () => of({})
@@ -230,32 +275,58 @@ describe('ContentManagementService', () => {
nodeActions.contentCopied.next(<any>createdItems); nodeActions.contentCopied.next(<any>createdItems);
expect(nodeActions.copyNodes).toHaveBeenCalled(); expect(nodeActions.copyNodes).toHaveBeenCalled();
expect(snackBar.open['calls'].argsFor(0)[0]).toBe('APP.MESSAGES.INFO.NODE_COPY.SINGULAR'); expect(snackBar.open['calls'].argsFor(0)[0]).toBe(
'APP.MESSAGES.INFO.NODE_COPY.SINGULAR'
);
expect(contentApi.deleteNode).toHaveBeenCalledWith(createdItems[0].entry.id, { permanent: true }); expect(contentApi.deleteNode).toHaveBeenCalledWith(
createdItems[0].entry.id,
{ permanent: true }
);
}); });
it('should delete also the node created inside an already existing folder from destination', () => { it('should delete also the node created inside an already existing folder from destination', () => {
const spyOnDeleteNode = spyOn(contentApi, 'deleteNode').and.returnValue(of(null)); const spyOnDeleteNode = spyOn(contentApi, 'deleteNode').and.returnValue(
of(null)
);
const selection = [ const selection = [
{ entry: { id: 'node-to-copy-1', name: 'name1' } }, { entry: { id: 'node-to-copy-1', name: 'name1' } },
{ entry: { id: 'node-to-copy-2', name: 'folder-with-name-already-existing-on-destination' } }]; {
entry: {
id: 'node-to-copy-2',
name: 'folder-with-name-already-existing-on-destination'
}
}
];
const id1 = 'copy-of-node-1'; const id1 = 'copy-of-node-1';
const id2 = 'copy-of-child-of-node-2'; const id2 = 'copy-of-child-of-node-2';
const createdItems = [ const createdItems = [
{ entry: { id: id1, name: 'name1' } }, { entry: { id: id1, name: 'name1' } },
[ { entry: { id: id2, name: 'name-of-child-of-node-2' , parentId: 'the-folder-already-on-destination' } }] ]; [
{
entry: {
id: id2,
name: 'name-of-child-of-node-2',
parentId: 'the-folder-already-on-destination'
}
}
]
];
store.dispatch(new CopyNodesAction(selection)); store.dispatch(new CopyNodesAction(selection));
nodeActions.contentCopied.next(<any>createdItems); nodeActions.contentCopied.next(<any>createdItems);
expect(nodeActions.copyNodes).toHaveBeenCalled(); expect(nodeActions.copyNodes).toHaveBeenCalled();
expect(snackBar.open['calls'].argsFor(0)[0]).toBe('APP.MESSAGES.INFO.NODE_COPY.PLURAL'); expect(snackBar.open['calls'].argsFor(0)[0]).toBe(
'APP.MESSAGES.INFO.NODE_COPY.PLURAL'
);
expect(spyOnDeleteNode).toHaveBeenCalled(); expect(spyOnDeleteNode).toHaveBeenCalled();
expect(spyOnDeleteNode.calls.allArgs()) expect(spyOnDeleteNode.calls.allArgs()).toEqual([
.toEqual([[id1, { permanent: true }], [id2, { permanent: true }]]); [id1, { permanent: true }],
[id2, { permanent: true }]
]);
}); });
it('notifies when error occurs on Undo action', () => { it('notifies when error occurs on Undo action', () => {
@@ -269,11 +340,15 @@ describe('ContentManagementService', () => {
expect(nodeActions.copyNodes).toHaveBeenCalled(); expect(nodeActions.copyNodes).toHaveBeenCalled();
expect(contentApi.deleteNode).toHaveBeenCalled(); expect(contentApi.deleteNode).toHaveBeenCalled();
expect(snackBar.open['calls'].argsFor(0)[0]).toEqual('APP.MESSAGES.INFO.NODE_COPY.SINGULAR'); expect(snackBar.open['calls'].argsFor(0)[0]).toEqual(
'APP.MESSAGES.INFO.NODE_COPY.SINGULAR'
);
}); });
it('notifies when some error of type Error occurs on Undo action', () => { it('notifies when some error of type Error occurs on Undo action', () => {
spyOn(contentApi, 'deleteNode').and.returnValue(throwError(new Error('oops!'))); spyOn(contentApi, 'deleteNode').and.returnValue(
throwError(new Error('oops!'))
);
const selection = [{ entry: { id: 'node-to-copy-id', name: 'name' } }]; const selection = [{ entry: { id: 'node-to-copy-id', name: 'name' } }];
const createdItems = [{ entry: { id: 'copy-id', name: 'name' } }]; const createdItems = [{ entry: { id: 'copy-id', name: 'name' } }];
@@ -283,11 +358,15 @@ describe('ContentManagementService', () => {
expect(nodeActions.copyNodes).toHaveBeenCalled(); expect(nodeActions.copyNodes).toHaveBeenCalled();
expect(contentApi.deleteNode).toHaveBeenCalled(); expect(contentApi.deleteNode).toHaveBeenCalled();
expect(snackBar.open['calls'].argsFor(0)[0]).toEqual('APP.MESSAGES.INFO.NODE_COPY.SINGULAR'); expect(snackBar.open['calls'].argsFor(0)[0]).toEqual(
'APP.MESSAGES.INFO.NODE_COPY.SINGULAR'
);
}); });
it('notifies permission error when it occurs on Undo action', () => { it('notifies permission error when it occurs on Undo action', () => {
spyOn(contentApi, 'deleteNode').and.returnValue(throwError(new Error(JSON.stringify({error: {statusCode: 403}})))); spyOn(contentApi, 'deleteNode').and.returnValue(
throwError(new Error(JSON.stringify({ error: { statusCode: 403 } })))
);
const selection = [{ entry: { id: 'node-to-copy-id', name: 'name' } }]; const selection = [{ entry: { id: 'node-to-copy-id', name: 'name' } }];
const createdItems = [{ entry: { id: 'copy-id', name: 'name' } }]; const createdItems = [{ entry: { id: 'copy-id', name: 'name' } }];
@@ -297,16 +376,18 @@ describe('ContentManagementService', () => {
expect(nodeActions.copyNodes).toHaveBeenCalled(); expect(nodeActions.copyNodes).toHaveBeenCalled();
expect(contentApi.deleteNode).toHaveBeenCalled(); expect(contentApi.deleteNode).toHaveBeenCalled();
expect(snackBar.open['calls'].argsFor(0)[0]).toEqual('APP.MESSAGES.INFO.NODE_COPY.SINGULAR'); expect(snackBar.open['calls'].argsFor(0)[0]).toEqual(
'APP.MESSAGES.INFO.NODE_COPY.SINGULAR'
);
}); });
}); });
describe('Move node action', () => { describe('Move node action', () => {
beforeEach(() => { beforeEach(() => {
spyOn(translationService, 'instant').and.callFake((keysArray) => { spyOn(translationService, 'instant').and.callFake(keysArray => {
if (Array.isArray(keysArray)) { if (Array.isArray(keysArray)) {
const processedKeys = {}; const processedKeys = {};
keysArray.forEach((key) => { keysArray.forEach(key => {
processedKeys[key] = key; processedKeys[key] = key;
}); });
return processedKeys; return processedKeys;
@@ -328,7 +409,9 @@ describe('ContentManagementService', () => {
partiallySucceeded: [] partiallySucceeded: []
}; };
spyOn(nodeActions, 'moveNodes').and.returnValue(of('OPERATION.SUCCES.CONTENT.MOVE')); spyOn(nodeActions, 'moveNodes').and.returnValue(
of('OPERATION.SUCCES.CONTENT.MOVE')
);
spyOn(nodeActions, 'processResponse').and.returnValue(moveResponse); spyOn(nodeActions, 'processResponse').and.returnValue(moveResponse);
const selection = node; const selection = node;
@@ -337,20 +420,25 @@ describe('ContentManagementService', () => {
nodeActions.contentMoved.next(moveResponse); nodeActions.contentMoved.next(moveResponse);
expect(nodeActions.moveNodes).toHaveBeenCalled(); expect(nodeActions.moveNodes).toHaveBeenCalled();
expect(snackBar.open['calls'].argsFor(0)[0]).toBe('APP.MESSAGES.INFO.NODE_MOVE.SINGULAR'); expect(snackBar.open['calls'].argsFor(0)[0]).toBe(
'APP.MESSAGES.INFO.NODE_MOVE.SINGULAR'
);
}); });
it('notifies successful move of multiple nodes', () => { it('notifies successful move of multiple nodes', () => {
const nodes = [ const nodes = [
{ entry: { id: '1', name: 'name1' } }, { entry: { id: '1', name: 'name1' } },
{ entry: { id: '2', name: 'name2' } }]; { entry: { id: '2', name: 'name2' } }
];
const moveResponse = { const moveResponse = {
succeeded: nodes, succeeded: nodes,
failed: [], failed: [],
partiallySucceeded: [] partiallySucceeded: []
}; };
spyOn(nodeActions, 'moveNodes').and.returnValue(of('OPERATION.SUCCES.CONTENT.MOVE')); spyOn(nodeActions, 'moveNodes').and.returnValue(
of('OPERATION.SUCCES.CONTENT.MOVE')
);
spyOn(nodeActions, 'processResponse').and.returnValue(moveResponse); spyOn(nodeActions, 'processResponse').and.returnValue(moveResponse);
const selection = nodes; const selection = nodes;
@@ -359,7 +447,9 @@ describe('ContentManagementService', () => {
nodeActions.contentMoved.next(moveResponse); nodeActions.contentMoved.next(moveResponse);
expect(nodeActions.moveNodes).toHaveBeenCalled(); expect(nodeActions.moveNodes).toHaveBeenCalled();
expect(snackBar.open['calls'].argsFor(0)[0]).toBe('APP.MESSAGES.INFO.NODE_MOVE.PLURAL'); expect(snackBar.open['calls'].argsFor(0)[0]).toBe(
'APP.MESSAGES.INFO.NODE_MOVE.PLURAL'
);
}); });
it('notifies partial move of a node', () => { it('notifies partial move of a node', () => {
@@ -370,7 +460,9 @@ describe('ContentManagementService', () => {
partiallySucceeded: nodes partiallySucceeded: nodes
}; };
spyOn(nodeActions, 'moveNodes').and.returnValue(of('OPERATION.SUCCES.CONTENT.MOVE')); spyOn(nodeActions, 'moveNodes').and.returnValue(
of('OPERATION.SUCCES.CONTENT.MOVE')
);
spyOn(nodeActions, 'processResponse').and.returnValue(moveResponse); spyOn(nodeActions, 'processResponse').and.returnValue(moveResponse);
const selection = nodes; const selection = nodes;
@@ -379,20 +471,25 @@ describe('ContentManagementService', () => {
nodeActions.contentMoved.next(moveResponse); nodeActions.contentMoved.next(moveResponse);
expect(nodeActions.moveNodes).toHaveBeenCalled(); expect(nodeActions.moveNodes).toHaveBeenCalled();
expect(snackBar.open['calls'].argsFor(0)[0]).toBe('APP.MESSAGES.INFO.NODE_MOVE.PARTIAL.SINGULAR'); expect(snackBar.open['calls'].argsFor(0)[0]).toBe(
'APP.MESSAGES.INFO.NODE_MOVE.PARTIAL.SINGULAR'
);
}); });
it('notifies partial move of multiple nodes', () => { it('notifies partial move of multiple nodes', () => {
const nodes = [ const nodes = [
{ entry: { id: '1', name: 'name' } }, { entry: { id: '1', name: 'name' } },
{ entry: { id: '2', name: 'name2' } } ]; { entry: { id: '2', name: 'name2' } }
];
const moveResponse = { const moveResponse = {
succeeded: [], succeeded: [],
failed: [], failed: [],
partiallySucceeded: nodes partiallySucceeded: nodes
}; };
spyOn(nodeActions, 'moveNodes').and.returnValue(of('OPERATION.SUCCES.CONTENT.MOVE')); spyOn(nodeActions, 'moveNodes').and.returnValue(
of('OPERATION.SUCCES.CONTENT.MOVE')
);
spyOn(nodeActions, 'processResponse').and.returnValue(moveResponse); spyOn(nodeActions, 'processResponse').and.returnValue(moveResponse);
const selection = nodes; const selection = nodes;
@@ -401,19 +498,25 @@ describe('ContentManagementService', () => {
nodeActions.contentMoved.next(moveResponse); nodeActions.contentMoved.next(moveResponse);
expect(nodeActions.moveNodes).toHaveBeenCalled(); expect(nodeActions.moveNodes).toHaveBeenCalled();
expect(snackBar.open['calls'].argsFor(0)[0]).toBe('APP.MESSAGES.INFO.NODE_MOVE.PARTIAL.PLURAL'); expect(snackBar.open['calls'].argsFor(0)[0]).toBe(
'APP.MESSAGES.INFO.NODE_MOVE.PARTIAL.PLURAL'
);
}); });
it('notifies successful move and the number of nodes that could not be moved', () => { it('notifies successful move and the number of nodes that could not be moved', () => {
const nodes = [ { entry: { id: '1', name: 'name' } }, const nodes = [
{ entry: { id: '2', name: 'name2' } } ]; { entry: { id: '1', name: 'name' } },
{ entry: { id: '2', name: 'name2' } }
];
const moveResponse = { const moveResponse = {
succeeded: [nodes[0]], succeeded: [nodes[0]],
failed: [nodes[1]], failed: [nodes[1]],
partiallySucceeded: [] partiallySucceeded: []
}; };
spyOn(nodeActions, 'moveNodes').and.returnValue(of('OPERATION.SUCCES.CONTENT.MOVE')); spyOn(nodeActions, 'moveNodes').and.returnValue(
of('OPERATION.SUCCES.CONTENT.MOVE')
);
spyOn(nodeActions, 'processResponse').and.returnValue(moveResponse); spyOn(nodeActions, 'processResponse').and.returnValue(moveResponse);
store.dispatch(new MoveNodesAction(nodes)); store.dispatch(new MoveNodesAction(nodes));
@@ -421,28 +524,34 @@ describe('ContentManagementService', () => {
nodeActions.contentMoved.next(moveResponse); nodeActions.contentMoved.next(moveResponse);
expect(nodeActions.moveNodes).toHaveBeenCalled(); expect(nodeActions.moveNodes).toHaveBeenCalled();
expect(snackBar.open['calls'].argsFor(0)[0]) expect(snackBar.open['calls'].argsFor(0)[0]).toBe(
.toBe('APP.MESSAGES.INFO.NODE_MOVE.SINGULAR APP.MESSAGES.INFO.NODE_MOVE.PARTIAL.FAIL'); 'APP.MESSAGES.INFO.NODE_MOVE.SINGULAR APP.MESSAGES.INFO.NODE_MOVE.PARTIAL.FAIL'
);
}); });
it('notifies successful move and the number of partially moved ones', () => { it('notifies successful move and the number of partially moved ones', () => {
const nodes = [ { entry: { id: '1', name: 'name' } }, const nodes = [
{ entry: { id: '2', name: 'name2' } } ]; { entry: { id: '1', name: 'name' } },
{ entry: { id: '2', name: 'name2' } }
];
const moveResponse = { const moveResponse = {
succeeded: [nodes[0]], succeeded: [nodes[0]],
failed: [], failed: [],
partiallySucceeded: [nodes[1]] partiallySucceeded: [nodes[1]]
}; };
spyOn(nodeActions, 'moveNodes').and.returnValue(of('OPERATION.SUCCES.CONTENT.MOVE')); spyOn(nodeActions, 'moveNodes').and.returnValue(
of('OPERATION.SUCCES.CONTENT.MOVE')
);
spyOn(nodeActions, 'processResponse').and.returnValue(moveResponse); spyOn(nodeActions, 'processResponse').and.returnValue(moveResponse);
store.dispatch(new MoveNodesAction(nodes)); store.dispatch(new MoveNodesAction(nodes));
nodeActions.contentMoved.next(moveResponse); nodeActions.contentMoved.next(moveResponse);
expect(nodeActions.moveNodes).toHaveBeenCalled(); expect(nodeActions.moveNodes).toHaveBeenCalled();
expect(snackBar.open['calls'].argsFor(0)[0]) expect(snackBar.open['calls'].argsFor(0)[0]).toBe(
.toBe('APP.MESSAGES.INFO.NODE_MOVE.SINGULAR APP.MESSAGES.INFO.NODE_MOVE.PARTIAL.SINGULAR'); 'APP.MESSAGES.INFO.NODE_MOVE.SINGULAR APP.MESSAGES.INFO.NODE_MOVE.PARTIAL.SINGULAR'
);
}); });
it('notifies error if success message was not emitted', () => { it('notifies error if success message was not emitted', () => {
@@ -459,37 +568,51 @@ describe('ContentManagementService', () => {
nodeActions.contentMoved.next(moveResponse); nodeActions.contentMoved.next(moveResponse);
expect(nodeActions.moveNodes).toHaveBeenCalled(); expect(nodeActions.moveNodes).toHaveBeenCalled();
expect(snackBar.open['calls'].argsFor(0)[0]).toBe('APP.MESSAGES.ERRORS.GENERIC'); expect(snackBar.open['calls'].argsFor(0)[0]).toBe(
'APP.MESSAGES.ERRORS.GENERIC'
);
}); });
it('notifies permission error on move of node', () => { it('notifies permission error on move of node', () => {
spyOn(nodeActions, 'moveNodes').and.returnValue(throwError(new Error(JSON.stringify({error: {statusCode: 403}})))); spyOn(nodeActions, 'moveNodes').and.returnValue(
throwError(new Error(JSON.stringify({ error: { statusCode: 403 } })))
);
const selection = [{ entry: { id: '1', name: 'name' } }]; const selection = [{ entry: { id: '1', name: 'name' } }];
store.dispatch(new MoveNodesAction(selection)); store.dispatch(new MoveNodesAction(selection));
expect(nodeActions.moveNodes).toHaveBeenCalled(); expect(nodeActions.moveNodes).toHaveBeenCalled();
expect(snackBar.open['calls'].argsFor(0)[0]).toBe('APP.MESSAGES.ERRORS.PERMISSION'); expect(snackBar.open['calls'].argsFor(0)[0]).toBe(
'APP.MESSAGES.ERRORS.PERMISSION'
);
}); });
it('notifies generic error message on all errors, but 403', () => { it('notifies generic error message on all errors, but 403', () => {
spyOn(nodeActions, 'moveNodes').and.returnValue(throwError(new Error(JSON.stringify({error: {statusCode: 404}})))); spyOn(nodeActions, 'moveNodes').and.returnValue(
throwError(new Error(JSON.stringify({ error: { statusCode: 404 } })))
);
const selection = [{ entry: { id: '1', name: 'name' } }]; const selection = [{ entry: { id: '1', name: 'name' } }];
store.dispatch(new MoveNodesAction(selection)); store.dispatch(new MoveNodesAction(selection));
expect(nodeActions.moveNodes).toHaveBeenCalled(); expect(nodeActions.moveNodes).toHaveBeenCalled();
expect(snackBar.open['calls'].argsFor(0)[0]).toBe('APP.MESSAGES.ERRORS.GENERIC'); expect(snackBar.open['calls'].argsFor(0)[0]).toBe(
'APP.MESSAGES.ERRORS.GENERIC'
);
}); });
it('notifies conflict error message on 409', () => { it('notifies conflict error message on 409', () => {
spyOn(nodeActions, 'moveNodes').and.returnValue(throwError(new Error(JSON.stringify({error: {statusCode: 409}})))); spyOn(nodeActions, 'moveNodes').and.returnValue(
throwError(new Error(JSON.stringify({ error: { statusCode: 409 } })))
);
const selection = [{ entry: { id: '1', name: 'name' } }]; const selection = [{ entry: { id: '1', name: 'name' } }];
store.dispatch(new MoveNodesAction(selection)); store.dispatch(new MoveNodesAction(selection));
expect(nodeActions.moveNodes).toHaveBeenCalled(); expect(nodeActions.moveNodes).toHaveBeenCalled();
expect(snackBar.open['calls'].argsFor(0)[0]).toBe('APP.MESSAGES.ERRORS.NODE_MOVE'); expect(snackBar.open['calls'].argsFor(0)[0]).toBe(
'APP.MESSAGES.ERRORS.NODE_MOVE'
);
}); });
it('notifies error if move response has only failed items', () => { it('notifies error if move response has only failed items', () => {
@@ -500,23 +623,27 @@ describe('ContentManagementService', () => {
partiallySucceeded: [] partiallySucceeded: []
}; };
spyOn(nodeActions, 'moveNodes').and.returnValue(of('OPERATION.SUCCES.CONTENT.MOVE')); spyOn(nodeActions, 'moveNodes').and.returnValue(
of('OPERATION.SUCCES.CONTENT.MOVE')
);
spyOn(nodeActions, 'processResponse').and.returnValue(moveResponse); spyOn(nodeActions, 'processResponse').and.returnValue(moveResponse);
store.dispatch(new MoveNodesAction(nodes)); store.dispatch(new MoveNodesAction(nodes));
nodeActions.contentMoved.next(moveResponse); nodeActions.contentMoved.next(moveResponse);
expect(nodeActions.moveNodes).toHaveBeenCalled(); expect(nodeActions.moveNodes).toHaveBeenCalled();
expect(snackBar.open['calls'].argsFor(0)[0]).toBe('APP.MESSAGES.ERRORS.GENERIC'); expect(snackBar.open['calls'].argsFor(0)[0]).toBe(
'APP.MESSAGES.ERRORS.GENERIC'
);
}); });
}); });
describe('Undo Move action', () => { describe('Undo Move action', () => {
beforeEach(() => { beforeEach(() => {
spyOn(translationService, 'instant').and.callFake((keysArray) => { spyOn(translationService, 'instant').and.callFake(keysArray => {
if (Array.isArray(keysArray)) { if (Array.isArray(keysArray)) {
const processedKeys = {}; const processedKeys = {};
keysArray.forEach((key) => { keysArray.forEach(key => {
processedKeys[key] = key; processedKeys[key] = key;
}); });
return processedKeys; return processedKeys;
@@ -527,7 +654,9 @@ describe('ContentManagementService', () => {
}); });
beforeEach(() => { beforeEach(() => {
spyOn(nodeActions, 'moveNodes').and.returnValue(of('OPERATION.SUCCES.CONTENT.MOVE')); spyOn(nodeActions, 'moveNodes').and.returnValue(
of('OPERATION.SUCCES.CONTENT.MOVE')
);
spyOn(snackBar, 'open').and.returnValue({ spyOn(snackBar, 'open').and.returnValue({
onAction: () => of({}) onAction: () => of({})
@@ -538,7 +667,9 @@ describe('ContentManagementService', () => {
it('should move node back to initial parent, after succeeded move', () => { it('should move node back to initial parent, after succeeded move', () => {
const initialParent = 'parent-id-0'; const initialParent = 'parent-id-0';
const node = { entry: { id: 'node-to-move-id', name: 'name', parentId: initialParent } }; const node = {
entry: { id: 'node-to-move-id', name: 'name', parentId: initialParent }
};
const selection = [node]; const selection = [node];
spyOn(nodeActions, 'moveNodeAction').and.returnValue(of({})); spyOn(nodeActions, 'moveNodeAction').and.returnValue(of({}));
@@ -551,14 +682,25 @@ describe('ContentManagementService', () => {
}; };
nodeActions.contentMoved.next(<any>movedItems); nodeActions.contentMoved.next(<any>movedItems);
expect(nodeActions.moveNodeAction) expect(nodeActions.moveNodeAction).toHaveBeenCalledWith(
.toHaveBeenCalledWith(movedItems.succeeded[0].itemMoved.entry, movedItems.succeeded[0].initialParentId); movedItems.succeeded[0].itemMoved.entry,
expect(snackBar.open['calls'].argsFor(0)[0]).toBe('APP.MESSAGES.INFO.NODE_MOVE.SINGULAR'); movedItems.succeeded[0].initialParentId
);
expect(snackBar.open['calls'].argsFor(0)[0]).toBe(
'APP.MESSAGES.INFO.NODE_MOVE.SINGULAR'
);
}); });
it('should move node back to initial parent, after succeeded move of a single file', () => { it('should move node back to initial parent, after succeeded move of a single file', () => {
const initialParent = 'parent-id-0'; const initialParent = 'parent-id-0';
const node = { entry: { id: 'node-to-move-id', name: 'name', isFolder: false, parentId: initialParent } }; const node = {
entry: {
id: 'node-to-move-id',
name: 'name',
isFolder: false,
parentId: initialParent
}
};
const selection = [node]; const selection = [node];
spyOn(nodeActions, 'moveNodeAction').and.returnValue(of({})); spyOn(nodeActions, 'moveNodeAction').and.returnValue(of({}));
@@ -572,8 +714,13 @@ describe('ContentManagementService', () => {
store.dispatch(new MoveNodesAction(selection)); store.dispatch(new MoveNodesAction(selection));
nodeActions.contentMoved.next(<any>movedItems); nodeActions.contentMoved.next(<any>movedItems);
expect(nodeActions.moveNodeAction).toHaveBeenCalledWith(node.entry, initialParent); expect(nodeActions.moveNodeAction).toHaveBeenCalledWith(
expect(snackBar.open['calls'].argsFor(0)[0]).toBe('APP.MESSAGES.INFO.NODE_MOVE.SINGULAR'); node.entry,
initialParent
);
expect(snackBar.open['calls'].argsFor(0)[0]).toBe(
'APP.MESSAGES.INFO.NODE_MOVE.SINGULAR'
);
}); });
it('should restore deleted folder back to initial parent, after succeeded moving all its files', () => { it('should restore deleted folder back to initial parent, after succeeded moving all its files', () => {
@@ -581,7 +728,14 @@ describe('ContentManagementService', () => {
spyOn(contentApi, 'restoreNode').and.returnValue(of(null)); spyOn(contentApi, 'restoreNode').and.returnValue(of(null));
const initialParent = 'parent-id-0'; const initialParent = 'parent-id-0';
const node = { entry: { id: 'folder-to-move-id', name: 'conflicting-name', parentId: initialParent, isFolder: true } }; const node = {
entry: {
id: 'folder-to-move-id',
name: 'conflicting-name',
parentId: initialParent,
isFolder: true
}
};
const selection = [node]; const selection = [node];
const itemMoved = {}; // folder was empty const itemMoved = {}; // folder was empty
@@ -597,7 +751,9 @@ describe('ContentManagementService', () => {
nodeActions.contentMoved.next(<any>movedItems); nodeActions.contentMoved.next(<any>movedItems);
expect(contentApi.restoreNode).toHaveBeenCalled(); expect(contentApi.restoreNode).toHaveBeenCalled();
expect(snackBar.open['calls'].argsFor(0)[0]).toBe('APP.MESSAGES.INFO.NODE_MOVE.SINGULAR'); expect(snackBar.open['calls'].argsFor(0)[0]).toBe(
'APP.MESSAGES.INFO.NODE_MOVE.SINGULAR'
);
}); });
it('should notify when error occurs on Undo Move action', fakeAsync(done => { it('should notify when error occurs on Undo Move action', fakeAsync(done => {
@@ -609,11 +765,23 @@ describe('ContentManagementService', () => {
); );
const initialParent = 'parent-id-0'; const initialParent = 'parent-id-0';
const node = { entry: { id: 'node-to-move-id', name: 'conflicting-name', parentId: initialParent } }; const node = {
entry: {
id: 'node-to-move-id',
name: 'conflicting-name',
parentId: initialParent
}
};
const selection = [node]; const selection = [node];
const afterMoveParentId = 'parent-id-1'; const afterMoveParentId = 'parent-id-1';
const childMoved = { entry: { id: 'child-of-node-to-move-id', name: 'child-name', parentId: afterMoveParentId } }; const childMoved = {
entry: {
id: 'child-of-node-to-move-id',
name: 'child-name',
parentId: afterMoveParentId
}
};
nodeActions.moveDeletedEntries = [node]; // folder got deleted nodeActions.moveDeletedEntries = [node]; // folder got deleted
const movedItems = { const movedItems = {
@@ -629,7 +797,9 @@ describe('ContentManagementService', () => {
})); }));
it('should notify when some error of type Error occurs on Undo Move action', fakeAsync(done => { it('should notify when some error of type Error occurs on Undo Move action', fakeAsync(done => {
spyOn(contentApi, 'restoreNode').and.returnValue(throwError(new Error('oops!'))); spyOn(contentApi, 'restoreNode').and.returnValue(
throwError(new Error('oops!'))
);
actions$.pipe( actions$.pipe(
ofType<SnackbarErrorAction>(SNACKBAR_ERROR), ofType<SnackbarErrorAction>(SNACKBAR_ERROR),
@@ -637,10 +807,14 @@ describe('ContentManagementService', () => {
); );
const initialParent = 'parent-id-0'; const initialParent = 'parent-id-0';
const node = { entry: { id: 'node-to-move-id', name: 'name', parentId: initialParent } }; const node = {
entry: { id: 'node-to-move-id', name: 'name', parentId: initialParent }
};
const selection = [node]; const selection = [node];
const childMoved = { entry: { id: 'child-of-node-to-move-id', name: 'child-name' } }; const childMoved = {
entry: { id: 'child-of-node-to-move-id', name: 'child-name' }
};
nodeActions.moveDeletedEntries = [node]; // folder got deleted nodeActions.moveDeletedEntries = [node]; // folder got deleted
const movedItems = { const movedItems = {
@@ -656,7 +830,9 @@ describe('ContentManagementService', () => {
})); }));
it('should notify permission error when it occurs on Undo Move action', fakeAsync(done => { it('should notify permission error when it occurs on Undo Move action', fakeAsync(done => {
spyOn(contentApi, 'restoreNode').and.returnValue(throwError(new Error(JSON.stringify({error: {statusCode: 403}})))); spyOn(contentApi, 'restoreNode').and.returnValue(
throwError(new Error(JSON.stringify({ error: { statusCode: 403 } })))
);
actions$.pipe( actions$.pipe(
ofType<SnackbarErrorAction>(SNACKBAR_ERROR), ofType<SnackbarErrorAction>(SNACKBAR_ERROR),
@@ -664,10 +840,14 @@ describe('ContentManagementService', () => {
); );
const initialParent = 'parent-id-0'; const initialParent = 'parent-id-0';
const node = { entry: { id: 'node-to-move-id', name: 'name', parentId: initialParent } }; const node = {
entry: { id: 'node-to-move-id', name: 'name', parentId: initialParent }
};
const selection = [node]; const selection = [node];
const childMoved = { entry: { id: 'child-of-node-to-move-id', name: 'child-name' } }; const childMoved = {
entry: { id: 'child-of-node-to-move-id', name: 'child-name' }
};
nodeActions.moveDeletedEntries = [node]; // folder got deleted nodeActions.moveDeletedEntries = [node]; // folder got deleted
const movedItems = { const movedItems = {
@@ -752,7 +932,7 @@ describe('ContentManagementService', () => {
})); }));
it('should raise warning message when only one file is successful', fakeAsync(done => { it('should raise warning message when only one file is successful', fakeAsync(done => {
spyOn(contentApi, 'deleteNode').and.callFake((id) => { spyOn(contentApi, 'deleteNode').and.callFake(id => {
if (id === '1') { if (id === '1') {
return throwError(null); return throwError(null);
} else { } else {
@@ -776,7 +956,7 @@ describe('ContentManagementService', () => {
})); }));
it('should raise warning message when some files are successfully deleted', fakeAsync(done => { it('should raise warning message when some files are successfully deleted', fakeAsync(done => {
spyOn(contentApi, 'deleteNode').and.callFake((id) => { spyOn(contentApi, 'deleteNode').and.callFake(id => {
if (id === '1') { if (id === '1') {
return throwError(null); return throwError(null);
} }
@@ -807,7 +987,6 @@ describe('ContentManagementService', () => {
})); }));
}); });
describe('Permanent Delete', () => { describe('Permanent Delete', () => {
it('does not purge nodes if no selection', () => { it('does not purge nodes if no selection', () => {
spyOn(contentApi, 'purgeDeletedNode'); spyOn(contentApi, 'purgeDeletedNode');
@@ -834,7 +1013,7 @@ describe('ContentManagementService', () => {
}) })
); );
spyOn(contentApi, 'purgeDeletedNode').and.callFake((id) => { spyOn(contentApi, 'purgeDeletedNode').and.callFake(id => {
if (id === '1') { if (id === '1') {
return of({}); return of({});
} }
@@ -865,7 +1044,7 @@ describe('ContentManagementService', () => {
}) })
); );
spyOn(contentApi, 'purgeDeletedNode').and.callFake((id) => { spyOn(contentApi, 'purgeDeletedNode').and.callFake(id => {
if (id === '1') { if (id === '1') {
return of({}); return of({});
} }
@@ -903,9 +1082,7 @@ describe('ContentManagementService', () => {
spyOn(contentApi, 'purgeDeletedNode').and.returnValue(of({})); spyOn(contentApi, 'purgeDeletedNode').and.returnValue(of({}));
const selection = [ const selection = [{ entry: { id: '1', name: 'name1' } }];
{ entry: { id: '1', name: 'name1' } }
];
store.dispatch(new PurgeDeletedNodesAction(selection)); store.dispatch(new PurgeDeletedNodesAction(selection));
})); }));
@@ -920,9 +1097,7 @@ describe('ContentManagementService', () => {
spyOn(contentApi, 'purgeDeletedNode').and.returnValue(throwError({})); spyOn(contentApi, 'purgeDeletedNode').and.returnValue(throwError({}));
const selection = [ const selection = [{ entry: { id: '1', name: 'name1' } }];
{ entry: { id: '1', name: 'name1' } }
];
store.dispatch(new PurgeDeletedNodesAction(selection)); store.dispatch(new PurgeDeletedNodesAction(selection));
})); }));
@@ -934,7 +1109,7 @@ describe('ContentManagementService', () => {
done(); done();
}) })
); );
spyOn(contentApi, 'purgeDeletedNode').and.callFake((id) => { spyOn(contentApi, 'purgeDeletedNode').and.callFake(id => {
if (id === '1') { if (id === '1') {
return of({}); return of({});
} }
@@ -959,7 +1134,7 @@ describe('ContentManagementService', () => {
done(); done();
}) })
); );
spyOn(contentApi, 'purgeDeletedNode').and.callFake((id) => { spyOn(contentApi, 'purgeDeletedNode').and.callFake(id => {
if (id === '1') { if (id === '1') {
return throwError({}); return throwError({});
} }
@@ -1001,9 +1176,11 @@ describe('ContentManagementService', () => {
it('call restore nodes if selection has nodes with path', fakeAsync(() => { it('call restore nodes if selection has nodes with path', fakeAsync(() => {
spyOn(contentApi, 'restoreNode').and.returnValue(of({})); spyOn(contentApi, 'restoreNode').and.returnValue(of({}));
spyOn(contentApi, 'getDeletedNodes').and.returnValue(of({ spyOn(contentApi, 'getDeletedNodes').and.returnValue(
of({
list: { entries: [] } list: { entries: [] }
})); })
);
const path = { const path = {
elements: [ elements: [
@@ -1031,9 +1208,11 @@ describe('ContentManagementService', () => {
describe('refresh()', () => { describe('refresh()', () => {
it('dispatch event on finish', fakeAsync(done => { it('dispatch event on finish', fakeAsync(done => {
spyOn(contentApi, 'restoreNode').and.returnValue(of({})); spyOn(contentApi, 'restoreNode').and.returnValue(of({}));
spyOn(contentApi, 'getDeletedNodes').and.returnValue(of({ spyOn(contentApi, 'getDeletedNodes').and.returnValue(
of({
list: { entries: [] } list: { entries: [] }
})); })
);
const path = { const path = {
elements: [ elements: [
@@ -1061,9 +1240,11 @@ describe('ContentManagementService', () => {
describe('notification', () => { describe('notification', () => {
beforeEach(() => { beforeEach(() => {
spyOn(contentApi, 'getDeletedNodes').and.returnValue(of({ spyOn(contentApi, 'getDeletedNodes').and.returnValue(
of({
list: { entries: [] } list: { entries: [] }
})); })
);
}); });
it('should raise error message on partial multiple fail ', fakeAsync(done => { it('should raise error message on partial multiple fail ', fakeAsync(done => {
@@ -1074,7 +1255,7 @@ describe('ContentManagementService', () => {
map(action => done()) map(action => done())
); );
spyOn(contentApi, 'restoreNode').and.callFake((id) => { spyOn(contentApi, 'restoreNode').and.callFake(id => {
if (id === '1') { if (id === '1') {
return of({}); return of({});
} }
@@ -1124,9 +1305,7 @@ describe('ContentManagementService', () => {
] ]
}; };
const selection = [ const selection = [{ entry: { id: '1', name: 'name1', path } }];
{ entry: { id: '1', name: 'name1', path } }
];
store.dispatch(new RestoreDeletedNodesAction(selection)); store.dispatch(new RestoreDeletedNodesAction(selection));
})); }));
@@ -1150,9 +1329,7 @@ describe('ContentManagementService', () => {
] ]
}; };
const selection = [ const selection = [{ entry: { id: '1', name: 'name1', path } }];
{ entry: { id: '1', name: 'name1', path } }
];
store.dispatch(new RestoreDeletedNodesAction(selection)); store.dispatch(new RestoreDeletedNodesAction(selection));
})); }));
@@ -1176,15 +1353,13 @@ describe('ContentManagementService', () => {
] ]
}; };
const selection = [ const selection = [{ entry: { id: '1', name: 'name1', path } }];
{ entry: { id: '1', name: 'name1', path } }
];
store.dispatch(new RestoreDeletedNodesAction(selection)); store.dispatch(new RestoreDeletedNodesAction(selection));
})); }));
it('should raise info message when restore multiple nodes', fakeAsync(done => { it('should raise info message when restore multiple nodes', fakeAsync(done => {
spyOn(contentApi, 'restoreNode').and.callFake((id) => { spyOn(contentApi, 'restoreNode').and.callFake(id => {
if (id === '1') { if (id === '1') {
return of({}); return of({});
} }
@@ -1233,9 +1408,7 @@ describe('ContentManagementService', () => {
] ]
}; };
const selection = [ const selection = [{ entry: { id: '1', name: 'name1', path } }];
{ entry: { id: '1', name: 'name1', path } }
];
store.dispatch(new RestoreDeletedNodesAction(selection)); store.dispatch(new RestoreDeletedNodesAction(selection));
})); }));
@@ -1271,5 +1444,4 @@ describe('ContentManagementService', () => {
})); }));
}); });
}); });
}); });

View File

@@ -26,10 +26,22 @@
import { Subject, Observable, forkJoin, of, zip } from 'rxjs'; import { Subject, Observable, forkJoin, of, zip } from 'rxjs';
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { MatDialog, MatSnackBar } from '@angular/material'; import { MatDialog, MatSnackBar } from '@angular/material';
import { FolderDialogComponent, ConfirmDialogComponent, ShareDialogComponent } from '@alfresco/adf-content-services'; import {
FolderDialogComponent,
ConfirmDialogComponent,
ShareDialogComponent
} from '@alfresco/adf-content-services';
import { LibraryDialogComponent } from '../dialogs/library/library.dialog'; import { LibraryDialogComponent } from '../dialogs/library/library.dialog';
import { SnackbarErrorAction, SnackbarInfoAction, SnackbarAction, SnackbarWarningAction, import {
NavigateRouteAction, SnackbarUserAction, UndoDeleteNodesAction, SetSelectedNodesAction } from '../store/actions'; SnackbarErrorAction,
SnackbarInfoAction,
SnackbarAction,
SnackbarWarningAction,
NavigateRouteAction,
SnackbarUserAction,
UndoDeleteNodesAction,
SetSelectedNodesAction
} from '../store/actions';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
import { AppStore } from '../store/states'; import { AppStore } from '../store/states';
import { import {
@@ -133,12 +145,9 @@ export class ContentManagementService {
const id = node.entry.nodeId || (<any>node).entry.guid; const id = node.entry.nodeId || (<any>node).entry.guid;
if (id) { if (id) {
this.contentApi this.contentApi.getNodeInfo(id).subscribe(entry => {
.getNodeInfo(id)
.subscribe(entry => {
this.openVersionManagerDialog(entry); this.openVersionManagerDialog(entry);
}); });
} else { } else {
this.openVersionManagerDialog(node.entry); this.openVersionManagerDialog(node.entry);
} }
@@ -162,7 +171,6 @@ export class ContentManagementService {
shareNode(node: MinimalNodeEntity): void { shareNode(node: MinimalNodeEntity): void {
if (node && node.entry && node.entry.isFile) { if (node && node.entry && node.entry.isFile) {
this.store this.store
.select(sharedUrl) .select(sharedUrl)
.pipe(take(1)) .pipe(take(1))
@@ -244,23 +252,21 @@ export class ContentManagementService {
() => { () => {
this.libraryDeleted.next(id); this.libraryDeleted.next(id);
this.store.dispatch( this.store.dispatch(
new SnackbarInfoAction( new SnackbarInfoAction('APP.MESSAGES.INFO.LIBRARY_DELETED')
'APP.MESSAGES.INFO.LIBRARY_DELETED'
)
); );
}, },
() => { () => {
this.store.dispatch( this.store.dispatch(
new SnackbarErrorAction( new SnackbarErrorAction('APP.MESSAGES.ERRORS.DELETE_LIBRARY_FAILED')
'APP.MESSAGES.ERRORS.DELETE_LIBRARY_FAILED'
)
); );
} }
); );
} }
async unshareNodes(links: Array<MinimalNodeEntity>) { async unshareNodes(links: Array<MinimalNodeEntity>) {
const promises = links.map(link => this.contentApi.deleteSharedLink(link.entry.id).toPromise()); const promises = links.map(link =>
this.contentApi.deleteSharedLink(link.entry.id).toPromise()
);
await Promise.all(promises); await Promise.all(promises);
this.linksUnshared.next(); this.linksUnshared.next();
} }
@@ -330,10 +336,7 @@ export class ContentManagementService {
) )
.subscribe((nodes: DeletedNodesPaging) => { .subscribe((nodes: DeletedNodesPaging) => {
const selectedNodes = this.diff(status.fail, selection, false); const selectedNodes = this.diff(status.fail, selection, false);
const remainingNodes = this.diff( const remainingNodes = this.diff(selectedNodes, nodes.list.entries);
selectedNodes,
nodes.list.entries
);
if (!remainingNodes.length) { if (!remainingNodes.length) {
this.showRestoreNotification(status); this.showRestoreNotification(status);
@@ -376,16 +379,13 @@ export class ContentManagementService {
if (failedItems) { if (failedItems) {
if (numberOfCopiedItems) { if (numberOfCopiedItems) {
i18MessageSuffix = i18MessageSuffix =
numberOfCopiedItems === 1 numberOfCopiedItems === 1 ? 'PARTIAL_SINGULAR' : 'PARTIAL_PLURAL';
? 'PARTIAL_SINGULAR'
: 'PARTIAL_PLURAL';
} else { } else {
i18MessageSuffix = i18MessageSuffix =
failedItems === 1 ? 'FAIL_SINGULAR' : 'FAIL_PLURAL'; failedItems === 1 ? 'FAIL_SINGULAR' : 'FAIL_PLURAL';
} }
} else { } else {
i18MessageSuffix = i18MessageSuffix = numberOfCopiedItems === 1 ? 'SINGULAR' : 'PLURAL';
numberOfCopiedItems === 1 ? 'SINGULAR' : 'PLURAL';
} }
i18nMessageString = `APP.MESSAGES.INFO.NODE_COPY.${i18MessageSuffix}`; i18nMessageString = `APP.MESSAGES.INFO.NODE_COPY.${i18MessageSuffix}`;
@@ -461,47 +461,63 @@ export class ContentManagementService {
this.nodeActionsService.moveNodes(nodes, permissionForMove), this.nodeActionsService.moveNodes(nodes, permissionForMove),
this.nodeActionsService.contentMoved this.nodeActionsService.contentMoved
).subscribe( ).subscribe(
(result) => { result => {
const [operationResult, moveResponse] = result; const [operationResult, moveResponse] = result;
this.showMoveMessage(nodes, operationResult, moveResponse); this.showMoveMessage(nodes, operationResult, moveResponse);
this.nodesMoved.next(null); this.nodesMoved.next(null);
}, },
(error) => { error => {
this.showMoveMessage(nodes, error); this.showMoveMessage(nodes, error);
} }
); );
} }
private undoMoveNodes(moveResponse, selectionParentId) { private undoMoveNodes(moveResponse, selectionParentId) {
const movedNodes = (moveResponse && moveResponse['succeeded']) ? moveResponse['succeeded'] : []; const movedNodes =
const partiallyMovedNodes = (moveResponse && moveResponse['partiallySucceeded']) ? moveResponse['partiallySucceeded'] : []; moveResponse && moveResponse['succeeded']
? moveResponse['succeeded']
: [];
const partiallyMovedNodes =
moveResponse && moveResponse['partiallySucceeded']
? moveResponse['partiallySucceeded']
: [];
const restoreDeletedNodesBatch = this.nodeActionsService.moveDeletedEntries const restoreDeletedNodesBatch = this.nodeActionsService.moveDeletedEntries.map(
.map((folderEntry) => { folderEntry => {
return this.contentApi return this.contentApi
.restoreNode(folderEntry.nodeId || folderEntry.id) .restoreNode(folderEntry.nodeId || folderEntry.id)
.pipe(map(node => node.entry)); .pipe(map(node => node.entry));
}); }
);
zip(...restoreDeletedNodesBatch, of(null)) zip(...restoreDeletedNodesBatch, of(null))
.pipe(mergeMap(() => { .pipe(
mergeMap(() => {
const nodesToBeMovedBack = [...partiallyMovedNodes, ...movedNodes]; const nodesToBeMovedBack = [...partiallyMovedNodes, ...movedNodes];
const revertMoveBatch = this.nodeActionsService const revertMoveBatch = this.nodeActionsService
.flatten(nodesToBeMovedBack) .flatten(nodesToBeMovedBack)
.filter(node => node.entry || (node.itemMoved && node.itemMoved.entry)) .filter(
.map((node) => { node => node.entry || (node.itemMoved && node.itemMoved.entry)
)
.map(node => {
if (node.itemMoved) { if (node.itemMoved) {
return this.nodeActionsService.moveNodeAction(node.itemMoved.entry, node.initialParentId); return this.nodeActionsService.moveNodeAction(
node.itemMoved.entry,
node.initialParentId
);
} else { } else {
return this.nodeActionsService.moveNodeAction(node.entry, selectionParentId); return this.nodeActionsService.moveNodeAction(
node.entry,
selectionParentId
);
} }
}); });
return zip(...revertMoveBatch, of(null)); return zip(...revertMoveBatch, of(null));
})) })
)
.subscribe( .subscribe(
() => { () => {
this.nodesMoved.next(null); this.nodesMoved.next(null);
@@ -514,7 +530,11 @@ export class ContentManagementService {
errorJson = JSON.parse(error.message); errorJson = JSON.parse(error.message);
} catch {} } catch {}
if (errorJson && errorJson.error && errorJson.error.statusCode === 403) { if (
errorJson &&
errorJson.error &&
errorJson.error.statusCode === 403
) {
message = 'APP.MESSAGES.ERRORS.PERMISSION'; message = 'APP.MESSAGES.ERRORS.PERMISSION';
} }
@@ -574,9 +594,7 @@ export class ContentManagementService {
private undoDeleteNode(item: DeletedNodeInfo): Observable<DeletedNodeInfo> { private undoDeleteNode(item: DeletedNodeInfo): Observable<DeletedNodeInfo> {
const { id, name } = item; const { id, name } = item;
return this.contentApi return this.contentApi.restoreNode(id).pipe(
.restoreNode(id)
.pipe(
map(() => { map(() => {
return { return {
id, id,
@@ -614,8 +632,7 @@ export class ContentManagementService {
private restoreNode(node: MinimalNodeEntity): Observable<RestoredNode> { private restoreNode(node: MinimalNodeEntity): Observable<RestoredNode> {
const { entry } = node; const { entry } = node;
return this.contentApi.restoreNode(entry.id) return this.contentApi.restoreNode(entry.id).pipe(
.pipe(
map(() => ({ map(() => ({
status: 1, status: 1,
entry entry
@@ -655,9 +672,7 @@ export class ContentManagementService {
private purgeDeletedNode(node: NodeInfo): Observable<DeletedNodeInfo> { private purgeDeletedNode(node: NodeInfo): Observable<DeletedNodeInfo> {
const { id, name } = node; const { id, name } = node;
return this.contentApi return this.contentApi.purgeDeletedNode(id).pipe(
.purgeDeletedNode(id)
.pipe(
map(() => ({ map(() => ({
status: 1, status: 1,
id, id,
@@ -851,10 +866,7 @@ export class ContentManagementService {
const { name } = node.entry; const { name } = node.entry;
const id = node.entry.nodeId || node.entry.id; const id = node.entry.nodeId || node.entry.id;
return this.contentApi.deleteNode(id).pipe(
return this.contentApi
.deleteNode(id)
.pipe(
map(() => { map(() => {
return { return {
id, id,
@@ -881,10 +893,9 @@ export class ContentManagementService {
} }
if (status.allSucceeded && !status.oneSucceeded) { if (status.allSucceeded && !status.oneSucceeded) {
return new SnackbarInfoAction( return new SnackbarInfoAction('APP.MESSAGES.INFO.NODE_DELETION.PLURAL', {
'APP.MESSAGES.INFO.NODE_DELETION.PLURAL', number: status.success.length
{ number: status.success.length } });
);
} }
if (status.someFailed && status.someSucceeded && !status.oneSucceeded) { if (status.someFailed && status.someSucceeded && !status.oneSucceeded) {
@@ -908,10 +919,9 @@ export class ContentManagementService {
} }
if (status.oneFailed && !status.someSucceeded) { if (status.oneFailed && !status.someSucceeded) {
return new SnackbarErrorAction( return new SnackbarErrorAction('APP.MESSAGES.ERRORS.NODE_DELETION', {
'APP.MESSAGES.ERRORS.NODE_DELETION', name: status.fail[0].name
{ name: status.fail[0].name } });
);
} }
if (status.oneSucceeded && !status.someFailed) { if (status.oneSucceeded && !status.someFailed) {
@@ -924,10 +934,23 @@ export class ContentManagementService {
return null; return null;
} }
private showMoveMessage(nodes: Array<MinimalNodeEntity>, info: any, moveResponse?: any) { private showMoveMessage(
const succeeded = (moveResponse && moveResponse['succeeded']) ? moveResponse['succeeded'].length : 0; nodes: Array<MinimalNodeEntity>,
const partiallySucceeded = (moveResponse && moveResponse['partiallySucceeded']) ? moveResponse['partiallySucceeded'].length : 0; info: any,
const failures = (moveResponse && moveResponse['failed']) ? moveResponse['failed'].length : 0; moveResponse?: any
) {
const succeeded =
moveResponse && moveResponse['succeeded']
? moveResponse['succeeded'].length
: 0;
const partiallySucceeded =
moveResponse && moveResponse['partiallySucceeded']
? moveResponse['partiallySucceeded'].length
: 0;
const failures =
moveResponse && moveResponse['failed']
? moveResponse['failed'].length
: 0;
let successMessage = ''; let successMessage = '';
let partialSuccessMessage = ''; let partialSuccessMessage = '';
@@ -935,28 +958,29 @@ export class ContentManagementService {
let errorMessage = ''; let errorMessage = '';
if (typeof info === 'string') { if (typeof info === 'string') {
// in case of success // in case of success
if (info.toLowerCase().indexOf('succes') !== -1) { if (info.toLowerCase().indexOf('succes') !== -1) {
const i18nMessageString = 'APP.MESSAGES.INFO.NODE_MOVE.'; const i18nMessageString = 'APP.MESSAGES.INFO.NODE_MOVE.';
let i18MessageSuffix = ''; let i18MessageSuffix = '';
if (succeeded) { if (succeeded) {
i18MessageSuffix = ( succeeded === 1 ) ? 'SINGULAR' : 'PLURAL'; i18MessageSuffix = succeeded === 1 ? 'SINGULAR' : 'PLURAL';
successMessage = `${i18nMessageString}${i18MessageSuffix}`; successMessage = `${i18nMessageString}${i18MessageSuffix}`;
} }
if (partiallySucceeded) { if (partiallySucceeded) {
i18MessageSuffix = ( partiallySucceeded === 1 ) ? 'PARTIAL.SINGULAR' : 'PARTIAL.PLURAL'; i18MessageSuffix =
partiallySucceeded === 1 ? 'PARTIAL.SINGULAR' : 'PARTIAL.PLURAL';
partialSuccessMessage = `${i18nMessageString}${i18MessageSuffix}`; partialSuccessMessage = `${i18nMessageString}${i18MessageSuffix}`;
} }
if (failures) { if (failures) {
// if moving failed for ALL nodes, emit error // if moving failed for ALL nodes, emit error
if (failures === nodes.length) { if (failures === nodes.length) {
const errors = this.nodeActionsService.flatten(moveResponse['failed']); const errors = this.nodeActionsService.flatten(
moveResponse['failed']
);
errorMessage = this.getErrorMessage(errors[0]); errorMessage = this.getErrorMessage(errors[0]);
} else { } else {
i18MessageSuffix = 'PARTIAL.FAIL'; i18MessageSuffix = 'PARTIAL.FAIL';
failedMessage = `${i18nMessageString}${i18MessageSuffix}`; failedMessage = `${i18nMessageString}${i18MessageSuffix}`;
@@ -965,18 +989,24 @@ export class ContentManagementService {
} else { } else {
errorMessage = 'APP.MESSAGES.ERRORS.GENERIC'; errorMessage = 'APP.MESSAGES.ERRORS.GENERIC';
} }
} else { } else {
errorMessage = this.getErrorMessage(info); errorMessage = this.getErrorMessage(info);
} }
const undo = (succeeded + partiallySucceeded > 0) ? this.translation.instant('APP.ACTIONS.UNDO') : ''; const undo =
succeeded + partiallySucceeded > 0
? this.translation.instant('APP.ACTIONS.UNDO')
: '';
failedMessage = errorMessage ? errorMessage : failedMessage; failedMessage = errorMessage ? errorMessage : failedMessage;
const beforePartialSuccessMessage = (successMessage && partialSuccessMessage) ? ' ' : ''; const beforePartialSuccessMessage =
const beforeFailedMessage = ((successMessage || partialSuccessMessage) && failedMessage) ? ' ' : ''; successMessage && partialSuccessMessage ? ' ' : '';
const beforeFailedMessage =
(successMessage || partialSuccessMessage) && failedMessage ? ' ' : '';
const initialParentId = this.nodeActionsService.getEntryParentId(nodes[0].entry); const initialParentId = this.nodeActionsService.getEntryParentId(
nodes[0].entry
);
const messages = this.translation.instant( const messages = this.translation.instant(
[successMessage, partialSuccessMessage, failedMessage], [successMessage, partialSuccessMessage, failedMessage],
@@ -986,13 +1016,17 @@ export class ContentManagementService {
// TODO: review in terms of i18n // TODO: review in terms of i18n
this.snackBar this.snackBar
.open( .open(
messages[successMessage] messages[successMessage] +
+ beforePartialSuccessMessage + messages[partialSuccessMessage] beforePartialSuccessMessage +
+ beforeFailedMessage + messages[failedMessage] messages[partialSuccessMessage] +
, undo, { beforeFailedMessage +
messages[failedMessage],
undo,
{
panelClass: 'info-snackbar', panelClass: 'info-snackbar',
duration: 3000 duration: 3000
}) }
)
.onAction() .onAction()
.subscribe(() => this.undoMoveNodes(moveResponse, initialParentId)); .subscribe(() => this.undoMoveNodes(moveResponse, initialParentId));
} }
@@ -1001,16 +1035,18 @@ export class ContentManagementService {
let i18nMessageString = 'APP.MESSAGES.ERRORS.GENERIC'; let i18nMessageString = 'APP.MESSAGES.ERRORS.GENERIC';
try { try {
const { error: { statusCode } } = JSON.parse(errorObject.message); const {
error: { statusCode }
} = JSON.parse(errorObject.message);
if (statusCode === 409) { if (statusCode === 409) {
i18nMessageString = 'APP.MESSAGES.ERRORS.NODE_MOVE'; i18nMessageString = 'APP.MESSAGES.ERRORS.NODE_MOVE';
} else if (statusCode === 403) { } else if (statusCode === 403) {
i18nMessageString = 'APP.MESSAGES.ERRORS.PERMISSION'; i18nMessageString = 'APP.MESSAGES.ERRORS.PERMISSION';
} }
} catch (err) {
} catch (err) { /* Do nothing, keep the original message */ } /* Do nothing, keep the original message */
}
return i18nMessageString; return i18nMessageString;
} }

File diff suppressed because it is too large Load Diff

View File

@@ -27,9 +27,22 @@ import { Injectable } from '@angular/core';
import { MatDialog } from '@angular/material'; import { MatDialog } from '@angular/material';
import { Observable, Subject, of, zip, from } from 'rxjs'; import { Observable, Subject, of, zip, from } from 'rxjs';
import { AlfrescoApiService, ContentService, DataColumn, TranslationService } from '@alfresco/adf-core'; import {
import { DocumentListService, ContentNodeSelectorComponent, ContentNodeSelectorComponentData } from '@alfresco/adf-content-services'; AlfrescoApiService,
import { MinimalNodeEntity, MinimalNodeEntryEntity, SitePaging } from 'alfresco-js-api'; ContentService,
DataColumn,
TranslationService
} from '@alfresco/adf-core';
import {
DocumentListService,
ContentNodeSelectorComponent,
ContentNodeSelectorComponentData
} from '@alfresco/adf-content-services';
import {
MinimalNodeEntity,
MinimalNodeEntryEntity,
SitePaging
} from 'alfresco-js-api';
import { ContentApiService } from '../services/content-api.service'; import { ContentApiService } from '../services/content-api.service';
import { catchError, map, mergeMap } from 'rxjs/operators'; import { catchError, map, mergeMap } from 'rxjs/operators';
@@ -38,17 +51,21 @@ export class NodeActionsService {
static SNACK_MESSAGE_DURATION_WITH_UNDO = 10000; static SNACK_MESSAGE_DURATION_WITH_UNDO = 10000;
static SNACK_MESSAGE_DURATION = 3000; static SNACK_MESSAGE_DURATION = 3000;
contentCopied: Subject<MinimalNodeEntity[]> = new Subject<MinimalNodeEntity[]>(); contentCopied: Subject<MinimalNodeEntity[]> = new Subject<
MinimalNodeEntity[]
>();
contentMoved: Subject<any> = new Subject<any>(); contentMoved: Subject<any> = new Subject<any>();
moveDeletedEntries: any[] = []; moveDeletedEntries: any[] = [];
isSitesDestinationAvailable = false; isSitesDestinationAvailable = false;
constructor(private contentService: ContentService, constructor(
private contentService: ContentService,
private contentApi: ContentApiService, private contentApi: ContentApiService,
private dialog: MatDialog, private dialog: MatDialog,
private documentListService: DocumentListService, private documentListService: DocumentListService,
private apiService: AlfrescoApiService, private apiService: AlfrescoApiService,
private translation: TranslationService) {} private translation: TranslationService
) {}
/** /**
* Copy node list * Copy node list
@@ -56,7 +73,10 @@ export class NodeActionsService {
* @param contentEntities nodes to copy * @param contentEntities nodes to copy
* @param permission permission which is needed to apply the action * @param permission permission which is needed to apply the action
*/ */
public copyNodes(contentEntities: any[], permission?: string): Subject<string> { public copyNodes(
contentEntities: any[],
permission?: string
): Subject<string> {
return this.doBatchOperation('copy', contentEntities, permission); return this.doBatchOperation('copy', contentEntities, permission);
} }
@@ -66,7 +86,10 @@ export class NodeActionsService {
* @param contentEntities nodes to move * @param contentEntities nodes to move
* @param permission permission which is needed to apply the action * @param permission permission which is needed to apply the action
*/ */
public moveNodes(contentEntities: any[], permission?: string): Subject<string> { public moveNodes(
contentEntities: any[],
permission?: string
): Subject<string> {
return this.doBatchOperation('move', contentEntities, permission); return this.doBatchOperation('move', contentEntities, permission);
} }
@@ -77,17 +100,23 @@ export class NodeActionsService {
* @param contentEntities the contentEntities which have to have the action performed on * @param contentEntities the contentEntities which have to have the action performed on
* @param permission permission which is needed to apply the action * @param permission permission which is needed to apply the action
*/ */
doBatchOperation(action: string, contentEntities: any[], permission?: string): Subject<string> { doBatchOperation(
action: string,
contentEntities: any[],
permission?: string
): Subject<string> {
const observable: Subject<string> = new Subject<string>(); const observable: Subject<string> = new Subject<string>();
if (!this.isEntryEntitiesArray(contentEntities)) { if (!this.isEntryEntitiesArray(contentEntities)) {
observable.error(new Error(JSON.stringify({error: {statusCode: 400}}))); observable.error(
new Error(JSON.stringify({ error: { statusCode: 400 } }))
);
} else if (this.checkPermission(action, contentEntities, permission)) { } else if (this.checkPermission(action, contentEntities, permission)) {
const destinationSelection = this.getContentNodeSelection(
const destinationSelection = this.getContentNodeSelection(action, contentEntities); action,
contentEntities
);
destinationSelection.subscribe((selections: MinimalNodeEntryEntity[]) => { destinationSelection.subscribe((selections: MinimalNodeEntryEntity[]) => {
const contentEntry = contentEntities[0].entry; const contentEntry = contentEntities[0].entry;
// Check if there's nodeId for Shared Files // Check if there's nodeId for Shared Files
const contentEntryId = contentEntry.nodeId || contentEntry.id; const contentEntryId = contentEntry.nodeId || contentEntry.id;
@@ -98,35 +127,39 @@ export class NodeActionsService {
const selection = selections[0]; const selection = selections[0];
let action$: Observable<any>; let action$: Observable<any>;
if (action === 'move' && contentEntities.length === 1 && type === 'content') { if (
action$ = this.documentListService.moveNode(contentEntryId, selection.id); action === 'move' &&
contentEntities.length === 1 &&
type === 'content'
) {
action$ = this.documentListService.moveNode(
contentEntryId,
selection.id
);
} else { } else {
contentEntities.forEach((node) => { contentEntities.forEach(node => {
batch.push(this[`${action}NodeAction`](node.entry, selection.id)); batch.push(this[`${action}NodeAction`](node.entry, selection.id));
}); });
action$ = zip(...batch); action$ = zip(...batch);
} }
action$ action$.subscribe(newContent => {
.subscribe( observable.next(
(newContent) => { `OPERATION.SUCCES.${type.toUpperCase()}.${action.toUpperCase()}`
observable.next(`OPERATION.SUCCES.${type.toUpperCase()}.${action.toUpperCase()}`); );
const processedData = this.processResponse(newContent); const processedData = this.processResponse(newContent);
if (action === 'copy') { if (action === 'copy') {
this.contentCopied.next(processedData.succeeded); this.contentCopied.next(processedData.succeeded);
} else if (action === 'move') { } else if (action === 'move') {
this.contentMoved.next(processedData); this.contentMoved.next(processedData);
} }
}, observable.error.bind(observable));
},
observable.error.bind(observable)
);
}); });
} else { } else {
observable.error(new Error(JSON.stringify({error: {statusCode: 403}}))); observable.error(
new Error(JSON.stringify({ error: { statusCode: 403 } }))
);
} }
return observable; return observable;
@@ -134,14 +167,18 @@ export class NodeActionsService {
isEntryEntitiesArray(contentEntities: any[]): boolean { isEntryEntitiesArray(contentEntities: any[]): boolean {
if (contentEntities && contentEntities.length) { if (contentEntities && contentEntities.length) {
const nonEntryNode = contentEntities.find(node => (!node || !node.entry || !(node.entry.nodeId || node.entry.id))); const nonEntryNode = contentEntities.find(
node => !node || !node.entry || !(node.entry.nodeId || node.entry.id)
);
return !nonEntryNode; return !nonEntryNode;
} }
return false; return false;
} }
checkPermission(action: string, contentEntities: any[], permission?: string) { checkPermission(action: string, contentEntities: any[], permission?: string) {
const notAllowedNode = contentEntities.find(node => !this.isActionAllowed(action, node.entry, permission)); const notAllowedNode = contentEntities.find(
node => !this.isActionAllowed(action, node.entry, permission)
);
return !notAllowedNode; return !notAllowedNode;
} }
@@ -150,16 +187,25 @@ export class NodeActionsService {
if (nodeEntry.parentId) { if (nodeEntry.parentId) {
entryParentId = nodeEntry.parentId; entryParentId = nodeEntry.parentId;
} else if (
} else if (nodeEntry.path && nodeEntry.path.elements && nodeEntry.path.elements.length) { nodeEntry.path &&
entryParentId = nodeEntry.path.elements[nodeEntry.path.elements.length - 1].id; nodeEntry.path.elements &&
nodeEntry.path.elements.length
) {
entryParentId =
nodeEntry.path.elements[nodeEntry.path.elements.length - 1].id;
} }
return entryParentId; return entryParentId;
} }
getContentNodeSelection(action: string, contentEntities: MinimalNodeEntity[]): Subject<MinimalNodeEntryEntity[]> { getContentNodeSelection(
const currentParentFolderId = this.getEntryParentId(contentEntities[0].entry); action: string,
contentEntities: MinimalNodeEntity[]
): Subject<MinimalNodeEntryEntity[]> {
const currentParentFolderId = this.getEntryParentId(
contentEntities[0].entry
);
const customDropdown: SitePaging = { const customDropdown: SitePaging = {
list: { list: {
@@ -219,7 +265,10 @@ export class NodeActionsService {
} }
const number = nodes.length; const number = nodes.length;
return this.translation.instant(`NODE_SELECTOR.${action.toUpperCase()}_${keyPrefix}`, {name, number}); return this.translation.instant(
`NODE_SELECTOR.${action.toUpperCase()}_${keyPrefix}`,
{ name, number }
);
} }
private canCopyMoveInsideIt(entry: MinimalNodeEntryEntity): boolean { private canCopyMoveInsideIt(entry: MinimalNodeEntryEntity): boolean {
@@ -231,7 +280,11 @@ export class NodeActionsService {
} }
private isSite(entry) { private isSite(entry) {
return !!entry.guid || entry.nodeType === 'st:site' || entry.nodeType === 'st:sites'; return (
!!entry.guid ||
entry.nodeType === 'st:site' ||
entry.nodeType === 'st:sites'
);
} }
close() { close() {
@@ -240,7 +293,6 @@ export class NodeActionsService {
// todo: review this approach once 5.2.3 is out // todo: review this approach once 5.2.3 is out
private customizeBreadcrumb(node: MinimalNodeEntryEntity) { private customizeBreadcrumb(node: MinimalNodeEntryEntity) {
if (node && node.path && node.path.elements) { if (node && node.path && node.path.elements) {
const elements = node.path.elements; const elements = node.path.elements;
@@ -250,16 +302,16 @@ export class NodeActionsService {
// make sure first item is 'Personal Files' // make sure first item is 'Personal Files'
if (elements[0]) { if (elements[0]) {
elements[0].name = this.translation.instant('APP.BROWSE.PERSONAL.TITLE'); elements[0].name = this.translation.instant(
'APP.BROWSE.PERSONAL.TITLE'
);
elements[0].id = '-my-'; elements[0].id = '-my-';
} else { } else {
node.name = this.translation.instant('APP.BROWSE.PERSONAL.TITLE'); node.name = this.translation.instant('APP.BROWSE.PERSONAL.TITLE');
} }
} else if (elements[1].name === 'Sites') { } else if (elements[1].name === 'Sites') {
this.normalizeSitePath(node); this.normalizeSitePath(node);
} }
} else if (elements.length === 1) { } else if (elements.length === 1) {
if (node.name === 'Sites') { if (node.name === 'Sites') {
node.name = this.translation.instant('APP.BROWSE.LIBRARIES.TITLE'); node.name = this.translation.instant('APP.BROWSE.LIBRARIES.TITLE');
@@ -313,7 +365,6 @@ export class NodeActionsService {
copyNodeAction(nodeEntry, selectionId): Observable<any> { copyNodeAction(nodeEntry, selectionId): Observable<any> {
if (nodeEntry.isFolder) { if (nodeEntry.isFolder) {
return this.copyFolderAction(nodeEntry, selectionId); return this.copyFolderAction(nodeEntry, selectionId);
} else { } else {
// any other type is treated as 'content' // any other type is treated as 'content'
return this.copyContentAction(nodeEntry, selectionId); return this.copyContentAction(nodeEntry, selectionId);
@@ -326,22 +377,30 @@ export class NodeActionsService {
const contentEntryId = contentEntry.nodeId || contentEntry.id; const contentEntryId = contentEntry.nodeId || contentEntry.id;
// use local method until new name parameter is added on ADF copyNode // use local method until new name parameter is added on ADF copyNode
return this.copyNode(contentEntryId, selectionId, _oldName) return this.copyNode(contentEntryId, selectionId, _oldName).pipe(
.pipe(catchError((err) => { catchError(err => {
let errStatusCode; let errStatusCode;
try { try {
const {error: {statusCode}} = JSON.parse(err.message); const {
error: { statusCode }
} = JSON.parse(err.message);
errStatusCode = statusCode; errStatusCode = statusCode;
} catch (e) { // } catch (e) {
//
} }
if (errStatusCode && errStatusCode === 409) { if (errStatusCode && errStatusCode === 409) {
return this.copyContentAction(contentEntry, selectionId, this.getNewNameFrom(_oldName, contentEntry.name)); return this.copyContentAction(
contentEntry,
selectionId,
this.getNewNameFrom(_oldName, contentEntry.name)
);
} else { } else {
// do not throw error, to be able to show message in case of partial copy of files // do not throw error, to be able to show message in case of partial copy of files
return of(err || 'Server error'); return of(err || 'Server error');
} }
})); })
);
} }
copyFolderAction(contentEntry, selectionId): Observable<any> { copyFolderAction(contentEntry, selectionId): Observable<any> {
@@ -351,33 +410,45 @@ export class NodeActionsService {
let $childrenToCopy: Observable<any>; let $childrenToCopy: Observable<any>;
let newDestinationFolder; let newDestinationFolder;
return this.copyNode(contentEntryId, selectionId, contentEntry.name) return this.copyNode(contentEntryId, selectionId, contentEntry.name).pipe(
.pipe( catchError(err => {
catchError((err) => {
let errStatusCode; let errStatusCode;
try { try {
const {error: {statusCode}} = JSON.parse(err.message); const {
error: { statusCode }
} = JSON.parse(err.message);
errStatusCode = statusCode; errStatusCode = statusCode;
} catch {} } catch {}
if (errStatusCode && errStatusCode === 409) { if (errStatusCode && errStatusCode === 409) {
$destinationFolder = this.getChildByName(
$destinationFolder = this.getChildByName(selectionId, contentEntry.name); selectionId,
contentEntry.name
);
$childrenToCopy = this.getNodeChildren(contentEntryId); $childrenToCopy = this.getNodeChildren(contentEntryId);
return $destinationFolder return $destinationFolder.pipe(
.pipe( mergeMap(destination => {
mergeMap((destination) => {
newDestinationFolder = destination; newDestinationFolder = destination;
return $childrenToCopy; return $childrenToCopy;
}), }),
mergeMap((nodesToCopy) => { mergeMap(nodesToCopy => {
const batch = []; const batch = [];
nodesToCopy.list.entries.forEach((node) => { nodesToCopy.list.entries.forEach(node => {
if (node.entry.isFolder) { if (node.entry.isFolder) {
batch.push(this.copyFolderAction(node.entry, newDestinationFolder.entry.id)); batch.push(
this.copyFolderAction(
node.entry,
newDestinationFolder.entry.id
)
);
} else { } else {
batch.push(this.copyContentAction(node.entry, newDestinationFolder.entry.id)); batch.push(
this.copyContentAction(
node.entry,
newDestinationFolder.entry.id
)
);
} }
}); });
@@ -387,7 +458,6 @@ export class NodeActionsService {
return zip(...batch); return zip(...batch);
}) })
); );
} else { } else {
// do not throw error, to be able to show message in case of partial copy of files // do not throw error, to be able to show message in case of partial copy of files
return of(err || 'Server error'); return of(err || 'Server error');
@@ -402,9 +472,8 @@ export class NodeActionsService {
if (nodeEntry.isFolder) { if (nodeEntry.isFolder) {
const initialParentId = nodeEntry.parentId; const initialParentId = nodeEntry.parentId;
return this.moveFolderAction(nodeEntry, selectionId) return this.moveFolderAction(nodeEntry, selectionId).pipe(
.pipe(mergeMap((newContent) => { mergeMap(newContent => {
// take no extra action, if folder is moved to the same location // take no extra action, if folder is moved to the same location
if (initialParentId === selectionId) { if (initialParentId === selectionId) {
return of(newContent); return of(newContent);
@@ -415,18 +484,14 @@ export class NodeActionsService {
// else, check if moving this nodeEntry succeeded for ALL of its nodes // else, check if moving this nodeEntry succeeded for ALL of its nodes
if (processedData.failed.length === 0) { if (processedData.failed.length === 0) {
// check if folder still exists on location // check if folder still exists on location
return this.getChildByName(initialParentId, nodeEntry.name) return this.getChildByName(initialParentId, nodeEntry.name).pipe(
.pipe(
mergeMap(folderOnInitialLocation => { mergeMap(folderOnInitialLocation => {
if (folderOnInitialLocation) { if (folderOnInitialLocation) {
// Check if there's nodeId for Shared Files // Check if there's nodeId for Shared Files
const nodeEntryId = nodeEntry.nodeId || nodeEntry.id; const nodeEntryId = nodeEntry.nodeId || nodeEntry.id;
// delete it from location // delete it from location
return this.contentApi return this.contentApi.deleteNode(nodeEntryId).pipe(
.deleteNode(nodeEntryId)
.pipe(
mergeMap(() => { mergeMap(() => {
this.moveDeletedEntries.push(nodeEntry); this.moveDeletedEntries.push(nodeEntry);
return of(newContent); return of(newContent);
@@ -436,11 +501,10 @@ export class NodeActionsService {
return of(newContent); return of(newContent);
}) })
); );
} }
return of(newContent); return of(newContent);
})); })
);
} else { } else {
// any other type is treated as 'content' // any other type is treated as 'content'
return this.moveContentAction(nodeEntry, selectionId); return this.moveContentAction(nodeEntry, selectionId);
@@ -455,37 +519,50 @@ export class NodeActionsService {
let $childrenToMove: Observable<any>; let $childrenToMove: Observable<any>;
let newDestinationFolder; let newDestinationFolder;
return this.documentListService.moveNode(contentEntryId, selectionId) return this.documentListService.moveNode(contentEntryId, selectionId).pipe(
.pipe( map(itemMoved => {
map((itemMoved) => {
return { itemMoved, initialParentId }; return { itemMoved, initialParentId };
}), }),
catchError(err => { catchError(err => {
let errStatusCode; let errStatusCode;
try { try {
const {error: {statusCode}} = JSON.parse(err.message); const {
error: { statusCode }
} = JSON.parse(err.message);
errStatusCode = statusCode; errStatusCode = statusCode;
} catch (e) { // } catch (e) {
//
} }
if (errStatusCode && errStatusCode === 409) { if (errStatusCode && errStatusCode === 409) {
$destinationFolder = this.getChildByName(
$destinationFolder = this.getChildByName(selectionId, contentEntry.name); selectionId,
contentEntry.name
);
$childrenToMove = this.getNodeChildren(contentEntryId); $childrenToMove = this.getNodeChildren(contentEntryId);
return $destinationFolder return $destinationFolder.pipe(
.pipe( mergeMap(destination => {
mergeMap((destination) => {
newDestinationFolder = destination; newDestinationFolder = destination;
return $childrenToMove; return $childrenToMove;
}), }),
mergeMap((childrenToMove) => { mergeMap(childrenToMove => {
const batch = []; const batch = [];
childrenToMove.list.entries.forEach((node) => { childrenToMove.list.entries.forEach(node => {
if (node.entry.isFolder) { if (node.entry.isFolder) {
batch.push(this.moveFolderAction(node.entry, newDestinationFolder.entry.id)); batch.push(
this.moveFolderAction(
node.entry,
newDestinationFolder.entry.id
)
);
} else { } else {
batch.push(this.moveContentAction(node.entry, newDestinationFolder.entry.id)); batch.push(
this.moveContentAction(
node.entry,
newDestinationFolder.entry.id
)
);
} }
}); });
@@ -508,13 +585,11 @@ export class NodeActionsService {
const contentEntryId = contentEntry.nodeId || contentEntry.id; const contentEntryId = contentEntry.nodeId || contentEntry.id;
const initialParentId = this.getEntryParentId(contentEntry); const initialParentId = this.getEntryParentId(contentEntry);
return this.documentListService return this.documentListService.moveNode(contentEntryId, selectionId).pipe(
.moveNode(contentEntryId, selectionId) map(itemMoved => {
.pipe(
map((itemMoved) => {
return { itemMoved, initialParentId }; return { itemMoved, initialParentId };
}), }),
catchError((err) => { catchError(err => {
// do not throw error, to be able to show message in case of partial move of files // do not throw error, to be able to show message in case of partial move of files
return of(err); return of(err);
}) })
@@ -526,22 +601,28 @@ export class NodeActionsService {
this.getNodeChildren(parentId).subscribe( this.getNodeChildren(parentId).subscribe(
(childrenNodes: any) => { (childrenNodes: any) => {
const result = childrenNodes.list.entries.find(node => (node.entry.name === name)); const result = childrenNodes.list.entries.find(
node => node.entry.name === name
);
if (result) { if (result) {
matchedNodes.next(result); matchedNodes.next(result);
} else { } else {
matchedNodes.next(null); matchedNodes.next(null);
} }
}, },
(err) => { err => {
return of(err || 'Server error'); return of(err || 'Server error');
}); }
);
return matchedNodes; return matchedNodes;
} }
private isActionAllowed(action: string, node: MinimalNodeEntryEntity, permission?: string): boolean { private isActionAllowed(
action: string,
node: MinimalNodeEntryEntity,
permission?: string
): boolean {
if (action === 'copy') { if (action === 'copy') {
return true; return true;
} }
@@ -553,11 +634,14 @@ export class NodeActionsService {
const node: MinimalNodeEntryEntity = row.node.entry; const node: MinimalNodeEntryEntity = row.node.entry;
this.isSitesDestinationAvailable = !!node['guid']; this.isSitesDestinationAvailable = !!node['guid'];
return (!node.isFile && (node.nodeType !== 'app:folderlink')); return !node.isFile && node.nodeType !== 'app:folderlink';
} }
// todo: review once 1.10-beta6 is out // todo: review once 1.10-beta6 is out
private imageResolver(row: /*ShareDataRow*/ any, col: DataColumn): string | null { private imageResolver(
row: /*ShareDataRow*/ any,
col: DataColumn
): string | null {
const entry: MinimalNodeEntryEntity = row.node.entry; const entry: MinimalNodeEntryEntity = row.node.entry;
if (!this.contentService.hasPermission(entry, 'update')) { if (!this.contentService.hasPermission(entry, 'update')) {
return this.documentListService.getMimeTypeIcon('disable/folder'); return this.documentListService.getMimeTypeIcon('disable/folder');
@@ -571,7 +655,9 @@ export class NodeActionsService {
// remove extension in case there is one // remove extension in case there is one
const fileExtension = extensionMatch ? extensionMatch[0] : ''; const fileExtension = extensionMatch ? extensionMatch[0] : '';
let extensionFree = extensionMatch ? name.slice(0, extensionMatch.index) : name; let extensionFree = extensionMatch
? name.slice(0, extensionMatch.index)
: name;
let prefixNumber = 1; let prefixNumber = 1;
let baseExtensionFree; let baseExtensionFree;
@@ -580,25 +666,22 @@ export class NodeActionsService {
const baseExtensionMatch = baseName.match(/\.[^/.]+$/); const baseExtensionMatch = baseName.match(/\.[^/.]+$/);
// remove extension in case there is one // remove extension in case there is one
baseExtensionFree = baseExtensionMatch ? baseName.slice(0, baseExtensionMatch.index) : baseName; baseExtensionFree = baseExtensionMatch
? baseName.slice(0, baseExtensionMatch.index)
: baseName;
} }
if (!baseExtensionFree || baseExtensionFree !== extensionFree) { if (!baseExtensionFree || baseExtensionFree !== extensionFree) {
// check if name already has integer appended on end: // check if name already has integer appended on end:
const oldPrefix = extensionFree.match('-[0-9]+$'); const oldPrefix = extensionFree.match('-[0-9]+$');
if (oldPrefix) { if (oldPrefix) {
// if so, try to get the number at the end // if so, try to get the number at the end
const oldPrefixNumber = parseInt(oldPrefix[0].slice(1), 10); const oldPrefixNumber = parseInt(oldPrefix[0].slice(1), 10);
if (oldPrefixNumber.toString() === oldPrefix[0].slice(1)) { if (oldPrefixNumber.toString() === oldPrefix[0].slice(1)) {
extensionFree = extensionFree.slice(0, oldPrefix.index); extensionFree = extensionFree.slice(0, oldPrefix.index);
prefixNumber = oldPrefixNumber + 1; prefixNumber = oldPrefixNumber + 1;
} }
} }
} }
return extensionFree + '-' + prefixNumber + fileExtension; return extensionFree + '-' + prefixNumber + fileExtension;
} }
@@ -610,7 +693,9 @@ export class NodeActionsService {
* @param params optional parameters * @param params optional parameters
*/ */
getNodeChildren(nodeId: string, params?) { getNodeChildren(nodeId: string, params?) {
return from(this.apiService.getInstance().nodes.getNodeChildren(nodeId, params)); return from(
this.apiService.getInstance().nodes.getNodeChildren(nodeId, params)
);
} }
// Copied from ADF document-list.service, and added the name parameter // Copied from ADF document-list.service, and added the name parameter
@@ -622,7 +707,11 @@ export class NodeActionsService {
* @param name The new name for the copy that would be added on the destination folder * @param name The new name for the copy that would be added on the destination folder
*/ */
copyNode(nodeId: string, targetParentId: string, name?: string) { copyNode(nodeId: string, targetParentId: string, name?: string) {
return from(this.apiService.getInstance().nodes.copyNode(nodeId, {targetParentId, name})); return from(
this.apiService
.getInstance()
.nodes.copyNode(nodeId, { targetParentId, name })
);
} }
public flatten(nDimArray) { public flatten(nDimArray) {
@@ -634,8 +723,7 @@ export class NodeActionsService {
const resultingArray = []; const resultingArray = [];
do { do {
nodeQueue.forEach( nodeQueue.forEach(node => {
(node) => {
if (Array.isArray(node)) { if (Array.isArray(node)) {
nodeQueue.push(...node); nodeQueue.push(...node);
} else { } else {
@@ -644,8 +732,7 @@ export class NodeActionsService {
const nodeIndex = nodeQueue.indexOf(node); const nodeIndex = nodeQueue.indexOf(node);
nodeQueue.splice(nodeIndex, 1); nodeQueue.splice(nodeIndex, 1);
} });
);
} while (nodeQueue.length); } while (nodeQueue.length);
return resultingArray; return resultingArray;
@@ -659,43 +746,38 @@ export class NodeActionsService {
}; };
if (Array.isArray(data)) { if (Array.isArray(data)) {
return data.reduce( return data.reduce((acc, next) => {
(acc, next) => {
if (next instanceof Error) { if (next instanceof Error) {
acc.failed.push(next); acc.failed.push(next);
} else if (Array.isArray(next)) { } else if (Array.isArray(next)) {
// if content of a folder was moved // if content of a folder was moved
const folderMoveResponseData = this.flatten(next); const folderMoveResponseData = this.flatten(next);
const foundError = folderMoveResponseData.find(node => node instanceof Error); const foundError = folderMoveResponseData.find(
node => node instanceof Error
);
// data might contain also items of form: { itemMoved, initialParentId } // data might contain also items of form: { itemMoved, initialParentId }
const foundEntry = folderMoveResponseData.find( const foundEntry = folderMoveResponseData.find(
node => (node.itemMoved && node.itemMoved.entry) || (node && node.entry) node =>
(node.itemMoved && node.itemMoved.entry) || (node && node.entry)
); );
if (!foundError) { if (!foundError) {
// consider success if NONE of the items from the folder move response is an error // consider success if NONE of the items from the folder move response is an error
acc.succeeded.push(next); acc.succeeded.push(next);
} else if (!foundEntry) { } else if (!foundEntry) {
// consider failed if NONE of the items has an entry // consider failed if NONE of the items has an entry
acc.failed.push(next); acc.failed.push(next);
} else { } else {
// partially move folder // partially move folder
acc.partiallySucceeded.push(next); acc.partiallySucceeded.push(next);
} }
} else { } else {
acc.succeeded.push(next); acc.succeeded.push(next);
} }
return acc; return acc;
}, }, moveStatus);
moveStatus
);
} else { } else {
if ((data.itemMoved && data.itemMoved.entry) || (data && data.entry)) { if ((data.itemMoved && data.itemMoved.entry) || (data && data.entry)) {
moveStatus.succeeded.push(data); moveStatus.succeeded.push(data);

View File

@@ -32,7 +32,6 @@ describe('NodePermissionService', () => {
permission = new NodePermissionService(); permission = new NodePermissionService();
}); });
it('should return false when source is null', () => { it('should return false when source is null', () => {
const source = null; const source = null;
@@ -57,7 +56,11 @@ describe('NodePermissionService', () => {
{ entry: { allowableOperationsOnTarget: ['update'] } } { entry: { allowableOperationsOnTarget: ['update'] } }
]; ];
expect(permission.check(source, ['update'], { target: 'allowableOperationsOnTarget' })).toBe(true); expect(
permission.check(source, ['update'], {
target: 'allowableOperationsOnTarget'
})
).toBe(true);
}); });
it('should return false when source does not have allowableOperations permission', () => { it('should return false when source does not have allowableOperations permission', () => {
@@ -67,7 +70,11 @@ describe('NodePermissionService', () => {
{ entry: { allowableOperations: ['delete'] } } { entry: { allowableOperations: ['delete'] } }
]; ];
expect(permission.check(source, ['update'], { target: 'allowableOperationsOnTarget' })).toBe(false); expect(
permission.check(source, ['update'], {
target: 'allowableOperationsOnTarget'
})
).toBe(false);
}); });
it('should return false when source does not have allowableOperationsOnTarget permission', () => { it('should return false when source does not have allowableOperationsOnTarget permission', () => {
@@ -77,7 +84,11 @@ describe('NodePermissionService', () => {
{ entry: { allowableOperationsOnTarget: ['delete'] } } { entry: { allowableOperationsOnTarget: ['delete'] } }
]; ];
expect(permission.check(source, ['update'], { target: 'allowableOperationsOnTarget' })).toBe(false); expect(
permission.check(source, ['update'], {
target: 'allowableOperationsOnTarget'
})
).toBe(false);
}); });
it('should return true when source has `OR` allowableOperations permission', () => { it('should return true when source has `OR` allowableOperations permission', () => {
@@ -94,20 +105,32 @@ describe('NodePermissionService', () => {
const source = [ const source = [
{ entry: { allowableOperations: ['update', 'delete', 'other'] } }, { entry: { allowableOperations: ['update', 'delete', 'other'] } },
{ entry: { allowableOperations: ['update', 'create', 'other'] } }, { entry: { allowableOperations: ['update', 'create', 'other'] } },
{ entry: { allowableOperations: ['update', 'updatePermissions', 'other'] } } {
entry: {
allowableOperations: ['update', 'updatePermissions', 'other']
}
}
]; ];
expect(permission.check(source, ['update', 'other'], { operation: 'AND' })).toBe(true); expect(
permission.check(source, ['update', 'other'], { operation: 'AND' })
).toBe(true);
}); });
it('should return false when source has no `AND` allowableOperations permission', () => { it('should return false when source has no `AND` allowableOperations permission', () => {
const source = [ const source = [
{ entry: { allowableOperations: ['update', 'delete', 'other'] } }, { entry: { allowableOperations: ['update', 'delete', 'other'] } },
{ entry: { allowableOperations: ['update', 'create', 'other'] } }, { entry: { allowableOperations: ['update', 'create', 'other'] } },
{ entry: { allowableOperations: ['update', 'updatePermissions', 'other'] } } {
entry: {
allowableOperations: ['update', 'updatePermissions', 'other']
}
}
]; ];
expect(permission.check(source, ['update', 'bogus'], { operation: 'AND' })).toBe(false); expect(
permission.check(source, ['update', 'bogus'], { operation: 'AND' })
).toBe(false);
}); });
it('should return false when source has no allowableOperations', () => { it('should return false when source has no allowableOperations', () => {
@@ -131,7 +154,6 @@ describe('NodePermissionService', () => {
}); });
}); });
describe('Single source permission', () => { describe('Single source permission', () => {
it('should return true when source has allowableOperations permission', () => { it('should return true when source has allowableOperations permission', () => {
const source = { entry: { allowableOperations: ['update'] } }; const source = { entry: { allowableOperations: ['update'] } };
@@ -142,7 +164,11 @@ describe('NodePermissionService', () => {
it('should return true when source has allowableOperationsOnTarget permission', () => { it('should return true when source has allowableOperationsOnTarget permission', () => {
const source = { entry: { allowableOperationsOnTarget: ['update'] } }; const source = { entry: { allowableOperationsOnTarget: ['update'] } };
expect(permission.check(source, ['update'], { target: 'allowableOperationsOnTarget' })).toBe(true); expect(
permission.check(source, ['update'], {
target: 'allowableOperationsOnTarget'
})
).toBe(true);
}); });
it('should return false when source does not have allowableOperations permission', () => { it('should return false when source does not have allowableOperations permission', () => {
@@ -154,7 +180,11 @@ describe('NodePermissionService', () => {
it('should return false when source does not have allowableOperationsOnTarget permission', () => { it('should return false when source does not have allowableOperationsOnTarget permission', () => {
const source = { entry: { allowableOperationsOnTarget: ['delete'] } }; const source = { entry: { allowableOperationsOnTarget: ['delete'] } };
expect(permission.check(source, ['update'], { target: 'allowableOperationsOnTarget' })).toBe(false); expect(
permission.check(source, ['update'], {
target: 'allowableOperationsOnTarget'
})
).toBe(false);
}); });
it('should return true when source has `OR` allowableOperations permission', () => { it('should return true when source has `OR` allowableOperations permission', () => {
@@ -166,13 +196,19 @@ describe('NodePermissionService', () => {
it('should return true when source has `AND` allowableOperations permission', () => { it('should return true when source has `AND` allowableOperations permission', () => {
const source = { entry: { allowableOperations: ['update', 'other'] } }; const source = { entry: { allowableOperations: ['update', 'other'] } };
expect(permission.check(source, ['update', 'other'], { operation: 'AND' })).toBe(true); expect(
permission.check(source, ['update', 'other'], { operation: 'AND' })
).toBe(true);
}); });
it('should return false when source has no `AND` allowableOperations permission', () => { it('should return false when source has no `AND` allowableOperations permission', () => {
const source = { entry: { allowableOperations: ['update', 'updatePermissions', 'other'] } }; const source = {
entry: { allowableOperations: ['update', 'updatePermissions', 'other'] }
};
expect(permission.check(source, ['update', 'bogus'], { operation: 'AND' })).toBe(false); expect(
permission.check(source, ['update', 'bogus'], { operation: 'AND' })
).toBe(false);
}); });
it('should return false when source has no allowableOperations', () => { it('should return false when source has no allowableOperations', () => {

View File

@@ -42,7 +42,10 @@ export class NodePermissionService implements NodePermissions {
if (Array.isArray(source) && source.length) { if (Array.isArray(source) && source.length) {
const arr = this.sanitize(source); const arr = this.sanitize(source);
return !!arr.length && source.every(node => this.hasPermission(node, permissions, opts)); return (
!!arr.length &&
source.every(node => this.hasPermission(node, permissions, opts))
);
} }
return this.hasPermission(source, permissions, opts); return this.hasPermission(source, permissions, opts);
@@ -52,13 +55,20 @@ export class NodePermissionService implements NodePermissions {
} }
private hasPermission(node, permissions, options): boolean { private hasPermission(node, permissions, options): boolean {
const allowableOperations = this.getAllowableOperations(node, options.target); const allowableOperations = this.getAllowableOperations(
node,
options.target
);
if (allowableOperations.length) { if (allowableOperations.length) {
if (options.operation === NodePermissionService.DEFAULT_OPERATION) { if (options.operation === NodePermissionService.DEFAULT_OPERATION) {
return permissions.some(permission => allowableOperations.includes(permission)); return permissions.some(permission =>
allowableOperations.includes(permission)
);
} else { } else {
return permissions.every(permission => allowableOperations.includes(permission)); return permissions.every(permission =>
allowableOperations.includes(permission)
);
} }
} }

View File

@@ -45,10 +45,7 @@ import {
@NgModule({ @NgModule({
imports: [ imports: [
StoreModule.forRoot( StoreModule.forRoot({ app: appReducer }, { initialState: INITIAL_STATE }),
{ app: appReducer },
{ initialState: INITIAL_STATE }
),
StoreRouterConnectingModule.forRoot({ stateKey: 'router' }), StoreRouterConnectingModule.forRoot({ stateKey: 'router' }),
EffectsModule.forRoot([ EffectsModule.forRoot([
SnackbarEffects, SnackbarEffects,

View File

@@ -99,10 +99,7 @@ export class DownloadEffects {
private downloadFile(node: NodeInfo) { private downloadFile(node: NodeInfo) {
if (node) { if (node) {
this.download( this.download(this.contentApi.getContentUrl(node.id, true), node.name);
this.contentApi.getContentUrl(node.id, true),
node.name
);
} }
} }

View File

@@ -26,7 +26,12 @@
import { Effect, Actions, ofType } from '@ngrx/effects'; import { Effect, Actions, ofType } from '@ngrx/effects';
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { map, take } from 'rxjs/operators'; import { map, take } from 'rxjs/operators';
import { ADD_FAVORITE, AddFavoriteAction, RemoveFavoriteAction, REMOVE_FAVORITE } from '../actions/favorite.actions'; import {
ADD_FAVORITE,
AddFavoriteAction,
RemoveFavoriteAction,
REMOVE_FAVORITE
} from '../actions/favorite.actions';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
import { AppStore } from '../states'; import { AppStore } from '../states';
import { appSelection } from '../selectors/app.selectors'; import { appSelection } from '../selectors/app.selectors';
@@ -77,6 +82,4 @@ export class FavoriteEffects {
} }
}) })
); );
} }

View File

@@ -27,8 +27,10 @@ import { Effect, Actions, ofType } from '@ngrx/effects';
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { map, take } from 'rxjs/operators'; import { map, take } from 'rxjs/operators';
import { import {
DeleteLibraryAction, DELETE_LIBRARY, DeleteLibraryAction,
CreateLibraryAction, CREATE_LIBRARY DELETE_LIBRARY,
CreateLibraryAction,
CREATE_LIBRARY
} from '../actions'; } from '../actions';
import { ContentManagementService } from '../../services/content-management.service'; import { ContentManagementService } from '../../services/content-management.service';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';

View File

@@ -117,9 +117,7 @@ export class NodeEffects {
.pipe(take(1)) .pipe(take(1))
.subscribe(selection => { .subscribe(selection => {
if (selection && selection.count > 0) { if (selection && selection.count > 0) {
this.contentService.purgeDeletedNodes( this.contentService.purgeDeletedNodes(selection.nodes);
selection.nodes
);
} }
}); });
} }
@@ -138,9 +136,7 @@ export class NodeEffects {
.pipe(take(1)) .pipe(take(1))
.subscribe(selection => { .subscribe(selection => {
if (selection && selection.count > 0) { if (selection && selection.count > 0) {
this.contentService.restoreDeletedNodes( this.contentService.restoreDeletedNodes(selection.nodes);
selection.nodes
);
} }
}); });
} }
@@ -264,9 +260,7 @@ export class NodeEffects {
.pipe(take(1)) .pipe(take(1))
.subscribe(selection => { .subscribe(selection => {
if (selection && !selection.isEmpty) { if (selection && !selection.isEmpty) {
this.contentService.managePermissions( this.contentService.managePermissions(selection.first);
selection.first
);
} }
}); });
} }
@@ -285,9 +279,7 @@ export class NodeEffects {
.pipe(take(1)) .pipe(take(1))
.subscribe(selection => { .subscribe(selection => {
if (selection && selection.file) { if (selection && selection.file) {
this.contentService.manageVersions( this.contentService.manageVersions(selection.file);
selection.file
);
} }
}); });
} }

View File

@@ -55,7 +55,6 @@ export class UploadEffects {
this.fileInput.addEventListener('change', event => this.upload(event)); this.fileInput.addEventListener('change', event => this.upload(event));
renderer.appendChild(document.body, this.fileInput); renderer.appendChild(document.body, this.fileInput);
this.folderInput = renderer.createElement('input') as HTMLInputElement; this.folderInput = renderer.createElement('input') as HTMLInputElement;
this.folderInput.id = 'app-upload-folder'; this.folderInput.id = 'app-upload-folder';
this.folderInput.type = 'file'; this.folderInput.type = 'file';
@@ -89,15 +88,13 @@ export class UploadEffects {
.subscribe(node => { .subscribe(node => {
if (node && node.id) { if (node && node.id) {
const input = <HTMLInputElement>event.currentTarget; const input = <HTMLInputElement>event.currentTarget;
const files = FileUtils.toFileArray(input.files).map( const files = FileUtils.toFileArray(input.files).map(file => {
file => {
return new FileModel(file, { return new FileModel(file, {
parentId: node.id, parentId: node.id,
path: (file.webkitRelativePath || '').replace(/\/[^\/]*$/, ''), path: (file.webkitRelativePath || '').replace(/\/[^\/]*$/, ''),
nodeType: 'cm:content' nodeType: 'cm:content'
}); });
} });
);
this.uploadQueue(files); this.uploadQueue(files);
event.target.value = ''; event.target.value = '';

View File

@@ -67,16 +67,10 @@ export class ViewerEffects {
.pipe(take(1)) .pipe(take(1))
.subscribe(result => { .subscribe(result => {
if (result.selection && result.selection.file) { if (result.selection && result.selection.file) {
const { const { id, nodeId, isFile } = result.selection.file.entry;
id,
nodeId,
isFile
} = result.selection.file.entry;
if (isFile || nodeId) { if (isFile || nodeId) {
const parentId = result.folder const parentId = result.folder ? result.folder.id : null;
? result.folder.id
: null;
this.displayPreview(nodeId || id, parentId); this.displayPreview(nodeId || id, parentId);
} }
} }

View File

@@ -57,22 +57,16 @@ export function appReducer(
newState = Object.assign({}, (<SetInitialStateAction>action).payload); newState = Object.assign({}, (<SetInitialStateAction>action).payload);
break; break;
case SET_SELECTED_NODES: case SET_SELECTED_NODES:
newState = updateSelectedNodes(state, <SetSelectedNodesAction>( newState = updateSelectedNodes(state, <SetSelectedNodesAction>action);
action
));
break; break;
case SET_USER_PROFILE: case SET_USER_PROFILE:
newState = updateUser(state, <SetUserProfileAction>action); newState = updateUser(state, <SetUserProfileAction>action);
break; break;
case SET_LANGUAGE_PICKER: case SET_LANGUAGE_PICKER:
newState = updateLanguagePicker(state, <SetLanguagePickerAction>( newState = updateLanguagePicker(state, <SetLanguagePickerAction>action);
action
));
break; break;
case SET_CURRENT_FOLDER: case SET_CURRENT_FOLDER:
newState = updateCurrentFolder(state, <SetCurrentFolderAction>( newState = updateCurrentFolder(state, <SetCurrentFolderAction>action);
action
));
break; break;
case SET_CURRENT_URL: case SET_CURRENT_URL:
newState = updateCurrentUrl(state, <SetCurrentUrlAction>action); newState = updateCurrentUrl(state, <SetCurrentUrlAction>action);
@@ -81,9 +75,9 @@ export function appReducer(
newState = updateInfoDrawer(state, <ToggleInfoDrawerAction>action); newState = updateInfoDrawer(state, <ToggleInfoDrawerAction>action);
break; break;
case TOGGLE_DOCUMENT_DISPLAY_MODE: case TOGGLE_DOCUMENT_DISPLAY_MODE:
newState = updateDocumentDisplayMode(state, < newState = updateDocumentDisplayMode(state, <ToggleDocumentDisplayMode>(
ToggleDocumentDisplayMode action
>action); ));
break; break;
default: default:
newState = Object.assign({}, state); newState = Object.assign({}, state);
@@ -185,9 +179,7 @@ function updateSelectedNodes(
if (nodes.length === 1) { if (nodes.length === 1) {
file = nodes.find(entity => { file = nodes.find(entity => {
// workaround Shared // workaround Shared
return entity.entry.isFile || entity.entry.nodeId return entity.entry.isFile || entity.entry.nodeId ? true : false;
? true
: false;
}); });
folder = nodes.find(entity => entity.entry.isFolder); folder = nodes.find(entity => entity.entry.isFolder);
} }

View File

@@ -27,17 +27,38 @@ import { createSelector } from '@ngrx/store';
import { AppStore } from '../states/app.state'; import { AppStore } from '../states/app.state';
export const selectApp = (state: AppStore) => state.app; export const selectApp = (state: AppStore) => state.app;
export const selectHeaderColor = createSelector(selectApp, state => state.headerColor); export const selectHeaderColor = createSelector(
selectApp,
state => state.headerColor
);
export const selectAppName = createSelector(selectApp, state => state.appName); export const selectAppName = createSelector(selectApp, state => state.appName);
export const selectLogoPath = createSelector(selectApp, state => state.logoPath); export const selectLogoPath = createSelector(
selectApp,
state => state.logoPath
);
export const appSelection = createSelector(selectApp, state => state.selection); export const appSelection = createSelector(selectApp, state => state.selection);
export const appLanguagePicker = createSelector(selectApp, state => state.languagePicker); export const appLanguagePicker = createSelector(
selectApp,
state => state.languagePicker
);
export const selectUser = createSelector(selectApp, state => state.user); export const selectUser = createSelector(selectApp, state => state.user);
export const sharedUrl = createSelector(selectApp, state => state.sharedUrl); export const sharedUrl = createSelector(selectApp, state => state.sharedUrl);
export const appNavigation = createSelector(selectApp, state => state.navigation); export const appNavigation = createSelector(
export const currentFolder = createSelector(selectApp, state => state.navigation.currentFolder); selectApp,
export const infoDrawerOpened = createSelector(selectApp, state => state.infoDrawerOpened); state => state.navigation
export const documentDisplayMode = createSelector(selectApp, state => state.documentDisplayMode); );
export const currentFolder = createSelector(
selectApp,
state => state.navigation.currentFolder
);
export const infoDrawerOpened = createSelector(
selectApp,
state => state.infoDrawerOpened
);
export const documentDisplayMode = createSelector(
selectApp,
state => state.documentDisplayMode
);
export const ruleContext = createSelector( export const ruleContext = createSelector(
appSelection, appSelection,

View File

@@ -23,7 +23,11 @@
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>. * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/ */
import { SelectionState, ProfileState, NavigationState } from '@alfresco/adf-extensions'; import {
SelectionState,
ProfileState,
NavigationState
} from '@alfresco/adf-extensions';
export interface AppState { export interface AppState {
appName: string; appName: string;

View File

@@ -61,7 +61,10 @@ import { NodePermissionService } from '../services/node-permission.service';
import { ContentApiService } from '../services/content-api.service'; import { ContentApiService } from '../services/content-api.service';
import { AppExtensionService } from '../extensions/extension.service'; import { AppExtensionService } from '../extensions/extension.service';
import { ViewUtilService } from '../components/preview/view-util.service'; import { ViewUtilService } from '../components/preview/view-util.service';
import { ExtensionLoaderService, ExtensionService } from '@alfresco/adf-extensions'; import {
ExtensionLoaderService,
ExtensionService
} from '@alfresco/adf-extensions';
@NgModule({ @NgModule({
imports: [ imports: [
@@ -69,18 +72,11 @@ import { ExtensionLoaderService, ExtensionService } from '@alfresco/adf-extensio
HttpClientModule, HttpClientModule,
RouterTestingModule, RouterTestingModule,
MaterialModule, MaterialModule,
StoreModule.forRoot( StoreModule.forRoot({ app: appReducer }, { initialState: INITIAL_STATE }),
{ app: appReducer },
{ initialState: INITIAL_STATE }
),
EffectsModule.forRoot([]) EffectsModule.forRoot([])
], ],
declarations: [TranslatePipeMock], declarations: [TranslatePipeMock],
exports: [ exports: [TranslatePipeMock, RouterTestingModule, MaterialModule],
TranslatePipeMock,
RouterTestingModule,
MaterialModule,
],
providers: [ providers: [
{ provide: AlfrescoApiService, useClass: AlfrescoApiMock }, { provide: AlfrescoApiService, useClass: AlfrescoApiMock },
{ provide: TranslationService, useClass: TranslationMock }, { provide: TranslationService, useClass: TranslationMock },

View File

@@ -33,11 +33,17 @@ export class TranslateServiceMock extends TranslateService {
super(null, null, null, null, null); super(null, null, null, null, null);
} }
get(key: string | Array<string>, interpolateParams?: Object): Observable<string | any> { get(
key: string | Array<string>,
interpolateParams?: Object
): Observable<string | any> {
return of(key); return of(key);
} }
instant(key: string | Array<string>, interpolateParams?: Object): string | any { instant(
key: string | Array<string>,
interpolateParams?: Object
): string | any {
return key; return key;
} }
} }

Some files were not shown because too many files have changed in this diff Show More