mirror of
https://github.com/Alfresco/alfresco-content-app.git
synced 2025-05-12 17:04:46 +00:00
[ACA-1631] more application ngrx actions (#540)
* delete action * library path evaluator * extension for sharing files * upload actions * delete library * use extensions for experimental library actions * unshare nodes * fix icons and titles * "create menu" backed by core extension * support for descriptions, update upload selector * update code and tests * support disabled tooltips for navbar * fix selector * [ACA-1486] remove double fetch call * migrate to trashcan actions, element IDs * cleanup code, remove deprecated directives * add/remove favorite * improve rendering performance * update favorites without reload * support for adding Sites to favorites * disable favorites for Libraries for now * copy action * move node * manage versions and permissions * cleanup code * toggle info drawer * card view mode * use extension layer for favorites toolbar * fix menu tooltips * fix 'remove as favorite' tests * update tests * test fixes * fix edit folder for favorites * fix test * cleanup favorites layout * upgrade recent files layout * update evaluators for shared nodes * test fixes * test fixes * restore recent files layout * workaround for "favorite" toggle and recent files * upgrade shared files page * upgrade files page layout * fix library evaluator * workaround for shared files and permissions * cleanup code * upgrade search results * upgrade sidebar and viewer actions * code cleanup * code cleanup * code cleanup
This commit is contained in:
parent
617f80c9fd
commit
ae8675dfd7
@ -32,12 +32,12 @@ export class Menu extends Component {
|
|||||||
root: '.mat-menu-panel',
|
root: '.mat-menu-panel',
|
||||||
item: '.mat-menu-item',
|
item: '.mat-menu-item',
|
||||||
icon: '.mat-icon',
|
icon: '.mat-icon',
|
||||||
uploadFiles: 'input[id="upload-multiple-files"]'
|
uploadFiles: 'app-upload-files'
|
||||||
};
|
};
|
||||||
|
|
||||||
items: ElementArrayFinder = this.component.all(by.css(Menu.selectors.item));
|
items: ElementArrayFinder = this.component.all(by.css(Menu.selectors.item));
|
||||||
backdrop: ElementFinder = browser.element(by.css('.cdk-overlay-backdrop'));
|
backdrop: ElementFinder = browser.element(by.css('.cdk-overlay-backdrop'));
|
||||||
uploadFiles: ElementFinder = this.component.element(by.css(Menu.selectors.uploadFiles));
|
uploadFiles: ElementFinder = browser.element(by.id(Menu.selectors.uploadFiles));
|
||||||
|
|
||||||
constructor(ancestor?: ElementFinder) {
|
constructor(ancestor?: ElementFinder) {
|
||||||
super(Menu.selectors.root, ancestor);
|
super(Menu.selectors.root, ancestor);
|
||||||
|
@ -155,7 +155,7 @@ describe('Create folder', () => {
|
|||||||
.then(() => menu))
|
.then(() => menu))
|
||||||
.then(menu => {
|
.then(menu => {
|
||||||
const tooltip = menu.getItemTooltip('Create folder');
|
const tooltip = menu.getItemTooltip('Create folder');
|
||||||
expect(tooltip).toContain(`You can't create a folder here`);
|
expect(tooltip).toContain(`Folders cannot be created whilst viewing the current items.`);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -115,6 +115,14 @@
|
|||||||
"description": "Element title",
|
"description": "Element title",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"description": {
|
||||||
|
"description": "Element description, used for the tooltips.",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"description-disabled": {
|
||||||
|
"description": "Description to use when element is in the disabled state.",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"order": {
|
"order": {
|
||||||
"description": "Element order",
|
"description": "Element order",
|
||||||
"type": "number"
|
"type": "number"
|
||||||
@ -283,6 +291,12 @@
|
|||||||
"type": "array",
|
"type": "array",
|
||||||
"items": { "$ref": "#/definitions/contentActionRef" },
|
"items": { "$ref": "#/definitions/contentActionRef" },
|
||||||
"minItems": 1
|
"minItems": 1
|
||||||
|
},
|
||||||
|
"actions": {
|
||||||
|
"description": "Content actions (toolbar, context menus, etc.)",
|
||||||
|
"type": "array",
|
||||||
|
"items": { "$ref": "#/definitions/contentActionRef" },
|
||||||
|
"minItems": 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -9,7 +9,6 @@
|
|||||||
"© 2017 - 2018 Alfresco Software, Inc. All rights reserved."
|
"© 2017 - 2018 Alfresco Software, Inc. All rights reserved."
|
||||||
},
|
},
|
||||||
"experimental": {
|
"experimental": {
|
||||||
"libraries": false,
|
|
||||||
"comments": false,
|
"comments": false,
|
||||||
"cardview": false,
|
"cardview": false,
|
||||||
"permissions": false,
|
"permissions": false,
|
||||||
|
@ -73,6 +73,9 @@ import { ViewUtilService} from './services/view-util.service';
|
|||||||
import { ExtensionService } from './extensions/extension.service';
|
import { ExtensionService } from './extensions/extension.service';
|
||||||
import { AppInfoDrawerModule } from './components/info-drawer/info.drawer.module';
|
import { AppInfoDrawerModule } from './components/info-drawer/info.drawer.module';
|
||||||
import { DirectivesModule } from './directives/directives.module';
|
import { DirectivesModule } from './directives/directives.module';
|
||||||
|
import { ToggleInfoDrawerComponent } from './components/toolbar/toggle-info-drawer/toggle-info-drawer.component';
|
||||||
|
import { DocumentDisplayModeComponent } from './components/toolbar/document-display-mode/document-display-mode.component';
|
||||||
|
import { ToggleFavoriteComponent } from './components/toolbar/toggle-favorite/toggle-favorite.component';
|
||||||
|
|
||||||
export function setupExtensionServiceFactory(service: ExtensionService): Function {
|
export function setupExtensionServiceFactory(service: ExtensionService): Function {
|
||||||
return () => service.load();
|
return () => service.load();
|
||||||
@ -121,7 +124,10 @@ export function setupExtensionServiceFactory(service: ExtensionService): Functio
|
|||||||
PermissionsManagerComponent,
|
PermissionsManagerComponent,
|
||||||
SearchResultsComponent,
|
SearchResultsComponent,
|
||||||
SettingsComponent,
|
SettingsComponent,
|
||||||
SharedLinkViewComponent
|
SharedLinkViewComponent,
|
||||||
|
ToggleInfoDrawerComponent,
|
||||||
|
DocumentDisplayModeComponent,
|
||||||
|
ToggleFavoriteComponent
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
{ provide: RouteReuseStrategy, useClass: AppRouteReuseStrategy },
|
{ provide: RouteReuseStrategy, useClass: AppRouteReuseStrategy },
|
||||||
@ -151,7 +157,10 @@ export function setupExtensionServiceFactory(service: ExtensionService): Functio
|
|||||||
entryComponents: [
|
entryComponents: [
|
||||||
LibraryDialogComponent,
|
LibraryDialogComponent,
|
||||||
NodeVersionsDialogComponent,
|
NodeVersionsDialogComponent,
|
||||||
NodePermissionsDialogComponent
|
NodePermissionsDialogComponent,
|
||||||
|
ToggleInfoDrawerComponent,
|
||||||
|
DocumentDisplayModeComponent,
|
||||||
|
ToggleFavoriteComponent
|
||||||
],
|
],
|
||||||
bootstrap: [AppComponent]
|
bootstrap: [AppComponent]
|
||||||
})
|
})
|
||||||
|
@ -4,129 +4,17 @@
|
|||||||
</adf-breadcrumb>
|
</adf-breadcrumb>
|
||||||
|
|
||||||
<adf-toolbar class="inline">
|
<adf-toolbar class="inline">
|
||||||
<button *ifExperimental="'cardview'"
|
<app-document-display-mode *ifExperimental="'cardview'"></app-document-display-mode>
|
||||||
color="primary"
|
|
||||||
mat-icon-button
|
|
||||||
(click)="toggleGalleryView()">
|
|
||||||
<mat-icon *ngIf="displayMode === 'list'" matTooltip="{{ 'APP.DOCUMENT_LIST.TOOLBAR.CARDVIEW' | translate }}">view_comfy</mat-icon>
|
|
||||||
<mat-icon *ngIf="displayMode === 'gallery'" matTooltip="{{ 'APP.DOCUMENT_LIST.TOOLBAR.LISTVIEW' | translate }}">list</mat-icon>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<ng-container *ifExperimental="'extensions'">
|
<ng-container *ngFor="let entry of actions; trackBy: trackByActionId">
|
||||||
<ng-container *ngFor="let entry of actions">
|
|
||||||
<aca-toolbar-action [entry]="entry"></aca-toolbar-action>
|
<aca-toolbar-action [entry]="entry"></aca-toolbar-action>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</ng-container>
|
|
||||||
|
|
||||||
<ng-container *ngIf="!selection.isEmpty">
|
|
||||||
<button
|
|
||||||
mat-icon-button
|
|
||||||
color="primary"
|
|
||||||
*ngIf="selection.file"
|
|
||||||
title="{{ 'APP.ACTIONS.VIEW' | translate }}"
|
|
||||||
(click)="showPreview(selection.file)">
|
|
||||||
<mat-icon>open_in_browser</mat-icon>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button
|
|
||||||
mat-icon-button
|
|
||||||
color="primary"
|
|
||||||
title="{{ 'APP.ACTIONS.DOWNLOAD' | translate }}"
|
|
||||||
(click)="downloadSelection()">
|
|
||||||
<mat-icon>get_app</mat-icon>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button
|
|
||||||
mat-icon-button
|
|
||||||
color="primary"
|
|
||||||
*ngIf="selection.folder"
|
|
||||||
title="{{ 'APP.ACTIONS.EDIT' | translate }}"
|
|
||||||
[acaEditFolder]="selection.folder">
|
|
||||||
<mat-icon>create</mat-icon>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button mat-icon-button
|
|
||||||
[color]="infoDrawerOpened ? 'accent' : 'primary'"
|
|
||||||
title="{{ 'APP.ACTIONS.DETAILS' | translate }}"
|
|
||||||
(click)="toggleSidebar()">
|
|
||||||
<mat-icon>info_outline</mat-icon>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<ng-container *ifExperimental="'share'">
|
|
||||||
<button mat-icon-button
|
|
||||||
color="primary"
|
|
||||||
title="{{ 'APP.ACTIONS.SHARE' | translate }}"
|
|
||||||
*ngIf="selection.file"
|
|
||||||
[baseShareUrl]="sharedPreviewUrl$ | async"
|
|
||||||
[adf-share]="selection.file">
|
|
||||||
<mat-icon>share</mat-icon>
|
|
||||||
</button>
|
|
||||||
</ng-container>
|
|
||||||
|
|
||||||
<button
|
|
||||||
color="primary"
|
|
||||||
mat-icon-button
|
|
||||||
title="{{ 'APP.ACTIONS.MORE' | translate }}"
|
|
||||||
[matMenuTriggerFor]="actionsMenu">
|
|
||||||
<mat-icon>more_vert</mat-icon>
|
|
||||||
</button>
|
|
||||||
<mat-menu #actionsMenu="matMenu"
|
|
||||||
[overlapTrigger]="false">
|
|
||||||
<button
|
|
||||||
mat-menu-item
|
|
||||||
#favorites="adfFavorite"
|
|
||||||
(toggle)="reload()"
|
|
||||||
[adf-node-favorite]="selection.nodes">
|
|
||||||
<mat-icon color="primary" *ngIf="favorites.hasFavorites()">star</mat-icon>
|
|
||||||
<mat-icon *ngIf="!favorites.hasFavorites()">star_border</mat-icon>
|
|
||||||
<span>{{ 'APP.ACTIONS.FAVORITE' | translate }}</span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button
|
|
||||||
mat-menu-item
|
|
||||||
[acaCopyNode]="selection.nodes">
|
|
||||||
<mat-icon>content_copy</mat-icon>
|
|
||||||
<span>{{ 'APP.ACTIONS.COPY' | translate }}</span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button
|
|
||||||
mat-menu-item
|
|
||||||
[acaMoveNode]="selection.nodes">
|
|
||||||
<mat-icon>library_books</mat-icon>
|
|
||||||
<span>{{ 'APP.ACTIONS.MOVE' | translate }}</span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button
|
|
||||||
mat-menu-item
|
|
||||||
[acaDeleteNode]="selection.nodes">
|
|
||||||
<mat-icon>delete</mat-icon>
|
|
||||||
<span>{{ 'APP.ACTIONS.DELETE' | translate }}</span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button
|
|
||||||
mat-menu-item
|
|
||||||
*ngIf="selection.file"
|
|
||||||
[acaNodeVersions]="selection.file">
|
|
||||||
<mat-icon>history</mat-icon>
|
|
||||||
<span>{{ 'APP.ACTIONS.VERSIONS' | translate }}</span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<ng-container *ifExperimental="'permissions'">
|
|
||||||
<button
|
|
||||||
mat-menu-item
|
|
||||||
*ngIf="selection.count === 1"
|
|
||||||
[acaNodePermissions]="selection.first">
|
|
||||||
<mat-icon>settings_input_component</mat-icon>
|
|
||||||
<span>{{ 'APP.ACTIONS.PERMISSIONS' | translate }}</span>
|
|
||||||
</button>
|
|
||||||
</ng-container>
|
|
||||||
</mat-menu>
|
|
||||||
</ng-container>
|
|
||||||
</adf-toolbar>
|
</adf-toolbar>
|
||||||
</div>
|
</div>
|
||||||
<div class="inner-layout__content">
|
<div class="inner-layout__content">
|
||||||
<div class="inner-layout__panel">
|
<div class="inner-layout__panel">
|
||||||
<adf-document-list acaDocumentList #documentList
|
<adf-document-list acaDocumentList #documentList
|
||||||
|
[display]="documentDisplayMode$ | async"
|
||||||
currentFolderId="-favorites-"
|
currentFolderId="-favorites-"
|
||||||
selectionMode="multiple"
|
selectionMode="multiple"
|
||||||
[navigate]="false"
|
[navigate]="false"
|
||||||
@ -200,7 +88,7 @@
|
|||||||
</adf-pagination>
|
</adf-pagination>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="inner-layout__side-panel" *ngIf="infoDrawerOpened">
|
<div class="inner-layout__side-panel" *ngIf="infoDrawerOpened$ | async">
|
||||||
<aca-info-drawer [node]="selection.last"></aca-info-drawer>
|
<aca-info-drawer [node]="selection.last"></aca-info-drawer>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -59,7 +59,8 @@ export class FavoritesComponent extends PageComponent implements OnInit {
|
|||||||
this.content.nodesDeleted.subscribe(() => this.reload()),
|
this.content.nodesDeleted.subscribe(() => this.reload()),
|
||||||
this.content.nodesRestored.subscribe(() => this.reload()),
|
this.content.nodesRestored.subscribe(() => this.reload()),
|
||||||
this.content.folderEdited.subscribe(() => this.reload()),
|
this.content.folderEdited.subscribe(() => this.reload()),
|
||||||
this.content.nodesMoved.subscribe(() => this.reload())
|
this.content.nodesMoved.subscribe(() => this.reload()),
|
||||||
|
this.content.favoriteRemoved.subscribe(() => this.reload())
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,126 +7,10 @@
|
|||||||
</adf-breadcrumb>
|
</adf-breadcrumb>
|
||||||
|
|
||||||
<adf-toolbar class="inline">
|
<adf-toolbar class="inline">
|
||||||
<button *ifExperimental="'cardview'"
|
<app-document-display-mode *ifExperimental="'cardview'"></app-document-display-mode>
|
||||||
color="primary"
|
<ng-container *ngFor="let entry of actions; trackBy: trackByActionId">
|
||||||
mat-icon-button
|
|
||||||
(click)="toggleGalleryView()">
|
|
||||||
<mat-icon *ngIf="displayMode === 'list'" matTooltip="{{ 'APP.DOCUMENT_LIST.TOOLBAR.CARDVIEW' | translate }}">view_comfy</mat-icon>
|
|
||||||
<mat-icon *ngIf="displayMode === 'gallery'" matTooltip="{{ 'APP.DOCUMENT_LIST.TOOLBAR.LISTVIEW' | translate }}">list</mat-icon>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<ng-container *ifExperimental="'extensions'">
|
|
||||||
<ng-container *ngFor="let entry of actions">
|
|
||||||
<aca-toolbar-action [entry]="entry"></aca-toolbar-action>
|
<aca-toolbar-action [entry]="entry"></aca-toolbar-action>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</ng-container>
|
|
||||||
|
|
||||||
<ng-container *ngIf="!selection.isEmpty">
|
|
||||||
<button
|
|
||||||
color="primary"
|
|
||||||
mat-icon-button
|
|
||||||
*ngIf="selection.file"
|
|
||||||
title="{{ 'APP.ACTIONS.VIEW' | translate }}"
|
|
||||||
(click)="showPreview(selection.file)">
|
|
||||||
<mat-icon>open_in_browser</mat-icon>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button
|
|
||||||
color="primary"
|
|
||||||
mat-icon-button
|
|
||||||
title="{{ 'APP.ACTIONS.DOWNLOAD' | translate }}"
|
|
||||||
(click)="downloadSelection()">
|
|
||||||
<mat-icon>get_app</mat-icon>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button
|
|
||||||
color="primary"
|
|
||||||
mat-icon-button
|
|
||||||
*ngIf="canEditFolder"
|
|
||||||
title="{{ 'APP.ACTIONS.EDIT' | translate }}"
|
|
||||||
[acaEditFolder]="selection.folder">
|
|
||||||
<mat-icon>create</mat-icon>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button mat-icon-button
|
|
||||||
[color]="infoDrawerOpened ? 'accent' : 'primary'"
|
|
||||||
title="{{ 'APP.ACTIONS.DETAILS' | translate }}"
|
|
||||||
(click)="toggleSidebar()">
|
|
||||||
<mat-icon>info_outline</mat-icon>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<ng-container *ifExperimental="'share'">
|
|
||||||
<button mat-icon-button
|
|
||||||
color="primary"
|
|
||||||
title="{{ 'APP.ACTIONS.SHARE' | translate }}"
|
|
||||||
*ngIf="selection.file"
|
|
||||||
[baseShareUrl]="sharedPreviewUrl$ | async"
|
|
||||||
[adf-share]="selection.file">
|
|
||||||
<mat-icon>share</mat-icon>
|
|
||||||
</button>
|
|
||||||
</ng-container>
|
|
||||||
|
|
||||||
<button
|
|
||||||
color="primary"
|
|
||||||
mat-icon-button
|
|
||||||
title="{{ 'APP.ACTIONS.MORE' | translate }}"
|
|
||||||
[matMenuTriggerFor]="actionsMenu">
|
|
||||||
<mat-icon>more_vert</mat-icon>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<mat-menu #actionsMenu="matMenu"
|
|
||||||
[overlapTrigger]="false">
|
|
||||||
<button
|
|
||||||
mat-menu-item
|
|
||||||
#favorites="adfFavorite"
|
|
||||||
[adf-node-favorite]="selection.nodes">
|
|
||||||
<mat-icon color="primary" *ngIf="favorites.hasFavorites()">star</mat-icon>
|
|
||||||
<mat-icon *ngIf="!favorites.hasFavorites()">star_border</mat-icon>
|
|
||||||
<span>{{ 'APP.ACTIONS.FAVORITE' | translate }}</span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button
|
|
||||||
mat-menu-item
|
|
||||||
[acaCopyNode]="selection.nodes">
|
|
||||||
<mat-icon>content_copy</mat-icon>
|
|
||||||
<span>{{ 'APP.ACTIONS.COPY' | translate }}</span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button
|
|
||||||
mat-menu-item
|
|
||||||
*ngIf="canDelete"
|
|
||||||
[acaMoveNode]="selection.nodes">
|
|
||||||
<mat-icon>library_books</mat-icon>
|
|
||||||
<span>{{ 'APP.ACTIONS.MOVE' | translate }}</span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button
|
|
||||||
mat-menu-item
|
|
||||||
*ngIf="canDelete"
|
|
||||||
[acaDeleteNode]="selection.nodes">
|
|
||||||
<mat-icon>delete</mat-icon>
|
|
||||||
<span>{{ 'APP.ACTIONS.DELETE' | translate }}</span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button
|
|
||||||
mat-menu-item
|
|
||||||
*ngIf="selection.file"
|
|
||||||
[acaNodeVersions]="selection.file">
|
|
||||||
<mat-icon>history</mat-icon>
|
|
||||||
<span>{{ 'APP.ACTIONS.VERSIONS' | translate }}</span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<ng-container *ifExperimental="'permissions'">
|
|
||||||
<button
|
|
||||||
mat-menu-item
|
|
||||||
*ngIf="canUpdateNode"
|
|
||||||
[acaNodePermissions]="selection.first">
|
|
||||||
<mat-icon>settings_input_component</mat-icon>
|
|
||||||
<span>{{ 'APP.ACTIONS.PERMISSIONS' | translate }}</span>
|
|
||||||
</button>
|
|
||||||
</ng-container>
|
|
||||||
</mat-menu>
|
|
||||||
</ng-container>
|
|
||||||
</adf-toolbar>
|
</adf-toolbar>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -141,6 +25,7 @@
|
|||||||
[disabled]="!canUpload">
|
[disabled]="!canUpload">
|
||||||
|
|
||||||
<adf-document-list acaDocumentList #documentList
|
<adf-document-list acaDocumentList #documentList
|
||||||
|
[display]="documentDisplayMode$ | async"
|
||||||
[sorting]="[ 'modifiedAt', 'desc' ]"
|
[sorting]="[ 'modifiedAt', 'desc' ]"
|
||||||
selectionMode="multiple"
|
selectionMode="multiple"
|
||||||
[currentFolderId]="node?.id"
|
[currentFolderId]="node?.id"
|
||||||
@ -199,7 +84,7 @@
|
|||||||
</adf-upload-drag-area>
|
</adf-upload-drag-area>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="inner-layout__side-panel" *ngIf="infoDrawerOpened">
|
<div class="inner-layout__side-panel" *ngIf="infoDrawerOpened$ | async">
|
||||||
<aca-info-drawer [node]="selection.last"></aca-info-drawer>
|
<aca-info-drawer [node]="selection.last"></aca-info-drawer>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -29,7 +29,9 @@ 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, UploadService, AppConfigPipe
|
DataTableComponent,
|
||||||
|
UploadService,
|
||||||
|
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';
|
||||||
@ -41,7 +43,6 @@ import { ExperimentalDirective } from '../../directives/experimental.directive';
|
|||||||
|
|
||||||
describe('FilesComponent', () => {
|
describe('FilesComponent', () => {
|
||||||
let node;
|
let node;
|
||||||
let page;
|
|
||||||
let fixture: ComponentFixture<FilesComponent>;
|
let fixture: ComponentFixture<FilesComponent>;
|
||||||
let component: FilesComponent;
|
let component: FilesComponent;
|
||||||
let contentManagementService: ContentManagementService;
|
let contentManagementService: ContentManagementService;
|
||||||
@ -90,20 +91,12 @@ describe('FilesComponent', () => {
|
|||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
node = { id: 'node-id', isFolder: true };
|
node = { id: 'node-id', isFolder: true };
|
||||||
page = {
|
|
||||||
list: {
|
|
||||||
entries: ['a', 'b', 'c'],
|
|
||||||
pagination: {}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
spyOn(component.documentList, 'loadFolder').and.callFake(() => {});
|
spyOn(component.documentList, 'loadFolder').and.callFake(() => {});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Current page is valid', () => {
|
describe('Current page is valid', () => {
|
||||||
it('should be a valid current page', fakeAsync(() => {
|
it('should be a valid current page', fakeAsync(() => {
|
||||||
spyOn(contentApi, 'getNode').and.returnValue(Observable.of({ entry: node }));
|
spyOn(contentApi, 'getNode').and.returnValue(Observable.throw(null));
|
||||||
spyOn(component, 'fetchNodes').and.returnValue(Observable.throw(null));
|
|
||||||
|
|
||||||
component.ngOnInit();
|
component.ngOnInit();
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
@ -114,7 +107,6 @@ describe('FilesComponent', () => {
|
|||||||
|
|
||||||
it('should set current page as invalid path', fakeAsync(() => {
|
it('should set current page as invalid path', fakeAsync(() => {
|
||||||
spyOn(contentApi, 'getNode').and.returnValue(Observable.of({ entry: node }));
|
spyOn(contentApi, 'getNode').and.returnValue(Observable.of({ entry: node }));
|
||||||
spyOn(component, 'fetchNodes').and.returnValue(Observable.of(page));
|
|
||||||
|
|
||||||
component.ngOnInit();
|
component.ngOnInit();
|
||||||
tick();
|
tick();
|
||||||
@ -127,22 +119,10 @@ describe('FilesComponent', () => {
|
|||||||
describe('OnInit', () => {
|
describe('OnInit', () => {
|
||||||
it('should set current node', () => {
|
it('should set current node', () => {
|
||||||
spyOn(contentApi, 'getNode').and.returnValue(Observable.of({ entry: node }));
|
spyOn(contentApi, 'getNode').and.returnValue(Observable.of({ entry: node }));
|
||||||
spyOn(component, 'fetchNodes').and.returnValue(Observable.of(page));
|
|
||||||
|
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
|
|
||||||
expect(component.node).toBe(node);
|
expect(component.node).toBe(node);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should get current node children', () => {
|
|
||||||
spyOn(contentApi, 'getNode').and.returnValue(Observable.of({ entry: node }));
|
|
||||||
spyOn(component, 'fetchNodes').and.returnValue(Observable.of(page));
|
|
||||||
|
|
||||||
fixture.detectChanges();
|
|
||||||
|
|
||||||
expect(component.fetchNodes).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('if should navigate to parent if node is not a folder', () => {
|
it('if should navigate to parent if node is not a folder', () => {
|
||||||
node.isFolder = false;
|
node.isFolder = false;
|
||||||
node.parentId = 'parent-id';
|
node.parentId = 'parent-id';
|
||||||
@ -157,8 +137,7 @@ describe('FilesComponent', () => {
|
|||||||
|
|
||||||
describe('refresh on events', () => {
|
describe('refresh on events', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
spyOn(contentApi, 'getNode').and.returnValue(Observable.of(node));
|
spyOn(contentApi, 'getNode').and.returnValue(Observable.of({ entry: node }));
|
||||||
spyOn(component, 'fetchNodes').and.returnValue(Observable.of(page));
|
|
||||||
spyOn(component.documentList, 'reload');
|
spyOn(component.documentList, 'reload');
|
||||||
|
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
@ -170,9 +149,9 @@ describe('FilesComponent', () => {
|
|||||||
{ entry: { parentId: '2' } }
|
{ entry: { parentId: '2' } }
|
||||||
];
|
];
|
||||||
|
|
||||||
component.node = <any>{ id: '1' };
|
component.node = { id: '1' };
|
||||||
|
|
||||||
nodeActionsService.contentCopied.next(<any>nodes);
|
nodeActionsService.contentCopied.next(nodes);
|
||||||
|
|
||||||
expect(component.documentList.reload).toHaveBeenCalled();
|
expect(component.documentList.reload).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
@ -183,9 +162,9 @@ describe('FilesComponent', () => {
|
|||||||
{ entry: { parentId: '2' } }
|
{ entry: { parentId: '2' } }
|
||||||
];
|
];
|
||||||
|
|
||||||
component.node = <any>{ id: '3' };
|
component.node = { id: '3' };
|
||||||
|
|
||||||
nodeActionsService.contentCopied.next(<any>nodes);
|
nodeActionsService.contentCopied.next(nodes);
|
||||||
|
|
||||||
expect(component.documentList.reload).not.toHaveBeenCalled();
|
expect(component.documentList.reload).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
@ -222,7 +201,7 @@ describe('FilesComponent', () => {
|
|||||||
|
|
||||||
it('should call refresh on fileUploadComplete event if parent node match', () => {
|
it('should call refresh on fileUploadComplete event if parent node match', () => {
|
||||||
const file = { file: { options: { parentId: 'parentId' } } };
|
const file = { file: { options: { parentId: 'parentId' } } };
|
||||||
component.node = <any>{ id: 'parentId' };
|
component.node = { id: 'parentId' };
|
||||||
|
|
||||||
uploadService.fileUploadComplete.next(<any>file);
|
uploadService.fileUploadComplete.next(<any>file);
|
||||||
|
|
||||||
@ -231,7 +210,7 @@ describe('FilesComponent', () => {
|
|||||||
|
|
||||||
it('should not call refresh on fileUploadComplete event if parent mismatch', () => {
|
it('should not call refresh on fileUploadComplete event if parent mismatch', () => {
|
||||||
const file = { file: { options: { parentId: 'otherId' } } };
|
const file = { file: { options: { parentId: 'otherId' } } };
|
||||||
component.node = <any>{ id: 'parentId' };
|
component.node = { id: 'parentId' };
|
||||||
|
|
||||||
uploadService.fileUploadComplete.next(<any>file);
|
uploadService.fileUploadComplete.next(<any>file);
|
||||||
|
|
||||||
@ -240,7 +219,7 @@ describe('FilesComponent', () => {
|
|||||||
|
|
||||||
it('should call refresh on fileUploadDeleted event if parent node match', () => {
|
it('should call refresh on fileUploadDeleted event if parent node match', () => {
|
||||||
const file = { file: { options: { parentId: 'parentId' } } };
|
const file = { file: { options: { parentId: 'parentId' } } };
|
||||||
component.node = <any>{ id: 'parentId' };
|
component.node = { id: 'parentId' };
|
||||||
|
|
||||||
uploadService.fileUploadDeleted.next(<any>file);
|
uploadService.fileUploadDeleted.next(<any>file);
|
||||||
|
|
||||||
@ -248,40 +227,24 @@ describe('FilesComponent', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should not call refresh on fileUploadDeleted event if parent mismatch', () => {
|
it('should not call refresh on fileUploadDeleted event if parent mismatch', () => {
|
||||||
const file = { file: { options: { parentId: 'otherId' } } };
|
const file: any = { file: { options: { parentId: 'otherId' } } };
|
||||||
component.node = <any>{ id: 'parentId' };
|
component.node = { id: 'parentId' };
|
||||||
|
|
||||||
uploadService.fileUploadDeleted.next(<any>file);
|
uploadService.fileUploadDeleted.next(file);
|
||||||
|
|
||||||
expect(component.documentList.reload).not.toHaveBeenCalled();
|
expect(component.documentList.reload).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('fetchNodes()', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
spyOn(contentApi, 'getNode').and.returnValue(Observable.of(node));
|
|
||||||
spyOn(contentApi, 'getNodeChildren').and.returnValue(Observable.of(page));
|
|
||||||
|
|
||||||
fixture.detectChanges();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should call getNode api with node id', () => {
|
|
||||||
component.fetchNodes('nodeId');
|
|
||||||
|
|
||||||
expect(contentApi.getNodeChildren).toHaveBeenCalledWith('nodeId');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('onBreadcrumbNavigate()', () => {
|
describe('onBreadcrumbNavigate()', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
spyOn(contentApi, 'getNode').and.returnValue(Observable.of(node));
|
spyOn(contentApi, 'getNode').and.returnValue(Observable.of({ entry: node }));
|
||||||
spyOn(component, 'fetchNodes').and.returnValue(Observable.of(page));
|
|
||||||
|
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should navigates to node id', () => {
|
it('should navigates to node id', () => {
|
||||||
const routeData = <any>{ id: 'some-where-over-the-rainbow' };
|
const routeData: any = { id: 'some-where-over-the-rainbow' };
|
||||||
spyOn(component, 'navigate');
|
spyOn(component, 'navigate');
|
||||||
|
|
||||||
component.onBreadcrumbNavigate(routeData);
|
component.onBreadcrumbNavigate(routeData);
|
||||||
@ -292,8 +255,7 @@ describe('FilesComponent', () => {
|
|||||||
|
|
||||||
describe('Node navigation', () => {
|
describe('Node navigation', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
spyOn(contentApi, 'getNode').and.returnValue(Observable.of(node));
|
spyOn(contentApi, 'getNode').and.returnValue(Observable.of({ entry: node }));
|
||||||
spyOn(component, 'fetchNodes').and.returnValue(Observable.of(page));
|
|
||||||
spyOn(router, 'navigate');
|
spyOn(router, 'navigate');
|
||||||
|
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
@ -312,7 +274,7 @@ describe('FilesComponent', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should navigate home if node is root', () => {
|
it('should navigate home if node is root', () => {
|
||||||
(<any>component).node = {
|
component.node = {
|
||||||
path: {
|
path: {
|
||||||
elements: [ {id: 'node-id'} ]
|
elements: [ {id: 'node-id'} ]
|
||||||
}
|
}
|
||||||
|
@ -27,8 +27,7 @@ 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, NodePaging, PathElement, PathElementEntity } from 'alfresco-js-api';
|
import { MinimalNodeEntity, MinimalNodeEntryEntity, PathElement, PathElementEntity } from 'alfresco-js-api';
|
||||||
import { Observable } from 'rxjs/Rx';
|
|
||||||
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';
|
||||||
@ -68,19 +67,21 @@ 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.getNode(nodeId)
|
this.contentApi
|
||||||
.map(node => node.entry)
|
.getNode(nodeId)
|
||||||
.do(node => {
|
|
||||||
if (node.isFolder) {
|
|
||||||
this.updateCurrentNode(node);
|
|
||||||
} else {
|
|
||||||
this.router.navigate(['/personal-files', node.parentId], { replaceUrl: true });
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.skipWhile(node => !node.isFolder)
|
|
||||||
.flatMap(node => this.fetchNodes(node.id))
|
|
||||||
.subscribe(
|
.subscribe(
|
||||||
() => this.isValidPath = true,
|
node => {
|
||||||
|
this.isValidPath = true;
|
||||||
|
|
||||||
|
if (node.entry && node.entry.isFolder) {
|
||||||
|
this.updateCurrentNode(node.entry);
|
||||||
|
} else {
|
||||||
|
this.router.navigate(
|
||||||
|
['/personal-files', node.entry.parentId],
|
||||||
|
{ replaceUrl: true }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
() => this.isValidPath = false
|
() => this.isValidPath = false
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@ -102,10 +103,6 @@ export class FilesComponent extends PageComponent implements OnInit, OnDestroy {
|
|||||||
this.store.dispatch(new SetCurrentFolderAction(null));
|
this.store.dispatch(new SetCurrentFolderAction(null));
|
||||||
}
|
}
|
||||||
|
|
||||||
fetchNodes(parentNodeId?: string): Observable<NodePaging> {
|
|
||||||
return this.contentApi.getNodeChildren(parentNodeId);
|
|
||||||
}
|
|
||||||
|
|
||||||
navigate(nodeId: string = null) {
|
navigate(nodeId: string = null) {
|
||||||
const commands = [ './' ];
|
const commands = [ './' ];
|
||||||
|
|
||||||
|
@ -4,39 +4,11 @@
|
|||||||
</adf-breadcrumb>
|
</adf-breadcrumb>
|
||||||
|
|
||||||
<adf-toolbar class="inline">
|
<adf-toolbar class="inline">
|
||||||
<button *ifExperimental="'cardview'"
|
<app-document-display-mode *ifExperimental="'cardview'"></app-document-display-mode>
|
||||||
mat-icon-button
|
|
||||||
color="primary"
|
|
||||||
(click)="toggleGalleryView()">
|
|
||||||
<mat-icon *ngIf="displayMode === 'list'" matTooltip="{{ 'APP.DOCUMENT_LIST.TOOLBAR.CARDVIEW' | translate }}">view_comfy</mat-icon>
|
|
||||||
<mat-icon *ngIf="displayMode === 'gallery'" matTooltip="{{ 'APP.DOCUMENT_LIST.TOOLBAR.LISTVIEW' | translate }}">list</mat-icon>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button
|
<ng-container *ifExperimental="'extensions'">
|
||||||
mat-icon-button
|
<ng-container *ngFor="let entry of actions; trackBy: trackByActionId">
|
||||||
color="primary"
|
<aca-toolbar-action [entry]="entry"></aca-toolbar-action>
|
||||||
*ifExperimental="'libraries'"
|
|
||||||
(click)="createLibrary()">
|
|
||||||
<mat-icon>create_new_folder</mat-icon>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<ng-container *ngIf="!selection.isEmpty">
|
|
||||||
<ng-container *ifExperimental="'libraries'">
|
|
||||||
<button
|
|
||||||
color="primary"
|
|
||||||
mat-icon-button
|
|
||||||
title="{{ 'APP.ACTIONS.MORE' | translate }}"
|
|
||||||
[matMenuTriggerFor]="actionsMenu">
|
|
||||||
<mat-icon>more_vert</mat-icon>
|
|
||||||
</button>
|
|
||||||
<mat-menu #actionsMenu="matMenu" [overlapTrigger]="false">
|
|
||||||
<button
|
|
||||||
mat-menu-item
|
|
||||||
(click)="deleteLibrary(selection.first)">
|
|
||||||
<mat-icon>delete</mat-icon>
|
|
||||||
<span>{{ 'APP.ACTIONS.DELETE' | translate }}</span>
|
|
||||||
</button>
|
|
||||||
</mat-menu>
|
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</adf-toolbar>
|
</adf-toolbar>
|
||||||
@ -45,6 +17,7 @@
|
|||||||
<div class="inner-layout__content">
|
<div class="inner-layout__content">
|
||||||
<div class="inner-layout__panel">
|
<div class="inner-layout__panel">
|
||||||
<adf-document-list acaDocumentList #documentList
|
<adf-document-list acaDocumentList #documentList
|
||||||
|
[display]="documentDisplayMode$ | async"
|
||||||
currentFolderId="-mysites-"
|
currentFolderId="-mysites-"
|
||||||
selectionMode="single"
|
selectionMode="single"
|
||||||
[navigate]="false"
|
[navigate]="false"
|
||||||
|
@ -30,7 +30,6 @@ import { ShareDataRow } 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';
|
||||||
import { DeleteLibraryAction, CreateLibraryAction } from '../../store/actions';
|
|
||||||
import { SiteEntry } from 'alfresco-js-api';
|
import { SiteEntry } from 'alfresco-js-api';
|
||||||
import { ContentManagementService } from '../../services/content-management.service';
|
import { ContentManagementService } from '../../services/content-management.service';
|
||||||
import { ContentApiService } from '../../services/content-api.service';
|
import { ContentApiService } from '../../services/content-api.service';
|
||||||
@ -100,14 +99,4 @@ export class LibrariesComponent extends PageComponent implements OnInit {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
deleteLibrary(node: SiteEntry) {
|
|
||||||
if (node && node.entry) {
|
|
||||||
this.store.dispatch(new DeleteLibraryAction(node.entry.id));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
createLibrary() {
|
|
||||||
this.store.dispatch(new CreateLibraryAction());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -24,14 +24,13 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { DocumentListComponent, ShareDataRow } from '@alfresco/adf-content-services';
|
import { DocumentListComponent, ShareDataRow } from '@alfresco/adf-content-services';
|
||||||
import { DisplayMode } from '@alfresco/adf-core';
|
|
||||||
import { OnDestroy, OnInit, ViewChild } from '@angular/core';
|
import { OnDestroy, OnInit, ViewChild } from '@angular/core';
|
||||||
import { Store } from '@ngrx/store';
|
import { Store } from '@ngrx/store';
|
||||||
import { MinimalNodeEntity, MinimalNodeEntryEntity } from 'alfresco-js-api';
|
import { MinimalNodeEntity, MinimalNodeEntryEntity } from 'alfresco-js-api';
|
||||||
import { takeUntil } from 'rxjs/operators';
|
import { takeUntil } from 'rxjs/operators';
|
||||||
import { Subject, Subscription } from 'rxjs/Rx';
|
import { Subject, Subscription } from 'rxjs/Rx';
|
||||||
import { SetSelectedNodesAction, DownloadNodesAction, ViewFileAction } from '../store/actions';
|
import { SetSelectedNodesAction, ViewFileAction } from '../store/actions';
|
||||||
import { appSelection, sharedUrl, currentFolder } from '../store/selectors/app.selectors';
|
import { appSelection, sharedUrl, currentFolder, infoDrawerOpened, documentDisplayMode } from '../store/selectors/app.selectors';
|
||||||
import { AppStore } from '../store/states/app.state';
|
import { AppStore } from '../store/states/app.state';
|
||||||
import { SelectionState } from '../store/states/selection.state';
|
import { SelectionState } from '../store/states/selection.state';
|
||||||
import { Observable } from 'rxjs/Rx';
|
import { Observable } from 'rxjs/Rx';
|
||||||
@ -47,19 +46,15 @@ export abstract class PageComponent implements OnInit, OnDestroy {
|
|||||||
documentList: DocumentListComponent;
|
documentList: DocumentListComponent;
|
||||||
|
|
||||||
title = 'Page';
|
title = 'Page';
|
||||||
infoDrawerOpened = false;
|
infoDrawerOpened$: Observable<boolean>;
|
||||||
node: MinimalNodeEntryEntity;
|
node: MinimalNodeEntryEntity;
|
||||||
selection: SelectionState;
|
selection: SelectionState;
|
||||||
displayMode = DisplayMode.List;
|
documentDisplayMode$: Observable<string>;
|
||||||
sharedPreviewUrl$: Observable<string>;
|
sharedPreviewUrl$: Observable<string>;
|
||||||
actions: Array<ContentActionRef> = [];
|
actions: Array<ContentActionRef> = [];
|
||||||
canUpdateFile = false;
|
viewerActions: Array<ContentActionRef> = [];
|
||||||
canUpdateNode = false;
|
canUpdateNode = false;
|
||||||
canDelete = false;
|
|
||||||
canEditFolder = false;
|
|
||||||
canUpload = false;
|
canUpload = false;
|
||||||
canDeleteShared = false;
|
|
||||||
canUpdateShared = false;
|
|
||||||
|
|
||||||
protected subscriptions: Subscription[] = [];
|
protected subscriptions: Subscription[] = [];
|
||||||
|
|
||||||
@ -74,22 +69,17 @@ export abstract class PageComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.sharedPreviewUrl$ = this.store.select(sharedUrl);
|
this.sharedPreviewUrl$ = this.store.select(sharedUrl);
|
||||||
|
this.infoDrawerOpened$ = this.store.select(infoDrawerOpened);
|
||||||
|
this.documentDisplayMode$ = this.store.select(documentDisplayMode);
|
||||||
|
|
||||||
this.store
|
this.store
|
||||||
.select(appSelection)
|
.select(appSelection)
|
||||||
.pipe(takeUntil(this.onDestroy$))
|
.pipe(takeUntil(this.onDestroy$))
|
||||||
.subscribe(selection => {
|
.subscribe(selection => {
|
||||||
this.selection = selection;
|
this.selection = selection;
|
||||||
if (selection.isEmpty) {
|
|
||||||
this.infoDrawerOpened = false;
|
|
||||||
}
|
|
||||||
this.actions = this.extensions.getAllowedContentActions();
|
this.actions = this.extensions.getAllowedContentActions();
|
||||||
this.canUpdateFile = this.selection.file && this.content.canUpdateNode(selection.file);
|
this.viewerActions = this.extensions.getViewerActions();
|
||||||
this.canUpdateNode = this.selection.count === 1 && this.content.canUpdateNode(selection.first);
|
this.canUpdateNode = this.selection.count === 1 && this.content.canUpdateNode(selection.first);
|
||||||
this.canDelete = !this.selection.isEmpty && this.content.canDeleteNodes(selection.nodes);
|
|
||||||
this.canEditFolder = selection.folder && this.content.canUpdateNode(selection.folder);
|
|
||||||
this.canDeleteShared = !this.selection.isEmpty && this.content.canDeleteSharedNodes(selection.nodes);
|
|
||||||
this.canUpdateShared = selection.file && this.content.canUpdateSharedNode(selection.file);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
this.store.select(currentFolder)
|
this.store.select(currentFolder)
|
||||||
@ -127,14 +117,6 @@ export abstract class PageComponent implements OnInit, OnDestroy {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleSidebar(event) {
|
|
||||||
if (event) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.infoDrawerOpened = !this.infoDrawerOpened;
|
|
||||||
}
|
|
||||||
|
|
||||||
reload(): void {
|
reload(): void {
|
||||||
if (this.documentList) {
|
if (this.documentList) {
|
||||||
this.documentList.resetSelection();
|
this.documentList.resetSelection();
|
||||||
@ -143,22 +125,7 @@ export abstract class PageComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleGalleryView(): void {
|
trackByActionId(index: number, action: ContentActionRef) {
|
||||||
this.displayMode = this.displayMode === DisplayMode.List ? DisplayMode.Gallery : DisplayMode.List;
|
return action.id;
|
||||||
this.documentList.display = this.displayMode;
|
|
||||||
}
|
|
||||||
|
|
||||||
downloadSelection() {
|
|
||||||
this.store.dispatch(new DownloadNodesAction());
|
|
||||||
}
|
|
||||||
|
|
||||||
// this is where each application decides how to treat an action and what to do
|
|
||||||
// the ACA maps actions to the NgRx actions as an example
|
|
||||||
runAction(actionId: string) {
|
|
||||||
const context = {
|
|
||||||
selection: this.selection
|
|
||||||
};
|
|
||||||
|
|
||||||
this.extensions.runActionById(actionId, context);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
[canNavigateBefore]="previousNodeId"
|
[canNavigateBefore]="previousNodeId"
|
||||||
[canNavigateNext]="nextNodeId"
|
[canNavigateNext]="nextNodeId"
|
||||||
[overlayMode]="true"
|
[overlayMode]="true"
|
||||||
(print) = "printFile($event)"
|
(print)="printFile()"
|
||||||
(showViewerChange)="onVisibilityChanged($event)"
|
(showViewerChange)="onVisibilityChanged($event)"
|
||||||
(navigateBefore)="onNavigateBefore()"
|
(navigateBefore)="onNavigateBefore()"
|
||||||
(navigateNext)="onNavigateNext()">
|
(navigateNext)="onNavigateNext()">
|
||||||
@ -18,74 +18,14 @@
|
|||||||
</adf-viewer-sidebar>
|
</adf-viewer-sidebar>
|
||||||
|
|
||||||
<adf-viewer-open-with *ifExperimental="'extensions'">
|
<adf-viewer-open-with *ifExperimental="'extensions'">
|
||||||
<button *ngFor="let entry of openWith"
|
<ng-container *ngFor="let action of openWith; trackBy: trackByActionId">
|
||||||
mat-menu-item
|
<aca-toolbar-action type="menu-item" [entry]="action"></aca-toolbar-action>
|
||||||
(click)="runAction(entry.actions.click)">
|
</ng-container>
|
||||||
<mat-icon>{{ entry.icon }}</mat-icon>
|
|
||||||
<span>{{ entry.title }}</span>
|
|
||||||
</button>
|
|
||||||
</adf-viewer-open-with>
|
</adf-viewer-open-with>
|
||||||
|
|
||||||
<adf-viewer-more-actions>
|
<adf-viewer-more-actions>
|
||||||
|
<ng-container *ngFor="let action of viewerActions; trackBy: trackByActionId">
|
||||||
<button
|
<aca-toolbar-action type="menu-item" [entry]="action"></aca-toolbar-action>
|
||||||
mat-menu-item
|
|
||||||
#favorites="adfFavorite"
|
|
||||||
[adf-node-favorite]="selection.nodes">
|
|
||||||
<mat-icon color="primary" *ngIf="favorites.hasFavorites()">star</mat-icon>
|
|
||||||
<mat-icon *ngIf="!favorites.hasFavorites()">star_border</mat-icon>
|
|
||||||
<span>{{ 'APP.ACTIONS.FAVORITE' | translate }}</span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<ng-container *ifExperimental="'share'">
|
|
||||||
<button mat-menu-item
|
|
||||||
color="primary"
|
|
||||||
[baseShareUrl]="sharedPreviewUrl$ | async"
|
|
||||||
[adf-share]="selection.file">
|
|
||||||
<mat-icon>share</mat-icon>
|
|
||||||
<span>{{ 'APP.ACTIONS.SHARE' | translate }}</span>
|
|
||||||
</button>
|
|
||||||
</ng-container>
|
|
||||||
|
|
||||||
<button
|
|
||||||
mat-menu-item
|
|
||||||
[acaCopyNode]="selection.nodes">
|
|
||||||
<mat-icon>content_copy</mat-icon>
|
|
||||||
<span>{{ 'APP.ACTIONS.COPY' | translate }}</span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button
|
|
||||||
mat-menu-item
|
|
||||||
*ngIf="canDelete"
|
|
||||||
[acaMoveNode]="selection.nodes">
|
|
||||||
<mat-icon>library_books</mat-icon>
|
|
||||||
<span>{{ 'APP.ACTIONS.MOVE' | translate }}</span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button
|
|
||||||
mat-menu-item
|
|
||||||
*ngIf="canDelete"
|
|
||||||
(click)="deleteFile()">
|
|
||||||
<mat-icon>delete</mat-icon>
|
|
||||||
<span>{{ 'APP.ACTIONS.DELETE' | translate }}</span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button
|
|
||||||
mat-menu-item
|
|
||||||
*ngIf="canUpdateFile"
|
|
||||||
[acaNodeVersions]="selection.file">
|
|
||||||
<mat-icon>history</mat-icon>
|
|
||||||
<span>{{ 'APP.ACTIONS.VERSIONS' | translate }}</span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<ng-container *ifExperimental="'permissions'">
|
|
||||||
<button
|
|
||||||
mat-menu-item
|
|
||||||
*ngIf="canUpdateNode"
|
|
||||||
[acaNodePermissions]="selection.first">
|
|
||||||
<mat-icon>settings_input_component</mat-icon>
|
|
||||||
<span>{{ 'APP.ACTIONS.PERMISSIONS' | translate }}</span>
|
|
||||||
</button>
|
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</adf-viewer-more-actions>
|
</adf-viewer-more-actions>
|
||||||
</adf-viewer>
|
</adf-viewer>
|
||||||
|
@ -28,7 +28,7 @@ import { ActivatedRoute, Router, UrlTree, UrlSegmentGroup, UrlSegment, PRIMARY_O
|
|||||||
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';
|
||||||
import { DeleteNodesAction, SetSelectedNodesAction } from '../../store/actions';
|
import { SetSelectedNodesAction } from '../../store/actions';
|
||||||
import { PageComponent } from '../page.component';
|
import { PageComponent } from '../page.component';
|
||||||
import { ContentApiService } from '../../services/content-api.service';
|
import { ContentApiService } from '../../services/content-api.service';
|
||||||
import { ExtensionService } from '../../extensions/extension.service';
|
import { ExtensionService } from '../../extensions/extension.service';
|
||||||
@ -335,17 +335,7 @@ export class PreviewComponent extends PageComponent implements OnInit {
|
|||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
|
|
||||||
deleteFile() {
|
printFile() {
|
||||||
this.store.dispatch(new DeleteNodesAction([
|
|
||||||
{
|
|
||||||
id: this.node.nodeId || this.node.id,
|
|
||||||
name: this.node.name
|
|
||||||
}
|
|
||||||
]));
|
|
||||||
this.onVisibilityChanged(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
printFile(event: any) {
|
|
||||||
this.viewUtils.printFileGeneric(this.nodeId, this.node.content.mimeType);
|
this.viewUtils.printFileGeneric(this.nodeId, this.node.content.mimeType);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,7 +27,7 @@ import { CoreModule } from '@alfresco/adf-core';
|
|||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
import { Routes, RouterModule } from '@angular/router';
|
import { Routes, RouterModule } from '@angular/router';
|
||||||
|
import { CoreExtensionsModule } from '../../extensions/core.extensions.module';
|
||||||
import { DirectivesModule } from '../../directives/directives.module';
|
import { DirectivesModule } from '../../directives/directives.module';
|
||||||
import { AppInfoDrawerModule } from '../info-drawer/info.drawer.module';
|
import { AppInfoDrawerModule } from '../info-drawer/info.drawer.module';
|
||||||
import { PreviewComponent } from './preview.component';
|
import { PreviewComponent } from './preview.component';
|
||||||
@ -51,7 +51,8 @@ const routes: Routes = [
|
|||||||
CoreModule.forChild(),
|
CoreModule.forChild(),
|
||||||
ContentDirectiveModule,
|
ContentDirectiveModule,
|
||||||
DirectivesModule,
|
DirectivesModule,
|
||||||
AppInfoDrawerModule
|
AppInfoDrawerModule,
|
||||||
|
CoreExtensionsModule.forChild()
|
||||||
],
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
PreviewComponent,
|
PreviewComponent,
|
||||||
|
@ -4,123 +4,18 @@
|
|||||||
</adf-breadcrumb>
|
</adf-breadcrumb>
|
||||||
|
|
||||||
<adf-toolbar class="inline">
|
<adf-toolbar class="inline">
|
||||||
<button *ifExperimental="'cardview'"
|
<app-document-display-mode *ifExperimental="'cardview'"></app-document-display-mode>
|
||||||
color="primary"
|
|
||||||
mat-icon-button
|
|
||||||
(click)="toggleGalleryView()">
|
|
||||||
<mat-icon *ngIf="displayMode === 'list'" matTooltip="{{ 'APP.DOCUMENT_LIST.TOOLBAR.CARDVIEW' | translate }}">view_comfy</mat-icon>
|
|
||||||
<mat-icon *ngIf="displayMode === 'gallery'" matTooltip="{{ 'APP.DOCUMENT_LIST.TOOLBAR.LISTVIEW' | translate }}">list</mat-icon>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<ng-container *ifExperimental="'extensions'">
|
<ng-container *ngFor="let entry of actions; trackBy: trackByActionId">
|
||||||
<ng-container *ngFor="let entry of actions">
|
|
||||||
<aca-toolbar-action [entry]="entry"></aca-toolbar-action>
|
<aca-toolbar-action [entry]="entry"></aca-toolbar-action>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</ng-container>
|
|
||||||
|
|
||||||
<ng-container *ngIf="!selection.isEmpty">
|
|
||||||
<button
|
|
||||||
mat-icon-button
|
|
||||||
color="primary"
|
|
||||||
*ngIf="selection.file"
|
|
||||||
title="{{ 'APP.ACTIONS.VIEW' | translate }}"
|
|
||||||
(click)="showPreview(selection.file)">
|
|
||||||
<mat-icon>open_in_browser</mat-icon>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button
|
|
||||||
mat-icon-button
|
|
||||||
color="primary"
|
|
||||||
title="{{ 'APP.ACTIONS.DOWNLOAD' | translate }}"
|
|
||||||
(click)="downloadSelection()">
|
|
||||||
<mat-icon>get_app</mat-icon>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button mat-icon-button
|
|
||||||
[color]="infoDrawerOpened ? 'accent' : 'primary'"
|
|
||||||
title="{{ 'APP.ACTIONS.DETAILS' | translate }}"
|
|
||||||
(click)="toggleSidebar()">
|
|
||||||
<mat-icon>info_outline</mat-icon>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<ng-container *ifExperimental="'share'">
|
|
||||||
<button *ngIf="selection.file"
|
|
||||||
mat-icon-button
|
|
||||||
color="primary"
|
|
||||||
title="{{ 'APP.ACTIONS.SHARE' | translate }}"
|
|
||||||
[baseShareUrl]="sharedPreviewUrl$ | async"
|
|
||||||
[adf-share]="selection.file">
|
|
||||||
<mat-icon>share</mat-icon>
|
|
||||||
</button>
|
|
||||||
</ng-container>
|
|
||||||
|
|
||||||
<button
|
|
||||||
color="primary"
|
|
||||||
mat-icon-button
|
|
||||||
title="{{ 'APP.ACTIONS.MORE' | translate }}"
|
|
||||||
[matMenuTriggerFor]="actionsMenu">
|
|
||||||
<mat-icon>more_vert</mat-icon>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<mat-menu #actionsMenu="matMenu"
|
|
||||||
[overlapTrigger]="false">
|
|
||||||
<button
|
|
||||||
mat-menu-item
|
|
||||||
#favorites="adfFavorite"
|
|
||||||
[adf-node-favorite]="selection.nodes">
|
|
||||||
<mat-icon color="primary" *ngIf="favorites.hasFavorites()">star</mat-icon>
|
|
||||||
<mat-icon *ngIf="!favorites.hasFavorites()">star_border</mat-icon>
|
|
||||||
<span>{{ 'APP.ACTIONS.FAVORITE' | translate }}</span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button
|
|
||||||
mat-menu-item
|
|
||||||
[acaCopyNode]="selection.nodes">
|
|
||||||
<mat-icon>content_copy</mat-icon>
|
|
||||||
<span>{{ 'APP.ACTIONS.COPY' | translate }}</span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button
|
|
||||||
mat-menu-item
|
|
||||||
*ngIf="canDelete"
|
|
||||||
[acaMoveNode]="selection.nodes">
|
|
||||||
<mat-icon>library_books</mat-icon>
|
|
||||||
<span>{{ 'APP.ACTIONS.MOVE' | translate }}</span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button
|
|
||||||
mat-menu-item
|
|
||||||
*ngIf="canDelete"
|
|
||||||
[acaDeleteNode]="selection.nodes">
|
|
||||||
<mat-icon>delete</mat-icon>
|
|
||||||
<span>{{ 'APP.ACTIONS.DELETE' | translate }}</span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button
|
|
||||||
mat-menu-item
|
|
||||||
*ngIf="selection.file"
|
|
||||||
[acaNodeVersions]="selection.file">
|
|
||||||
<mat-icon>history</mat-icon>
|
|
||||||
<span>{{ 'APP.ACTIONS.VERSIONS' | translate }}</span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<ng-container *ifExperimental="'permissions'">
|
|
||||||
<button
|
|
||||||
mat-menu-item
|
|
||||||
*ngIf="canUpdateNode"
|
|
||||||
[acaNodePermissions]="selection.first">
|
|
||||||
<mat-icon>settings_input_component</mat-icon>
|
|
||||||
<span>{{ 'APP.ACTIONS.PERMISSIONS' | translate }}</span>
|
|
||||||
</button>
|
|
||||||
</ng-container>
|
|
||||||
</mat-menu>
|
|
||||||
</ng-container>
|
|
||||||
</adf-toolbar>
|
</adf-toolbar>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="inner-layout__content">
|
<div class="inner-layout__content">
|
||||||
<div class="inner-layout__panel">
|
<div class="inner-layout__panel">
|
||||||
<adf-document-list acaDocumentList #documentList
|
<adf-document-list acaDocumentList #documentList
|
||||||
|
[display]="documentDisplayMode$ | async"
|
||||||
currentFolderId="-recent-"
|
currentFolderId="-recent-"
|
||||||
selectionMode="multiple"
|
selectionMode="multiple"
|
||||||
[navigate]="false"
|
[navigate]="false"
|
||||||
@ -188,7 +83,7 @@
|
|||||||
</adf-pagination>
|
</adf-pagination>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="inner-layout__side-panel" *ngIf="infoDrawerOpened">
|
<div class="inner-layout__side-panel" *ngIf="infoDrawerOpened$ | async">
|
||||||
<aca-info-drawer [node]="selection.last"></aca-info-drawer>
|
<aca-info-drawer [node]="selection.last"></aca-info-drawer>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -3,81 +3,9 @@
|
|||||||
<adf-breadcrumb root="APP.BROWSE.SEARCH.TITLE">
|
<adf-breadcrumb root="APP.BROWSE.SEARCH.TITLE">
|
||||||
</adf-breadcrumb>
|
</adf-breadcrumb>
|
||||||
<adf-toolbar class="inline">
|
<adf-toolbar class="inline">
|
||||||
<ng-container *ifExperimental="'extensions'">
|
<ng-container *ngFor="let entry of actions; trackBy: trackByActionId">
|
||||||
<ng-container *ngFor="let entry of actions">
|
|
||||||
<aca-toolbar-action [entry]="entry"></aca-toolbar-action>
|
<aca-toolbar-action [entry]="entry"></aca-toolbar-action>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</ng-container>
|
|
||||||
|
|
||||||
<ng-container *ngIf="!selection.isEmpty">
|
|
||||||
<button
|
|
||||||
color="primary"
|
|
||||||
mat-icon-button
|
|
||||||
*ngIf="selection.file"
|
|
||||||
title="{{ 'APP.ACTIONS.VIEW' | translate }}"
|
|
||||||
(click)="showPreview(selection.file)">
|
|
||||||
<mat-icon>open_in_browser</mat-icon>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button
|
|
||||||
color="primary"
|
|
||||||
mat-icon-button
|
|
||||||
title="{{ 'APP.ACTIONS.DOWNLOAD' | translate }}"
|
|
||||||
(click)="downloadSelection()">
|
|
||||||
<mat-icon>get_app</mat-icon>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button mat-icon-button
|
|
||||||
[color]="infoDrawerOpened ? 'accent' : 'primary'"
|
|
||||||
title="{{ 'APP.ACTIONS.DETAILS' | translate }}"
|
|
||||||
(click)="toggleSidebar()">
|
|
||||||
<mat-icon>info_outline</mat-icon>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button
|
|
||||||
color="primary"
|
|
||||||
mat-icon-button
|
|
||||||
title="{{ 'APP.ACTIONS.MORE' | translate }}"
|
|
||||||
[matMenuTriggerFor]="actionsMenu">
|
|
||||||
<mat-icon>more_vert</mat-icon>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<mat-menu #actionsMenu="matMenu" [overlapTrigger]="false">
|
|
||||||
<button
|
|
||||||
mat-menu-item
|
|
||||||
#favorites="adfFavorite"
|
|
||||||
[adf-node-favorite]="selection.nodes">
|
|
||||||
<mat-icon color="primary" *ngIf="favorites.hasFavorites()">star</mat-icon>
|
|
||||||
<mat-icon *ngIf="!favorites.hasFavorites()">star_border</mat-icon>
|
|
||||||
<span>{{ 'APP.ACTIONS.FAVORITE' | translate }}</span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button
|
|
||||||
mat-menu-item
|
|
||||||
[acaCopyNode]="selection.nodes">
|
|
||||||
<mat-icon>content_copy</mat-icon>
|
|
||||||
<span>{{ 'APP.ACTIONS.COPY' | translate }}</span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button
|
|
||||||
mat-menu-item
|
|
||||||
*ngIf="selection.file"
|
|
||||||
[acaNodeVersions]="selection.file">
|
|
||||||
<mat-icon>history</mat-icon>
|
|
||||||
<span>{{ 'APP.ACTIONS.VERSIONS' | translate }}</span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<ng-container *ifExperimental="'permissions'">
|
|
||||||
<button
|
|
||||||
mat-menu-item
|
|
||||||
*ngIf="canUpdateNode"
|
|
||||||
[acaNodePermissions]="selection.first">
|
|
||||||
<mat-icon>settings_input_component</mat-icon>
|
|
||||||
<span>{{ 'APP.ACTIONS.PERMISSIONS' | translate }}</span>
|
|
||||||
</button>
|
|
||||||
</ng-container>
|
|
||||||
</mat-menu>
|
|
||||||
</ng-container>
|
|
||||||
</adf-toolbar>
|
</adf-toolbar>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -154,7 +82,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="inner-layout__side-panel" *ngIf="infoDrawerOpened">
|
<div class="inner-layout__side-panel" *ngIf="infoDrawerOpened$ | async">
|
||||||
<aca-info-drawer [node]="selection.last"></aca-info-drawer>
|
<aca-info-drawer [node]="selection.last"></aca-info-drawer>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -4,120 +4,18 @@
|
|||||||
</adf-breadcrumb>
|
</adf-breadcrumb>
|
||||||
|
|
||||||
<adf-toolbar class="inline">
|
<adf-toolbar class="inline">
|
||||||
<button *ifExperimental="'cardview'"
|
<app-document-display-mode *ifExperimental="'cardview'"></app-document-display-mode>
|
||||||
color="primary"
|
|
||||||
mat-icon-button
|
|
||||||
(click)="toggleGalleryView()">
|
|
||||||
<mat-icon *ngIf="displayMode === 'list'" matTooltip="{{ 'APP.DOCUMENT_LIST.TOOLBAR.CARDVIEW' | translate }}">view_comfy</mat-icon>
|
|
||||||
<mat-icon *ngIf="displayMode === 'gallery'" matTooltip="{{ 'APP.DOCUMENT_LIST.TOOLBAR.LISTVIEW' | translate }}">list</mat-icon>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<ng-container *ifExperimental="'extensions'">
|
<ng-container *ngFor="let entry of actions; trackBy: trackByActionId">
|
||||||
<ng-container *ngFor="let entry of actions">
|
|
||||||
<aca-toolbar-action [entry]="entry"></aca-toolbar-action>
|
<aca-toolbar-action [entry]="entry"></aca-toolbar-action>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</ng-container>
|
|
||||||
|
|
||||||
<ng-container *ngIf="!selection.isEmpty">
|
|
||||||
<button
|
|
||||||
*ngIf="selection.count === 1"
|
|
||||||
color="primary"
|
|
||||||
mat-icon-button
|
|
||||||
title="{{ 'APP.ACTIONS.VIEW' | translate }}"
|
|
||||||
(click)="showPreview(selection.nodes[0])">
|
|
||||||
<mat-icon>open_in_browser</mat-icon>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button
|
|
||||||
color="primary"
|
|
||||||
mat-icon-button
|
|
||||||
title="{{ 'APP.ACTIONS.DOWNLOAD' | translate }}"
|
|
||||||
(click)="downloadSelection()">
|
|
||||||
<mat-icon>get_app</mat-icon>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button mat-icon-button
|
|
||||||
[color]="infoDrawerOpened ? 'accent' : 'primary'"
|
|
||||||
title="{{ 'APP.ACTIONS.DETAILS' | translate }}"
|
|
||||||
(click)="toggleSidebar()">
|
|
||||||
<mat-icon>info_outline</mat-icon>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button
|
|
||||||
color="primary"
|
|
||||||
mat-icon-button
|
|
||||||
title="{{ 'APP.ACTIONS.MORE' | translate }}"
|
|
||||||
[matMenuTriggerFor]="actionsMenu">
|
|
||||||
<mat-icon>more_vert</mat-icon>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<mat-menu #actionsMenu="matMenu"
|
|
||||||
[overlapTrigger]="false">
|
|
||||||
<button
|
|
||||||
mat-menu-item
|
|
||||||
#favorites="adfFavorite"
|
|
||||||
[adf-node-favorite]="selection.nodes">
|
|
||||||
<mat-icon color="primary" *ngIf="favorites.hasFavorites()">star</mat-icon>
|
|
||||||
<mat-icon *ngIf="!favorites.hasFavorites()">star_border</mat-icon>
|
|
||||||
<span>{{ 'APP.ACTIONS.FAVORITE' | translate }}</span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button
|
|
||||||
mat-menu-item
|
|
||||||
[acaCopyNode]="selection.nodes">
|
|
||||||
<mat-icon>content_copy</mat-icon>
|
|
||||||
<span>{{ 'APP.ACTIONS.COPY' | translate }}</span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button
|
|
||||||
mat-menu-item
|
|
||||||
*ngIf="canDeleteShared"
|
|
||||||
[acaMoveNode]="selection.nodes">
|
|
||||||
<mat-icon>library_books</mat-icon>
|
|
||||||
<span>{{ 'APP.ACTIONS.MOVE' | translate }}</span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button
|
|
||||||
mat-menu-item
|
|
||||||
*ngIf="canDelete"
|
|
||||||
[acaUnshareNode]="selection.nodes">
|
|
||||||
<mat-icon>stop_screen_share</mat-icon>
|
|
||||||
<span>{{ 'APP.ACTIONS.UNSHARE' | translate }}</span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button
|
|
||||||
mat-menu-item
|
|
||||||
*ngIf="canDeleteShared"
|
|
||||||
[acaDeleteNode]="selection.nodes">
|
|
||||||
<mat-icon>delete</mat-icon>
|
|
||||||
<span>{{ 'APP.ACTIONS.DELETE' | translate }}</span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button
|
|
||||||
mat-menu-item
|
|
||||||
*ngIf="canUpdateShared"
|
|
||||||
[acaNodeVersions]="selection.file">
|
|
||||||
<mat-icon>history</mat-icon>
|
|
||||||
<span>{{ 'APP.ACTIONS.VERSIONS' | translate }}</span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<ng-container *ifExperimental="'permissions'">
|
|
||||||
<button
|
|
||||||
mat-menu-item
|
|
||||||
*ngIf="canUpdateShared"
|
|
||||||
[acaNodePermissions]="selection.first">
|
|
||||||
<mat-icon>settings_input_component</mat-icon>
|
|
||||||
<span>{{ 'APP.ACTIONS.PERMISSIONS' | translate }}</span>
|
|
||||||
</button>
|
|
||||||
</ng-container>
|
|
||||||
</mat-menu>
|
|
||||||
</ng-container>
|
|
||||||
</adf-toolbar>
|
</adf-toolbar>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="inner-layout__content">
|
<div class="inner-layout__content">
|
||||||
<div class="inner-layout__panel">
|
<div class="inner-layout__panel">
|
||||||
<adf-document-list acaDocumentList #documentList
|
<adf-document-list acaDocumentList #documentList
|
||||||
|
[display]="documentDisplayMode$ | async"
|
||||||
currentFolderId="-sharedlinks-"
|
currentFolderId="-sharedlinks-"
|
||||||
selectionMode="multiple"
|
selectionMode="multiple"
|
||||||
[sorting]="[ 'modifiedAt', 'desc' ]"
|
[sorting]="[ 'modifiedAt', 'desc' ]"
|
||||||
@ -196,7 +94,7 @@
|
|||||||
</adf-pagination>
|
</adf-pagination>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="inner-layout__side-panel" *ngIf="infoDrawerOpened">
|
<div class="inner-layout__side-panel" *ngIf="infoDrawerOpened$ | async">
|
||||||
<aca-info-drawer [node]="selection.last"></aca-info-drawer>
|
<aca-info-drawer [node]="selection.last"></aca-info-drawer>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,75 +1,39 @@
|
|||||||
<div class="sidenav">
|
<div class="sidenav">
|
||||||
<div class="sidenav__section sidenav__section sidenav_action-menu">
|
<div class="sidenav__section sidenav__section sidenav_action-menu">
|
||||||
<adf-sidebar-action-menu [expanded]="showLabel" [attr.title]="'APP.NEW_MENU.TOOLTIP' | translate" title="{{'APP.NEW_MENU.LABEL' | translate }}">
|
<adf-sidebar-action-menu
|
||||||
|
[expanded]="showLabel"
|
||||||
|
[attr.title]="'APP.NEW_MENU.TOOLTIP' | translate"
|
||||||
|
[title]="'APP.NEW_MENU.LABEL' | translate">
|
||||||
<mat-icon sidebar-menu-title-icon>arrow_drop_down</mat-icon>
|
<mat-icon sidebar-menu-title-icon>arrow_drop_down</mat-icon>
|
||||||
<div sidebar-menu-expand-icon>
|
<div sidebar-menu-expand-icon>
|
||||||
<mat-icon [title]="'APP.NEW_MENU.TOOLTIP' | translate">queue</mat-icon>
|
<mat-icon [title]="'APP.NEW_MENU.TOOLTIP' | translate">queue</mat-icon>
|
||||||
</div>
|
</div>
|
||||||
<div sidebar-menu-options>
|
<div sidebar-menu-options>
|
||||||
<ng-container *ifExperimental="'extensions'">
|
<ng-container *ngFor="let action of createActions; trackBy: trackById">
|
||||||
<button *ngFor="let entry of createActions"
|
<app-toolbar-button
|
||||||
mat-menu-item
|
type="menu-item"
|
||||||
[disabled]="entry.disabled"
|
[actionRef]="action"></app-toolbar-button>
|
||||||
(click)="runAction(entry.actions.click)">
|
|
||||||
<mat-icon>{{ entry.icon }}</mat-icon>
|
|
||||||
<span>{{ entry.title | translate }}</span>
|
|
||||||
</button>
|
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<button
|
|
||||||
mat-menu-item
|
|
||||||
[disabled]="!canCreateContent"
|
|
||||||
(click)="createNewFolder()"
|
|
||||||
[attr.title]="
|
|
||||||
( canCreateContent
|
|
||||||
? 'APP.NEW_MENU.TOOLTIPS.CREATE_FOLDER'
|
|
||||||
: 'APP.NEW_MENU.TOOLTIPS.CREATE_FOLDER_NOT_ALLOWED'
|
|
||||||
) | translate">
|
|
||||||
<mat-icon>create_new_folder</mat-icon>
|
|
||||||
<span>{{ 'APP.NEW_MENU.MENU_ITEMS.CREATE_FOLDER' | translate }}</span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<adf-upload-button
|
|
||||||
[tooltip]="
|
|
||||||
(canCreateContent
|
|
||||||
? 'APP.NEW_MENU.TOOLTIPS.UPLOAD_FILES'
|
|
||||||
: 'APP.NEW_MENU.TOOLTIPS.UPLOAD_FILES_NOT_ALLOWED'
|
|
||||||
) | translate"
|
|
||||||
[disabled]="!canCreateContent"
|
|
||||||
[rootFolderId]="node?.id"
|
|
||||||
[multipleFiles]="true"
|
|
||||||
[uploadFolders]="false"
|
|
||||||
[staticTitle]="'APP.NEW_MENU.MENU_ITEMS.UPLOAD_FILE' | translate">
|
|
||||||
</adf-upload-button>
|
|
||||||
|
|
||||||
<adf-upload-button
|
|
||||||
[tooltip]="
|
|
||||||
(canCreateContent
|
|
||||||
? 'APP.NEW_MENU.TOOLTIPS.UPLOAD_FOLDERS'
|
|
||||||
: 'APP.NEW_MENU.TOOLTIPS.UPLOAD_FOLDERS_NOT_ALLOWED'
|
|
||||||
) | translate"
|
|
||||||
[disabled]="!canCreateContent"
|
|
||||||
[rootFolderId]="node?.id"
|
|
||||||
[multipleFiles]="true"
|
|
||||||
[uploadFolders]="true"
|
|
||||||
[staticTitle]="'APP.NEW_MENU.MENU_ITEMS.UPLOAD_FOLDER' | translate">
|
|
||||||
</adf-upload-button>
|
|
||||||
</div>
|
</div>
|
||||||
</adf-sidebar-action-menu>
|
</adf-sidebar-action-menu>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="sidenav__section sidenav__section--menu" *ngFor="let group of groups">
|
<div *ngFor="let group of groups; trackBy: trackById"
|
||||||
|
class="sidenav__section sidenav__section--menu" >
|
||||||
<ul class="sidenav-menu">
|
<ul class="sidenav-menu">
|
||||||
<li *ngFor="let item of group.items" class="sidenav-menu__item"
|
<li *ngFor="let item of group.items; trackBy: trackById"
|
||||||
|
class="sidenav-menu__item"
|
||||||
routerLinkActive
|
routerLinkActive
|
||||||
#rla="routerLinkActive"
|
#rla="routerLinkActive"
|
||||||
title="{{ item.description | translate }}">
|
[attr.title]="item.description | translate">
|
||||||
|
|
||||||
<button
|
<button
|
||||||
|
[id]="item.id"
|
||||||
|
mat-icon-button
|
||||||
|
mat-ripple
|
||||||
[routerLink]="item.url"
|
[routerLink]="item.url"
|
||||||
[color]="rla.isActive ? 'accent': 'primary'"
|
[color]="rla.isActive ? 'accent': 'primary'"
|
||||||
[attr.aria-label]="item.title | translate"
|
[attr.aria-label]="item.title | translate"
|
||||||
mat-icon-button
|
|
||||||
mat-ripple
|
|
||||||
matRippleColor="primary"
|
matRippleColor="primary"
|
||||||
[matRippleTrigger]="rippleTrigger"
|
[matRippleTrigger]="rippleTrigger"
|
||||||
[matRippleCentered]="true">
|
[matRippleCentered]="true">
|
||||||
|
@ -25,12 +25,9 @@
|
|||||||
|
|
||||||
import { Subject } from 'rxjs/Rx';
|
import { Subject } from 'rxjs/Rx';
|
||||||
import { Component, Input, OnInit, OnDestroy } from '@angular/core';
|
import { Component, Input, OnInit, OnDestroy } from '@angular/core';
|
||||||
import { Node } from 'alfresco-js-api';
|
|
||||||
import { NodePermissionService } from '../../services/node-permission.service';
|
|
||||||
import { ExtensionService } from '../../extensions/extension.service';
|
import { ExtensionService } from '../../extensions/extension.service';
|
||||||
import { Store } from '@ngrx/store';
|
import { Store } from '@ngrx/store';
|
||||||
import { AppStore } from '../../store/states';
|
import { AppStore } from '../../store/states';
|
||||||
import { CreateFolderAction } from '../../store/actions';
|
|
||||||
import { currentFolder } from '../../store/selectors/app.selectors';
|
import { currentFolder } from '../../store/selectors/app.selectors';
|
||||||
import { takeUntil } from 'rxjs/operators';
|
import { takeUntil } from 'rxjs/operators';
|
||||||
import { NavBarGroupRef } from '../../extensions/navbar.extensions';
|
import { NavBarGroupRef } from '../../extensions/navbar.extensions';
|
||||||
@ -44,28 +41,23 @@ import { ContentActionRef } from '../../extensions/action.extensions';
|
|||||||
export class SidenavComponent implements OnInit, OnDestroy {
|
export class SidenavComponent implements OnInit, OnDestroy {
|
||||||
@Input() showLabel: boolean;
|
@Input() showLabel: boolean;
|
||||||
|
|
||||||
node: Node = null;
|
|
||||||
groups: Array<NavBarGroupRef> = [];
|
groups: Array<NavBarGroupRef> = [];
|
||||||
createActions: Array<ContentActionRef> = [];
|
createActions: Array<ContentActionRef> = [];
|
||||||
canCreateContent = false;
|
|
||||||
onDestroy$: Subject<boolean> = new Subject<boolean>();
|
onDestroy$: Subject<boolean> = new Subject<boolean>();
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private store: Store<AppStore>,
|
private store: Store<AppStore>,
|
||||||
private permission: NodePermissionService,
|
|
||||||
private extensions: ExtensionService
|
private extensions: ExtensionService
|
||||||
) {
|
) {}
|
||||||
}
|
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.groups = this.extensions.getNavigationGroups();
|
this.groups = this.extensions.getNavigationGroups();
|
||||||
|
|
||||||
this.store.select(currentFolder)
|
this.store
|
||||||
|
.select(currentFolder)
|
||||||
.pipe(takeUntil(this.onDestroy$))
|
.pipe(takeUntil(this.onDestroy$))
|
||||||
.subscribe(node => {
|
.subscribe(() => {
|
||||||
this.node = node;
|
|
||||||
this.createActions = this.extensions.getCreateActions();
|
this.createActions = this.extensions.getCreateActions();
|
||||||
this.canCreateContent = node && this.permission.check(node, ['create']);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -74,15 +66,7 @@ export class SidenavComponent implements OnInit, OnDestroy {
|
|||||||
this.onDestroy$.complete();
|
this.onDestroy$.complete();
|
||||||
}
|
}
|
||||||
|
|
||||||
createNewFolder() {
|
trackById(index: number, obj: { id: string }) {
|
||||||
if (this.node && this.node.id) {
|
return obj.id;
|
||||||
this.store.dispatch(new CreateFolderAction(this.node.id));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// this is where each application decides how to treat an action and what to do
|
|
||||||
// the ACA maps actions to the NgRx actions as an example
|
|
||||||
runAction(actionId: string) {
|
|
||||||
this.extensions.runActionById(actionId);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,56 @@
|
|||||||
|
/*!
|
||||||
|
* @license
|
||||||
|
* Alfresco Example Content Application
|
||||||
|
*
|
||||||
|
* Copyright (C) 2005 - 2018 Alfresco Software Limited
|
||||||
|
*
|
||||||
|
* This file is part of the Alfresco Example Content Application.
|
||||||
|
* If the software was purchased under a paid Alfresco license, the terms of
|
||||||
|
* the paid license agreement will prevail. Otherwise, the software is
|
||||||
|
* provided under the following open source license terms:
|
||||||
|
*
|
||||||
|
* The Alfresco Example Content Application is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* The Alfresco Example Content Application is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public License
|
||||||
|
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { Observable } from 'rxjs/Rx';
|
||||||
|
import { Store } from '@ngrx/store';
|
||||||
|
import { AppStore } from '../../../store/states';
|
||||||
|
import { documentDisplayMode } from '../../../store/selectors/app.selectors';
|
||||||
|
import { ToggleDocumentDisplayMode } from '../../../store/actions';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-document-display-mode',
|
||||||
|
template: `
|
||||||
|
<button
|
||||||
|
mat-icon-button
|
||||||
|
color="primary"
|
||||||
|
(click)="onClick()">
|
||||||
|
<mat-icon *ngIf="(displayMode$ | async) === 'list'">view_comfy</mat-icon>
|
||||||
|
<mat-icon *ngIf="(displayMode$ | async) === 'gallery'">list</mat-icon>
|
||||||
|
</button>
|
||||||
|
`
|
||||||
|
})
|
||||||
|
export class DocumentDisplayModeComponent {
|
||||||
|
|
||||||
|
displayMode$: Observable<string>;
|
||||||
|
|
||||||
|
constructor(private store: Store<AppStore>) {
|
||||||
|
this.displayMode$ = store.select(documentDisplayMode);
|
||||||
|
}
|
||||||
|
|
||||||
|
onClick() {
|
||||||
|
this.store.dispatch(new ToggleDocumentDisplayMode());
|
||||||
|
}
|
||||||
|
}
|
@ -23,25 +23,30 @@
|
|||||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Directive, Input, HostListener } from '@angular/core';
|
import { Component } from '@angular/core';
|
||||||
import { MinimalNodeEntity } from 'alfresco-js-api';
|
|
||||||
import { Store } from '@ngrx/store';
|
import { Store } from '@ngrx/store';
|
||||||
import { AppStore } from '../store/states';
|
import { AppStore, SelectionState } from '../../../store/states';
|
||||||
import { EditFolderAction } from '../store/actions';
|
import { appSelection } from '../../../store/selectors/app.selectors';
|
||||||
|
import { Observable } from 'rxjs/Observable';
|
||||||
|
|
||||||
@Directive({
|
@Component({
|
||||||
selector: '[acaEditFolder]'
|
selector: 'app-toggle-favorite',
|
||||||
|
template: `
|
||||||
|
<button
|
||||||
|
mat-menu-item
|
||||||
|
#favorites="adfFavorite"
|
||||||
|
[adf-node-favorite]="(selection$ | async).nodes">
|
||||||
|
<mat-icon *ngIf="favorites.hasFavorites()">star</mat-icon>
|
||||||
|
<mat-icon *ngIf="!favorites.hasFavorites()">star_border</mat-icon>
|
||||||
|
<span>{{ 'APP.ACTIONS.FAVORITE' | translate }}</span>
|
||||||
|
</button>
|
||||||
|
`
|
||||||
})
|
})
|
||||||
export class EditFolderDirective {
|
export class ToggleFavoriteComponent {
|
||||||
/** Folder node to edit. */
|
|
||||||
// tslint:disable-next-line:no-input-rename
|
|
||||||
@Input('acaEditFolder') folder: MinimalNodeEntity;
|
|
||||||
|
|
||||||
@HostListener('click', ['$event'])
|
selection$: Observable<SelectionState>;
|
||||||
onClick(event) {
|
|
||||||
event.preventDefault();
|
|
||||||
this.store.dispatch(new EditFolderAction(this.folder));
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(private store: Store<AppStore>) {}
|
constructor(private store: Store<AppStore>) {
|
||||||
|
this.selection$ = this.store.select(appSelection);
|
||||||
|
}
|
||||||
}
|
}
|
@ -0,0 +1,55 @@
|
|||||||
|
/*!
|
||||||
|
* @license
|
||||||
|
* Alfresco Example Content Application
|
||||||
|
*
|
||||||
|
* Copyright (C) 2005 - 2018 Alfresco Software Limited
|
||||||
|
*
|
||||||
|
* This file is part of the Alfresco Example Content Application.
|
||||||
|
* If the software was purchased under a paid Alfresco license, the terms of
|
||||||
|
* the paid license agreement will prevail. Otherwise, the software is
|
||||||
|
* provided under the following open source license terms:
|
||||||
|
*
|
||||||
|
* The Alfresco Example Content Application is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* The Alfresco Example Content Application is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public License
|
||||||
|
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { Observable } from 'rxjs/Rx';
|
||||||
|
import { Store } from '@ngrx/store';
|
||||||
|
import { AppStore } from '../../../store/states';
|
||||||
|
import { infoDrawerOpened } from '../../../store/selectors/app.selectors';
|
||||||
|
import { ToggleInfoDrawerAction } from '../../../store/actions';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-toggle-info-drawer',
|
||||||
|
template: `
|
||||||
|
<button
|
||||||
|
mat-icon-button
|
||||||
|
[color]="(infoDrawerOpened$ | async) ? 'accent' : 'primary'"
|
||||||
|
[attr.title]="'APP.ACTIONS.DETAILS' | translate"
|
||||||
|
(click)="onClick()">
|
||||||
|
<mat-icon>info_outline</mat-icon>
|
||||||
|
</button>
|
||||||
|
`
|
||||||
|
})
|
||||||
|
export class ToggleInfoDrawerComponent {
|
||||||
|
infoDrawerOpened$: Observable<boolean>;
|
||||||
|
|
||||||
|
constructor(private store: Store<AppStore>) {
|
||||||
|
this.infoDrawerOpened$ = this.store.select(infoDrawerOpened);
|
||||||
|
}
|
||||||
|
|
||||||
|
onClick() {
|
||||||
|
this.store.dispatch(new ToggleInfoDrawerAction());
|
||||||
|
}
|
||||||
|
}
|
@ -4,43 +4,18 @@
|
|||||||
</adf-breadcrumb>
|
</adf-breadcrumb>
|
||||||
|
|
||||||
<adf-toolbar class="inline">
|
<adf-toolbar class="inline">
|
||||||
<button *ifExperimental="'cardview'"
|
<app-document-display-mode *ifExperimental="'cardview'"></app-document-display-mode>
|
||||||
color="primary"
|
|
||||||
mat-icon-button
|
|
||||||
(click)="toggleGalleryView()">
|
|
||||||
<mat-icon *ngIf="displayMode === 'list'" matTooltip="{{ 'APP.DOCUMENT_LIST.TOOLBAR.CARDVIEW' | translate }}">view_comfy</mat-icon>
|
|
||||||
<mat-icon *ngIf="displayMode === 'gallery'" matTooltip="{{ 'APP.DOCUMENT_LIST.TOOLBAR.LISTVIEW' | translate }}">list</mat-icon>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<ng-container *ifExperimental="'extensions'">
|
<ng-container *ngFor="let entry of actions; trackBy: trackByActionId">
|
||||||
<ng-container *ngFor="let entry of actions">
|
|
||||||
<aca-toolbar-action [entry]="entry"></aca-toolbar-action>
|
<aca-toolbar-action [entry]="entry"></aca-toolbar-action>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</ng-container>
|
|
||||||
|
|
||||||
<ng-container *ngIf="!selection.isEmpty">
|
|
||||||
<button
|
|
||||||
color="primary"
|
|
||||||
mat-icon-button
|
|
||||||
[acaPermanentDelete]="selection.nodes"
|
|
||||||
title="{{ 'APP.ACTIONS.DELETE_PERMANENT' | translate }}">
|
|
||||||
<mat-icon>delete_forever</mat-icon>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button
|
|
||||||
color="primary"
|
|
||||||
mat-icon-button
|
|
||||||
[acaRestoreNode]="selection.nodes"
|
|
||||||
title="{{ 'APP.ACTIONS.RESTORE' | translate }}">
|
|
||||||
<mat-icon>restore</mat-icon>
|
|
||||||
</button>
|
|
||||||
</ng-container>
|
|
||||||
</adf-toolbar>
|
</adf-toolbar>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="inner-layout__content">
|
<div class="inner-layout__content">
|
||||||
<div class="inner-layout__panel">
|
<div class="inner-layout__panel">
|
||||||
<adf-document-list acaDocumentList #documentList
|
<adf-document-list acaDocumentList #documentList
|
||||||
|
[display]="documentDisplayMode$ | async"
|
||||||
currentFolderId="-trashcan-"
|
currentFolderId="-trashcan-"
|
||||||
selectionMode="multiple"
|
selectionMode="multiple"
|
||||||
[navigate]="false"
|
[navigate]="false"
|
||||||
|
@ -26,44 +26,17 @@
|
|||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
import { ExperimentalDirective } from './experimental.directive';
|
import { ExperimentalDirective } from './experimental.directive';
|
||||||
import { DocumentListDirective } from './document-list.directive';
|
import { DocumentListDirective } from './document-list.directive';
|
||||||
import { EditFolderDirective } from './edit-folder.directive';
|
|
||||||
import { NodeCopyDirective } from './node-copy.directive';
|
|
||||||
import { NodeDeleteDirective } from './node-delete.directive';
|
|
||||||
import { NodeMoveDirective } from './node-move.directive';
|
|
||||||
import { NodePermanentDeleteDirective } from './node-permanent-delete.directive';
|
|
||||||
import { NodePermissionsDirective } from './node-permissions.directive';
|
|
||||||
import { NodeRestoreDirective } from './node-restore.directive';
|
|
||||||
import { NodeUnshareDirective } from './node-unshare.directive';
|
|
||||||
import { NodeVersionsDirective } from './node-versions.directive';
|
|
||||||
import { PaginationDirective } from './pagination.directive';
|
import { PaginationDirective } from './pagination.directive';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [
|
declarations: [
|
||||||
ExperimentalDirective,
|
ExperimentalDirective,
|
||||||
DocumentListDirective,
|
DocumentListDirective,
|
||||||
EditFolderDirective,
|
|
||||||
NodeCopyDirective,
|
|
||||||
NodeDeleteDirective,
|
|
||||||
NodeMoveDirective,
|
|
||||||
NodePermanentDeleteDirective,
|
|
||||||
NodePermissionsDirective,
|
|
||||||
NodeRestoreDirective,
|
|
||||||
NodeUnshareDirective,
|
|
||||||
NodeVersionsDirective,
|
|
||||||
PaginationDirective
|
PaginationDirective
|
||||||
],
|
],
|
||||||
exports: [
|
exports: [
|
||||||
ExperimentalDirective,
|
ExperimentalDirective,
|
||||||
DocumentListDirective,
|
DocumentListDirective,
|
||||||
EditFolderDirective,
|
|
||||||
NodeCopyDirective,
|
|
||||||
NodeDeleteDirective,
|
|
||||||
NodeMoveDirective,
|
|
||||||
NodePermanentDeleteDirective,
|
|
||||||
NodePermissionsDirective,
|
|
||||||
NodeRestoreDirective,
|
|
||||||
NodeUnshareDirective,
|
|
||||||
NodeVersionsDirective,
|
|
||||||
PaginationDirective
|
PaginationDirective
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
@ -38,6 +38,7 @@ import { MinimalNodeEntryEntity } from 'alfresco-js-api';
|
|||||||
})
|
})
|
||||||
export class DocumentListDirective implements OnInit, OnDestroy {
|
export class DocumentListDirective implements OnInit, OnDestroy {
|
||||||
private subscriptions: Subscription[] = [];
|
private subscriptions: Subscription[] = [];
|
||||||
|
private isLibrary = false;
|
||||||
|
|
||||||
get sortingPreferenceKey(): string {
|
get sortingPreferenceKey(): string {
|
||||||
return this.route.snapshot.data.sortingPreferenceKey;
|
return this.route.snapshot.data.sortingPreferenceKey;
|
||||||
@ -53,6 +54,7 @@ export class DocumentListDirective implements OnInit, OnDestroy {
|
|||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.documentList.includeFields = ['isFavorite', 'aspectNames'];
|
this.documentList.includeFields = ['isFavorite', 'aspectNames'];
|
||||||
this.documentList.allowDropFiles = false;
|
this.documentList.allowDropFiles = false;
|
||||||
|
this.isLibrary = this.documentList.currentFolderId === '-mysites-';
|
||||||
|
|
||||||
if (this.sortingPreferenceKey) {
|
if (this.sortingPreferenceKey) {
|
||||||
const current = this.documentList.sorting;
|
const current = this.documentList.sorting;
|
||||||
@ -103,22 +105,27 @@ export class DocumentListDirective implements OnInit, OnDestroy {
|
|||||||
this.unSelectLockedNodes(this.documentList);
|
this.unSelectLockedNodes(this.documentList);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.store.dispatch(
|
this.updateSelection();
|
||||||
new SetSelectedNodesAction(this.documentList.selection)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@HostListener('node-unselect')
|
@HostListener('node-unselect')
|
||||||
onNodeUnselect() {
|
onNodeUnselect() {
|
||||||
this.store.dispatch(
|
this.updateSelection();
|
||||||
new SetSelectedNodesAction(this.documentList.selection)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onReady() {
|
onReady() {
|
||||||
|
this.updateSelection();
|
||||||
|
}
|
||||||
|
|
||||||
|
private updateSelection() {
|
||||||
|
const selection = this.documentList.selection.map(entry => {
|
||||||
|
entry['isLibrary'] = this.isLibrary;
|
||||||
|
return entry;
|
||||||
|
});
|
||||||
|
|
||||||
this.store.dispatch(
|
this.store.dispatch(
|
||||||
new SetSelectedNodesAction(this.documentList.selection)
|
new SetSelectedNodesAction(selection)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,307 +0,0 @@
|
|||||||
/*!
|
|
||||||
* @license
|
|
||||||
* Alfresco Example Content Application
|
|
||||||
*
|
|
||||||
* Copyright (C) 2005 - 2018 Alfresco Software Limited
|
|
||||||
*
|
|
||||||
* This file is part of the Alfresco Example Content Application.
|
|
||||||
* If the software was purchased under a paid Alfresco license, the terms of
|
|
||||||
* the paid license agreement will prevail. Otherwise, the software is
|
|
||||||
* provided under the following open source license terms:
|
|
||||||
*
|
|
||||||
* The Alfresco Example Content Application is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU Lesser General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* The Alfresco Example Content Application is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU Lesser General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU Lesser General Public License
|
|
||||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { Component, DebugElement } from '@angular/core';
|
|
||||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
|
||||||
import { By } from '@angular/platform-browser';
|
|
||||||
import { Observable } from 'rxjs/Rx';
|
|
||||||
import { MatSnackBar } from '@angular/material';
|
|
||||||
import { NodeActionsService } from '../services/node-actions.service';
|
|
||||||
import { NodeCopyDirective } from './node-copy.directive';
|
|
||||||
import { ContentApiService } from '../services/content-api.service';
|
|
||||||
import { AppTestingModule } from '../testing/app-testing.module';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
template: '<div [acaCopyNode]="selection"></div>'
|
|
||||||
})
|
|
||||||
class TestComponent {
|
|
||||||
selection;
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('NodeCopyDirective', () => {
|
|
||||||
let fixture: ComponentFixture<TestComponent>;
|
|
||||||
let component: TestComponent;
|
|
||||||
let element: DebugElement;
|
|
||||||
let snackBar: MatSnackBar;
|
|
||||||
let service: NodeActionsService;
|
|
||||||
let contentApi: ContentApiService;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
TestBed.configureTestingModule({
|
|
||||||
imports: [ AppTestingModule ],
|
|
||||||
declarations: [
|
|
||||||
TestComponent,
|
|
||||||
NodeCopyDirective
|
|
||||||
]
|
|
||||||
});
|
|
||||||
|
|
||||||
contentApi = TestBed.get(ContentApiService);
|
|
||||||
|
|
||||||
fixture = TestBed.createComponent(TestComponent);
|
|
||||||
component = fixture.componentInstance;
|
|
||||||
element = fixture.debugElement.query(By.directive(NodeCopyDirective));
|
|
||||||
snackBar = TestBed.get(MatSnackBar);
|
|
||||||
service = TestBed.get(NodeActionsService);
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('Copy node action', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
spyOn(snackBar, 'open').and.callThrough();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('notifies successful copy of a node', () => {
|
|
||||||
spyOn(service, 'copyNodes').and.returnValue(Observable.of('OPERATION.SUCCES.CONTENT.COPY'));
|
|
||||||
|
|
||||||
component.selection = [{ entry: { id: 'node-to-copy-id', name: 'name' } }];
|
|
||||||
const createdItems = [{ entry: { id: 'copy-id', name: 'name' } }];
|
|
||||||
|
|
||||||
fixture.detectChanges();
|
|
||||||
element.triggerEventHandler('click', null);
|
|
||||||
service.contentCopied.next(<any>createdItems);
|
|
||||||
|
|
||||||
expect(service.copyNodes).toHaveBeenCalled();
|
|
||||||
expect(snackBar.open['calls'].argsFor(0)[0]).toBe('APP.MESSAGES.INFO.NODE_COPY.SINGULAR');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('notifies successful copy of multiple nodes', () => {
|
|
||||||
spyOn(service, 'copyNodes').and.returnValue(Observable.of('OPERATION.SUCCES.CONTENT.COPY'));
|
|
||||||
|
|
||||||
component.selection = [
|
|
||||||
{ entry: { id: 'node-to-copy-1', name: 'name1' } },
|
|
||||||
{ entry: { id: 'node-to-copy-2', name: 'name2' } }];
|
|
||||||
const createdItems = [
|
|
||||||
{ entry: { id: 'copy-of-node-1', name: 'name1' } },
|
|
||||||
{ entry: { id: 'copy-of-node-2', name: 'name2' } }];
|
|
||||||
|
|
||||||
fixture.detectChanges();
|
|
||||||
element.triggerEventHandler('click', null);
|
|
||||||
service.contentCopied.next(<any>createdItems);
|
|
||||||
|
|
||||||
expect(service.copyNodes).toHaveBeenCalled();
|
|
||||||
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', () => {
|
|
||||||
spyOn(service, 'copyNodes').and.returnValue(Observable.of('OPERATION.SUCCES.CONTENT.COPY'));
|
|
||||||
|
|
||||||
component.selection = [
|
|
||||||
{ entry: { id: 'node-to-copy-1', name: 'name1' } },
|
|
||||||
{ entry: { id: 'node-to-copy-2', name: 'name2' } }];
|
|
||||||
const createdItems = [
|
|
||||||
{ entry: { id: 'copy-of-node-1', name: 'name1' } }];
|
|
||||||
|
|
||||||
fixture.detectChanges();
|
|
||||||
element.triggerEventHandler('click', null);
|
|
||||||
service.contentCopied.next(<any>createdItems);
|
|
||||||
|
|
||||||
expect(service.copyNodes).toHaveBeenCalled();
|
|
||||||
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', () => {
|
|
||||||
spyOn(service, 'copyNodes').and.returnValue(Observable.of('OPERATION.SUCCES.CONTENT.COPY'));
|
|
||||||
|
|
||||||
component.selection = [
|
|
||||||
{ entry: { id: 'node-to-copy-0', name: 'name0' } },
|
|
||||||
{ entry: { id: 'node-to-copy-1', name: 'name1' } },
|
|
||||||
{ entry: { id: 'node-to-copy-2', name: 'name2' } }];
|
|
||||||
const createdItems = [
|
|
||||||
{ entry: { id: 'copy-of-node-0', name: 'name0' } },
|
|
||||||
{ entry: { id: 'copy-of-node-1', name: 'name1' } }];
|
|
||||||
|
|
||||||
fixture.detectChanges();
|
|
||||||
element.triggerEventHandler('click', null);
|
|
||||||
service.contentCopied.next(<any>createdItems);
|
|
||||||
|
|
||||||
expect(service.copyNodes).toHaveBeenCalled();
|
|
||||||
expect(snackBar.open['calls'].argsFor(0)[0]).toBe('APP.MESSAGES.INFO.NODE_COPY.PARTIAL_PLURAL');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('notifies of failed copy of multiple nodes', () => {
|
|
||||||
spyOn(service, 'copyNodes').and.returnValue(Observable.of('OPERATION.SUCCES.CONTENT.COPY'));
|
|
||||||
|
|
||||||
component.selection = [
|
|
||||||
{ entry: { id: 'node-to-copy-0', name: 'name0' } },
|
|
||||||
{ entry: { id: 'node-to-copy-1', name: 'name1' } },
|
|
||||||
{ entry: { id: 'node-to-copy-2', name: 'name2' } }];
|
|
||||||
const createdItems = [];
|
|
||||||
|
|
||||||
fixture.detectChanges();
|
|
||||||
element.triggerEventHandler('click', null);
|
|
||||||
service.contentCopied.next(<any>createdItems);
|
|
||||||
|
|
||||||
expect(service.copyNodes).toHaveBeenCalled();
|
|
||||||
expect(snackBar.open['calls'].argsFor(0)[0]).toBe('APP.MESSAGES.INFO.NODE_COPY.FAIL_PLURAL');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('notifies of failed copy of one node', () => {
|
|
||||||
spyOn(service, 'copyNodes').and.returnValue(Observable.of('OPERATION.SUCCES.CONTENT.COPY'));
|
|
||||||
|
|
||||||
component.selection = [
|
|
||||||
{ entry: { id: 'node-to-copy', name: 'name' } }];
|
|
||||||
const createdItems = [];
|
|
||||||
|
|
||||||
fixture.detectChanges();
|
|
||||||
element.triggerEventHandler('click', null);
|
|
||||||
service.contentCopied.next(<any>createdItems);
|
|
||||||
|
|
||||||
expect(service.copyNodes).toHaveBeenCalled();
|
|
||||||
expect(snackBar.open['calls'].argsFor(0)[0]).toBe('APP.MESSAGES.INFO.NODE_COPY.FAIL_SINGULAR');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('notifies error if success message was not emitted', () => {
|
|
||||||
spyOn(service, 'copyNodes').and.returnValue(Observable.of(''));
|
|
||||||
|
|
||||||
component.selection = [{ entry: { id: 'node-to-copy-id', name: 'name' } }];
|
|
||||||
|
|
||||||
fixture.detectChanges();
|
|
||||||
element.triggerEventHandler('click', null);
|
|
||||||
service.contentCopied.next();
|
|
||||||
|
|
||||||
expect(service.copyNodes).toHaveBeenCalled();
|
|
||||||
expect(snackBar.open['calls'].argsFor(0)[0]).toBe('APP.MESSAGES.ERRORS.GENERIC');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('notifies permission error on copy of node', () => {
|
|
||||||
spyOn(service, 'copyNodes').and.returnValue(Observable.throw(new Error(JSON.stringify({error: {statusCode: 403}}))));
|
|
||||||
|
|
||||||
component.selection = [{ entry: { id: '1', name: 'name' } }];
|
|
||||||
|
|
||||||
fixture.detectChanges();
|
|
||||||
element.triggerEventHandler('click', null);
|
|
||||||
|
|
||||||
expect(service.copyNodes).toHaveBeenCalled();
|
|
||||||
expect(snackBar.open['calls'].argsFor(0)[0]).toBe('APP.MESSAGES.ERRORS.PERMISSION');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('notifies generic error message on all errors, but 403', () => {
|
|
||||||
spyOn(service, 'copyNodes').and.returnValue(Observable.throw(new Error(JSON.stringify({error: {statusCode: 404}}))));
|
|
||||||
|
|
||||||
component.selection = [{ entry: { id: '1', name: 'name' } }];
|
|
||||||
|
|
||||||
fixture.detectChanges();
|
|
||||||
element.triggerEventHandler('click', null);
|
|
||||||
|
|
||||||
expect(service.copyNodes).toHaveBeenCalled();
|
|
||||||
expect(snackBar.open['calls'].argsFor(0)[0]).toBe('APP.MESSAGES.ERRORS.GENERIC');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('Undo Copy action', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
spyOn(service, 'copyNodes').and.returnValue(Observable.of('OPERATION.SUCCES.CONTENT.COPY'));
|
|
||||||
|
|
||||||
spyOn(snackBar, 'open').and.returnValue({
|
|
||||||
onAction: () => Observable.of({})
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should delete the newly created node on Undo action', () => {
|
|
||||||
spyOn(contentApi, 'deleteNode').and.returnValue(Observable.of(null));
|
|
||||||
|
|
||||||
component.selection = [{ entry: { id: 'node-to-copy-id', name: 'name' } }];
|
|
||||||
const createdItems = [{ entry: { id: 'copy-id', name: 'name' } }];
|
|
||||||
|
|
||||||
fixture.detectChanges();
|
|
||||||
element.triggerEventHandler('click', null);
|
|
||||||
service.contentCopied.next(<any>createdItems);
|
|
||||||
|
|
||||||
expect(service.copyNodes).toHaveBeenCalled();
|
|
||||||
expect(snackBar.open['calls'].argsFor(0)[0]).toBe('APP.MESSAGES.INFO.NODE_COPY.SINGULAR');
|
|
||||||
|
|
||||||
expect(contentApi.deleteNode).toHaveBeenCalledWith(createdItems[0].entry.id, { permanent: true });
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should delete also the node created inside an already existing folder from destination', () => {
|
|
||||||
const spyOnDeleteNode = spyOn(contentApi, 'deleteNode').and.returnValue(Observable.of(null));
|
|
||||||
|
|
||||||
component.selection = [
|
|
||||||
{ entry: { id: 'node-to-copy-1', name: 'name1' } },
|
|
||||||
{ entry: { id: 'node-to-copy-2', name: 'folder-with-name-already-existing-on-destination' } }];
|
|
||||||
const id1 = 'copy-of-node-1';
|
|
||||||
const id2 = 'copy-of-child-of-node-2';
|
|
||||||
const createdItems = [
|
|
||||||
{ entry: { id: id1, name: 'name1' } },
|
|
||||||
[ { entry: { id: id2, name: 'name-of-child-of-node-2' , parentId: 'the-folder-already-on-destination' } }] ];
|
|
||||||
|
|
||||||
fixture.detectChanges();
|
|
||||||
element.triggerEventHandler('click', null);
|
|
||||||
service.contentCopied.next(<any>createdItems);
|
|
||||||
|
|
||||||
expect(service.copyNodes).toHaveBeenCalled();
|
|
||||||
expect(snackBar.open['calls'].argsFor(0)[0]).toBe('APP.MESSAGES.INFO.NODE_COPY.PLURAL');
|
|
||||||
|
|
||||||
expect(spyOnDeleteNode).toHaveBeenCalled();
|
|
||||||
expect(spyOnDeleteNode.calls.allArgs())
|
|
||||||
.toEqual([[id1, { permanent: true }], [id2, { permanent: true }]]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('notifies when error occurs on Undo action', () => {
|
|
||||||
spyOn(contentApi, 'deleteNode').and.returnValue(Observable.throw(null));
|
|
||||||
|
|
||||||
component.selection = [{ entry: { id: 'node-to-copy-id', name: 'name' } }];
|
|
||||||
const createdItems = [{ entry: { id: 'copy-id', name: 'name' } }];
|
|
||||||
|
|
||||||
fixture.detectChanges();
|
|
||||||
element.triggerEventHandler('click', null);
|
|
||||||
service.contentCopied.next(<any>createdItems);
|
|
||||||
|
|
||||||
expect(service.copyNodes).toHaveBeenCalled();
|
|
||||||
expect(contentApi.deleteNode).toHaveBeenCalled();
|
|
||||||
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', () => {
|
|
||||||
spyOn(contentApi, 'deleteNode').and.returnValue(Observable.throw(new Error('oops!')));
|
|
||||||
|
|
||||||
component.selection = [{ entry: { id: 'node-to-copy-id', name: 'name' } }];
|
|
||||||
const createdItems = [{ entry: { id: 'copy-id', name: 'name' } }];
|
|
||||||
|
|
||||||
fixture.detectChanges();
|
|
||||||
element.triggerEventHandler('click', null);
|
|
||||||
service.contentCopied.next(<any>createdItems);
|
|
||||||
|
|
||||||
expect(service.copyNodes).toHaveBeenCalled();
|
|
||||||
expect(contentApi.deleteNode).toHaveBeenCalled();
|
|
||||||
expect(snackBar.open['calls'].argsFor(0)[0]).toEqual('APP.MESSAGES.INFO.NODE_COPY.SINGULAR');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('notifies permission error when it occurs on Undo action', () => {
|
|
||||||
spyOn(contentApi, 'deleteNode').and.returnValue(Observable.throw(new Error(JSON.stringify({error: {statusCode: 403}}))));
|
|
||||||
|
|
||||||
component.selection = [{ entry: { id: 'node-to-copy-id', name: 'name' } }];
|
|
||||||
const createdItems = [{ entry: { id: 'copy-id', name: 'name' } }];
|
|
||||||
|
|
||||||
fixture.detectChanges();
|
|
||||||
element.triggerEventHandler('click', null);
|
|
||||||
service.contentCopied.next(<any>createdItems);
|
|
||||||
|
|
||||||
expect(service.copyNodes).toHaveBeenCalled();
|
|
||||||
expect(contentApi.deleteNode).toHaveBeenCalled();
|
|
||||||
expect(snackBar.open['calls'].argsFor(0)[0]).toEqual('APP.MESSAGES.INFO.NODE_COPY.SINGULAR');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
@ -1,155 +0,0 @@
|
|||||||
/*!
|
|
||||||
* @license
|
|
||||||
* Alfresco Example Content Application
|
|
||||||
*
|
|
||||||
* Copyright (C) 2005 - 2018 Alfresco Software Limited
|
|
||||||
*
|
|
||||||
* This file is part of the Alfresco Example Content Application.
|
|
||||||
* If the software was purchased under a paid Alfresco license, the terms of
|
|
||||||
* the paid license agreement will prevail. Otherwise, the software is
|
|
||||||
* provided under the following open source license terms:
|
|
||||||
*
|
|
||||||
* The Alfresco Example Content Application is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU Lesser General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* The Alfresco Example Content Application is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU Lesser General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU Lesser General Public License
|
|
||||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { Directive, HostListener, Input } from '@angular/core';
|
|
||||||
import { Observable } from 'rxjs/Rx';
|
|
||||||
import { MatSnackBar } from '@angular/material';
|
|
||||||
|
|
||||||
import { TranslationService } from '@alfresco/adf-core';
|
|
||||||
import { MinimalNodeEntity } from 'alfresco-js-api';
|
|
||||||
import { NodeActionsService } from '../services/node-actions.service';
|
|
||||||
import { ContentManagementService } from '../services/content-management.service';
|
|
||||||
import { ContentApiService } from '../services/content-api.service';
|
|
||||||
|
|
||||||
@Directive({
|
|
||||||
selector: '[acaCopyNode]'
|
|
||||||
})
|
|
||||||
export class NodeCopyDirective {
|
|
||||||
|
|
||||||
// tslint:disable-next-line:no-input-rename
|
|
||||||
@Input('acaCopyNode')
|
|
||||||
selection: MinimalNodeEntity[];
|
|
||||||
|
|
||||||
@HostListener('click')
|
|
||||||
onClick() {
|
|
||||||
this.copySelected();
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
private content: ContentManagementService,
|
|
||||||
private contentApi: ContentApiService,
|
|
||||||
private snackBar: MatSnackBar,
|
|
||||||
private nodeActionsService: NodeActionsService,
|
|
||||||
private translation: TranslationService
|
|
||||||
) {}
|
|
||||||
|
|
||||||
copySelected() {
|
|
||||||
Observable.zip(
|
|
||||||
this.nodeActionsService.copyNodes(this.selection),
|
|
||||||
this.nodeActionsService.contentCopied
|
|
||||||
).subscribe(
|
|
||||||
(result) => {
|
|
||||||
const [ operationResult, newItems ] = result;
|
|
||||||
this.toastMessage(operationResult, newItems);
|
|
||||||
},
|
|
||||||
(error) => {
|
|
||||||
this.toastMessage(error);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private toastMessage(info: any, newItems?: MinimalNodeEntity[]) {
|
|
||||||
const numberOfCopiedItems = newItems ? newItems.length : 0;
|
|
||||||
const failedItems = this.selection.length - numberOfCopiedItems;
|
|
||||||
|
|
||||||
let i18nMessageString = 'APP.MESSAGES.ERRORS.GENERIC';
|
|
||||||
|
|
||||||
if (typeof info === 'string') {
|
|
||||||
if (info.toLowerCase().indexOf('succes') !== -1) {
|
|
||||||
let i18MessageSuffix;
|
|
||||||
|
|
||||||
if (failedItems) {
|
|
||||||
if (numberOfCopiedItems) {
|
|
||||||
i18MessageSuffix = ( numberOfCopiedItems === 1 ) ? 'PARTIAL_SINGULAR' : 'PARTIAL_PLURAL';
|
|
||||||
|
|
||||||
} else {
|
|
||||||
i18MessageSuffix = ( failedItems === 1 ) ? 'FAIL_SINGULAR' : 'FAIL_PLURAL';
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
i18MessageSuffix = ( numberOfCopiedItems === 1 ) ? 'SINGULAR' : 'PLURAL';
|
|
||||||
}
|
|
||||||
|
|
||||||
i18nMessageString = `APP.MESSAGES.INFO.NODE_COPY.${i18MessageSuffix}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
|
|
||||||
const { error: { statusCode } } = JSON.parse(info.message);
|
|
||||||
|
|
||||||
if (statusCode === 403) {
|
|
||||||
i18nMessageString = 'APP.MESSAGES.ERRORS.PERMISSION';
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (err) { /* Do nothing, keep the original message */ }
|
|
||||||
}
|
|
||||||
|
|
||||||
const undo = (numberOfCopiedItems > 0) ? this.translation.instant('APP.ACTIONS.UNDO') : '';
|
|
||||||
|
|
||||||
const message = this.translation.instant(i18nMessageString, { success: numberOfCopiedItems, failed: failedItems });
|
|
||||||
|
|
||||||
this.snackBar
|
|
||||||
.open(message, undo, {
|
|
||||||
panelClass: 'info-snackbar',
|
|
||||||
duration: 3000
|
|
||||||
})
|
|
||||||
.onAction()
|
|
||||||
.subscribe(() => this.deleteCopy(newItems));
|
|
||||||
}
|
|
||||||
|
|
||||||
private deleteCopy(nodes: MinimalNodeEntity[]) {
|
|
||||||
const batch = this.nodeActionsService.flatten(nodes)
|
|
||||||
.filter(item => item.entry)
|
|
||||||
.map(item => this.contentApi.deleteNode(item.entry.id, { permanent: true }));
|
|
||||||
|
|
||||||
Observable.forkJoin(...batch)
|
|
||||||
.subscribe(
|
|
||||||
() => {
|
|
||||||
this.content.nodesDeleted.next(null);
|
|
||||||
},
|
|
||||||
(error) => {
|
|
||||||
let i18nMessageString = 'APP.MESSAGES.ERRORS.GENERIC';
|
|
||||||
|
|
||||||
let errorJson = null;
|
|
||||||
try {
|
|
||||||
errorJson = JSON.parse(error.message);
|
|
||||||
} catch (e) { //
|
|
||||||
}
|
|
||||||
|
|
||||||
if (errorJson && errorJson.error && errorJson.error.statusCode === 403) {
|
|
||||||
i18nMessageString = 'APP.MESSAGES.ERRORS.PERMISSION';
|
|
||||||
}
|
|
||||||
|
|
||||||
const message = this.translation.instant(i18nMessageString);
|
|
||||||
|
|
||||||
this.snackBar.open(message, '', {
|
|
||||||
panelClass: 'error-snackbar',
|
|
||||||
duration: 3000
|
|
||||||
});
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,274 +0,0 @@
|
|||||||
/*!
|
|
||||||
* @license
|
|
||||||
* Alfresco Example Content Application
|
|
||||||
*
|
|
||||||
* Copyright (C) 2005 - 2018 Alfresco Software Limited
|
|
||||||
*
|
|
||||||
* This file is part of the Alfresco Example Content Application.
|
|
||||||
* If the software was purchased under a paid Alfresco license, the terms of
|
|
||||||
* the paid license agreement will prevail. Otherwise, the software is
|
|
||||||
* provided under the following open source license terms:
|
|
||||||
*
|
|
||||||
* The Alfresco Example Content Application is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU Lesser General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* The Alfresco Example Content Application is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU Lesser General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU Lesser General Public License
|
|
||||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { TestBed, ComponentFixture, fakeAsync, tick } from '@angular/core/testing';
|
|
||||||
import { By } from '@angular/platform-browser';
|
|
||||||
import { Component, DebugElement } from '@angular/core';
|
|
||||||
|
|
||||||
import { NodeDeleteDirective } from './node-delete.directive';
|
|
||||||
import { EffectsModule, Actions, ofType } from '@ngrx/effects';
|
|
||||||
import { NodeEffects } from '../store/effects/node.effects';
|
|
||||||
import {
|
|
||||||
SnackbarInfoAction, SNACKBAR_INFO, SNACKBAR_ERROR,
|
|
||||||
SnackbarErrorAction, SnackbarWarningAction, SNACKBAR_WARNING
|
|
||||||
} from '../store/actions';
|
|
||||||
import { map } from 'rxjs/operators';
|
|
||||||
import { AppTestingModule } from '../testing/app-testing.module';
|
|
||||||
import { ContentApiService } from '../services/content-api.service';
|
|
||||||
import { Observable } from 'rxjs/Rx';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
template: '<div [acaDeleteNode]="selection"></div>'
|
|
||||||
})
|
|
||||||
class TestComponent {
|
|
||||||
selection;
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('NodeDeleteDirective', () => {
|
|
||||||
let component: TestComponent;
|
|
||||||
let fixture: ComponentFixture<TestComponent>;
|
|
||||||
let element: DebugElement;
|
|
||||||
let actions$: Actions;
|
|
||||||
let contentApi: ContentApiService;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
TestBed.configureTestingModule({
|
|
||||||
imports: [
|
|
||||||
AppTestingModule,
|
|
||||||
EffectsModule.forRoot([NodeEffects])
|
|
||||||
],
|
|
||||||
declarations: [
|
|
||||||
NodeDeleteDirective,
|
|
||||||
TestComponent
|
|
||||||
]
|
|
||||||
});
|
|
||||||
|
|
||||||
contentApi = TestBed.get(ContentApiService);
|
|
||||||
actions$ = TestBed.get(Actions);
|
|
||||||
|
|
||||||
fixture = TestBed.createComponent(TestComponent);
|
|
||||||
component = fixture.componentInstance;
|
|
||||||
element = fixture.debugElement.query(By.directive(NodeDeleteDirective));
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('Delete action', () => {
|
|
||||||
it('should raise info message on successful single file deletion', fakeAsync(done => {
|
|
||||||
spyOn(contentApi, 'deleteNode').and.returnValue(Observable.of(null));
|
|
||||||
|
|
||||||
actions$.pipe(
|
|
||||||
ofType<SnackbarInfoAction>(SNACKBAR_INFO),
|
|
||||||
map(action => {
|
|
||||||
done();
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
component.selection = [{ entry: { id: '1', name: 'name1' } }];
|
|
||||||
|
|
||||||
fixture.detectChanges();
|
|
||||||
element.triggerEventHandler('click', null);
|
|
||||||
|
|
||||||
tick();
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('should raise error message on failed single file deletion', fakeAsync(done => {
|
|
||||||
spyOn(contentApi, 'deleteNode').and.returnValue(Observable.throw(null));
|
|
||||||
|
|
||||||
actions$.pipe(
|
|
||||||
ofType<SnackbarErrorAction>(SNACKBAR_ERROR),
|
|
||||||
map(action => {
|
|
||||||
done();
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
component.selection = [{ entry: { id: '1', name: 'name1' } }];
|
|
||||||
|
|
||||||
fixture.detectChanges();
|
|
||||||
element.triggerEventHandler('click', null);
|
|
||||||
|
|
||||||
tick();
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('should raise info message on successful multiple files deletion', fakeAsync(done => {
|
|
||||||
spyOn(contentApi, 'deleteNode').and.returnValue(Observable.of(null));
|
|
||||||
|
|
||||||
actions$.pipe(
|
|
||||||
ofType<SnackbarInfoAction>(SNACKBAR_INFO),
|
|
||||||
map(action => {
|
|
||||||
done();
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
component.selection = [
|
|
||||||
{ entry: { id: '1', name: 'name1' } },
|
|
||||||
{ entry: { id: '2', name: 'name2' } }
|
|
||||||
];
|
|
||||||
|
|
||||||
fixture.detectChanges();
|
|
||||||
element.triggerEventHandler('click', null);
|
|
||||||
|
|
||||||
tick();
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('should raise error message failed multiple files deletion', fakeAsync(done => {
|
|
||||||
spyOn(contentApi, 'deleteNode').and.returnValue(Observable.throw(null));
|
|
||||||
|
|
||||||
actions$.pipe(
|
|
||||||
ofType<SnackbarErrorAction>(SNACKBAR_ERROR),
|
|
||||||
map(action => {
|
|
||||||
done();
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
component.selection = [
|
|
||||||
{ entry: { id: '1', name: 'name1' } },
|
|
||||||
{ entry: { id: '2', name: 'name2' } }
|
|
||||||
];
|
|
||||||
|
|
||||||
fixture.detectChanges();
|
|
||||||
element.triggerEventHandler('click', null);
|
|
||||||
|
|
||||||
tick();
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('should raise warning message when only one file is successful', fakeAsync(done => {
|
|
||||||
spyOn(contentApi, 'deleteNode').and.callFake((id) => {
|
|
||||||
if (id === '1') {
|
|
||||||
return Observable.throw(null);
|
|
||||||
} else {
|
|
||||||
return Observable.of(null);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
actions$.pipe(
|
|
||||||
ofType<SnackbarWarningAction>(SNACKBAR_WARNING),
|
|
||||||
map(action => {
|
|
||||||
done();
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
component.selection = [
|
|
||||||
{ entry: { id: '1', name: 'name1' } },
|
|
||||||
{ entry: { id: '2', name: 'name2' } }
|
|
||||||
];
|
|
||||||
|
|
||||||
fixture.detectChanges();
|
|
||||||
element.triggerEventHandler('click', null);
|
|
||||||
|
|
||||||
tick();
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('should raise warning message when some files are successfully deleted', fakeAsync(done => {
|
|
||||||
spyOn(contentApi, 'deleteNode').and.callFake((id) => {
|
|
||||||
if (id === '1') {
|
|
||||||
return Observable.throw(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (id === '2') {
|
|
||||||
return Observable.of(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (id === '3') {
|
|
||||||
return Observable.of(null);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
actions$.pipe(
|
|
||||||
ofType<SnackbarWarningAction>(SNACKBAR_WARNING),
|
|
||||||
map(action => {
|
|
||||||
done();
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
component.selection = [
|
|
||||||
{ entry: { id: '1', name: 'name1' } },
|
|
||||||
{ entry: { id: '2', name: 'name2' } },
|
|
||||||
{ entry: { id: '3', name: 'name3' } }
|
|
||||||
];
|
|
||||||
|
|
||||||
fixture.detectChanges();
|
|
||||||
element.triggerEventHandler('click', null);
|
|
||||||
|
|
||||||
tick();
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
|
|
||||||
/*
|
|
||||||
describe('Restore action', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
spyOn(alfrescoApiService.nodesApi, 'deleteNode').and.returnValue(Promise.resolve(null));
|
|
||||||
});
|
|
||||||
|
|
||||||
it('notifies failed file on on restore', () => {
|
|
||||||
spyOn(alfrescoApiService.nodesApi, 'restoreNode').and.returnValue(Promise.reject(null));
|
|
||||||
|
|
||||||
component.selection = [
|
|
||||||
{ entry: { id: '1', name: 'name1' } }
|
|
||||||
];
|
|
||||||
|
|
||||||
fixture.detectChanges();
|
|
||||||
element.triggerEventHandler('click', null);
|
|
||||||
|
|
||||||
expect(spySnackBar.calls.mostRecent().args)
|
|
||||||
.toEqual((['APP.MESSAGES.ERRORS.NODE_RESTORE', '', 3000]));
|
|
||||||
});
|
|
||||||
|
|
||||||
it('notifies failed files on on restore', () => {
|
|
||||||
spyOn(alfrescoApiService.nodesApi, 'restoreNode').and.returnValue(Promise.reject(null));
|
|
||||||
|
|
||||||
component.selection = [
|
|
||||||
{ entry: { id: '1', name: 'name1' } },
|
|
||||||
{ entry: { id: '2', name: 'name2' } }
|
|
||||||
];
|
|
||||||
|
|
||||||
fixture.detectChanges();
|
|
||||||
element.triggerEventHandler('click', null);
|
|
||||||
|
|
||||||
expect(spySnackBar.calls.mostRecent().args)
|
|
||||||
.toEqual((['APP.MESSAGES.ERRORS.NODE_RESTORE_PLURAL', '', 3000]));
|
|
||||||
});
|
|
||||||
|
|
||||||
it('signals files restored', () => {
|
|
||||||
spyOn(contentService.nodeRestored, 'next');
|
|
||||||
spyOn(alfrescoApiService.nodesApi, 'restoreNode').and.callFake((id) => {
|
|
||||||
if (id === '1') {
|
|
||||||
return Promise.resolve(null);
|
|
||||||
} else {
|
|
||||||
return Promise.reject(null);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
component.selection = [
|
|
||||||
{ entry: { id: '1', name: 'name1' } },
|
|
||||||
{ entry: { id: '2', name: 'name2' } }
|
|
||||||
];
|
|
||||||
|
|
||||||
fixture.detectChanges();
|
|
||||||
element.triggerEventHandler('click', null);
|
|
||||||
|
|
||||||
expect(contentService.nodeRestored.next).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
*/
|
|
||||||
});
|
|
@ -1,59 +0,0 @@
|
|||||||
/*!
|
|
||||||
* @license
|
|
||||||
* Alfresco Example Content Application
|
|
||||||
*
|
|
||||||
* Copyright (C) 2005 - 2018 Alfresco Software Limited
|
|
||||||
*
|
|
||||||
* This file is part of the Alfresco Example Content Application.
|
|
||||||
* If the software was purchased under a paid Alfresco license, the terms of
|
|
||||||
* the paid license agreement will prevail. Otherwise, the software is
|
|
||||||
* provided under the following open source license terms:
|
|
||||||
*
|
|
||||||
* The Alfresco Example Content Application is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU Lesser General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* The Alfresco Example Content Application is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU Lesser General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU Lesser General Public License
|
|
||||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { Directive, HostListener, Input } from '@angular/core';
|
|
||||||
import { MinimalNodeEntity } from 'alfresco-js-api';
|
|
||||||
import { Store } from '@ngrx/store';
|
|
||||||
import { AppStore } from '../store/states/app.state';
|
|
||||||
import { DeleteNodesAction } from '../store/actions';
|
|
||||||
import { NodeInfo } from '../store/models';
|
|
||||||
|
|
||||||
@Directive({
|
|
||||||
selector: '[acaDeleteNode]'
|
|
||||||
})
|
|
||||||
export class NodeDeleteDirective {
|
|
||||||
|
|
||||||
// tslint:disable-next-line:no-input-rename
|
|
||||||
@Input('acaDeleteNode')
|
|
||||||
selection: MinimalNodeEntity[];
|
|
||||||
|
|
||||||
constructor(private store: Store<AppStore>) {}
|
|
||||||
|
|
||||||
@HostListener('click')
|
|
||||||
onClick() {
|
|
||||||
if (this.selection && this.selection.length > 0) {
|
|
||||||
const toDelete: NodeInfo[] = this.selection.map(node => {
|
|
||||||
const { name } = node.entry;
|
|
||||||
const id = node.entry.nodeId || node.entry.id;
|
|
||||||
|
|
||||||
return {
|
|
||||||
id,
|
|
||||||
name
|
|
||||||
};
|
|
||||||
});
|
|
||||||
this.store.dispatch(new DeleteNodesAction(toDelete));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,477 +0,0 @@
|
|||||||
/*!
|
|
||||||
* @license
|
|
||||||
* Alfresco Example Content Application
|
|
||||||
*
|
|
||||||
* Copyright (C) 2005 - 2018 Alfresco Software Limited
|
|
||||||
*
|
|
||||||
* This file is part of the Alfresco Example Content Application.
|
|
||||||
* If the software was purchased under a paid Alfresco license, the terms of
|
|
||||||
* the paid license agreement will prevail. Otherwise, the software is
|
|
||||||
* provided under the following open source license terms:
|
|
||||||
*
|
|
||||||
* The Alfresco Example Content Application is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU Lesser General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* The Alfresco Example Content Application is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU Lesser General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU Lesser General Public License
|
|
||||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { Component, DebugElement } from '@angular/core';
|
|
||||||
import { ComponentFixture, TestBed, fakeAsync } from '@angular/core/testing';
|
|
||||||
import { By } from '@angular/platform-browser';
|
|
||||||
import { Observable } from 'rxjs/Rx';
|
|
||||||
import { MatSnackBar } from '@angular/material';
|
|
||||||
import { TranslationService } from '@alfresco/adf-core';
|
|
||||||
import { NodeActionsService } from '../services/node-actions.service';
|
|
||||||
import { NodeMoveDirective } from './node-move.directive';
|
|
||||||
import { EffectsModule, Actions, ofType } from '@ngrx/effects';
|
|
||||||
import { NodeEffects } from '../store/effects/node.effects';
|
|
||||||
import { SnackbarErrorAction, SNACKBAR_ERROR } from '../store/actions';
|
|
||||||
import { map } from 'rxjs/operators';
|
|
||||||
import { AppTestingModule } from '../testing/app-testing.module';
|
|
||||||
import { ContentApiService } from '../services/content-api.service';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
template: '<div [acaMoveNode]="selection"></div>'
|
|
||||||
})
|
|
||||||
class TestComponent {
|
|
||||||
selection;
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('NodeMoveDirective', () => {
|
|
||||||
let fixture: ComponentFixture<TestComponent>;
|
|
||||||
let component: TestComponent;
|
|
||||||
let element: DebugElement;
|
|
||||||
let service: NodeActionsService;
|
|
||||||
let actions$: Actions;
|
|
||||||
let translationService: TranslationService;
|
|
||||||
let contentApi: ContentApiService;
|
|
||||||
let snackBar: MatSnackBar;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
TestBed.configureTestingModule({
|
|
||||||
imports: [
|
|
||||||
AppTestingModule,
|
|
||||||
EffectsModule.forRoot([NodeEffects])
|
|
||||||
],
|
|
||||||
declarations: [
|
|
||||||
NodeMoveDirective,
|
|
||||||
TestComponent
|
|
||||||
]
|
|
||||||
});
|
|
||||||
|
|
||||||
contentApi = TestBed.get(ContentApiService);
|
|
||||||
translationService = TestBed.get(TranslationService);
|
|
||||||
|
|
||||||
actions$ = TestBed.get(Actions);
|
|
||||||
fixture = TestBed.createComponent(TestComponent);
|
|
||||||
component = fixture.componentInstance;
|
|
||||||
element = fixture.debugElement.query(By.directive(NodeMoveDirective));
|
|
||||||
service = TestBed.get(NodeActionsService);
|
|
||||||
snackBar = TestBed.get(MatSnackBar);
|
|
||||||
});
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
spyOn(translationService, 'instant').and.callFake((keysArray) => {
|
|
||||||
if (Array.isArray(keysArray)) {
|
|
||||||
const processedKeys = {};
|
|
||||||
keysArray.forEach((key) => {
|
|
||||||
processedKeys[key] = key;
|
|
||||||
});
|
|
||||||
return processedKeys;
|
|
||||||
} else {
|
|
||||||
return keysArray;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('Move node action', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
spyOn(snackBar, 'open').and.callThrough();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('notifies successful move of a node', () => {
|
|
||||||
const node = [ { entry: { id: 'node-to-move-id', name: 'name' } } ];
|
|
||||||
const moveResponse = {
|
|
||||||
succeeded: node,
|
|
||||||
failed: [],
|
|
||||||
partiallySucceeded: []
|
|
||||||
};
|
|
||||||
|
|
||||||
spyOn(service, 'moveNodes').and.returnValue(Observable.of('OPERATION.SUCCES.CONTENT.MOVE'));
|
|
||||||
spyOn(service, 'processResponse').and.returnValue(moveResponse);
|
|
||||||
|
|
||||||
component.selection = node;
|
|
||||||
|
|
||||||
fixture.detectChanges();
|
|
||||||
element.triggerEventHandler('click', null);
|
|
||||||
service.contentMoved.next(moveResponse);
|
|
||||||
|
|
||||||
expect(service.moveNodes).toHaveBeenCalled();
|
|
||||||
expect(snackBar.open['calls'].argsFor(0)[0]).toBe('APP.MESSAGES.INFO.NODE_MOVE.SINGULAR');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('notifies successful move of multiple nodes', () => {
|
|
||||||
const nodes = [
|
|
||||||
{ entry: { id: '1', name: 'name1' } },
|
|
||||||
{ entry: { id: '2', name: 'name2' } }];
|
|
||||||
const moveResponse = {
|
|
||||||
succeeded: nodes,
|
|
||||||
failed: [],
|
|
||||||
partiallySucceeded: []
|
|
||||||
};
|
|
||||||
|
|
||||||
spyOn(service, 'moveNodes').and.returnValue(Observable.of('OPERATION.SUCCES.CONTENT.MOVE'));
|
|
||||||
spyOn(service, 'processResponse').and.returnValue(moveResponse);
|
|
||||||
|
|
||||||
component.selection = nodes;
|
|
||||||
|
|
||||||
fixture.detectChanges();
|
|
||||||
element.triggerEventHandler('click', null);
|
|
||||||
service.contentMoved.next(moveResponse);
|
|
||||||
|
|
||||||
expect(service.moveNodes).toHaveBeenCalled();
|
|
||||||
expect(snackBar.open['calls'].argsFor(0)[0]).toBe('APP.MESSAGES.INFO.NODE_MOVE.PLURAL');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('notifies partial move of a node', () => {
|
|
||||||
const node = [ { entry: { id: '1', name: 'name' } } ];
|
|
||||||
const moveResponse = {
|
|
||||||
succeeded: [],
|
|
||||||
failed: [],
|
|
||||||
partiallySucceeded: node
|
|
||||||
};
|
|
||||||
|
|
||||||
spyOn(service, 'moveNodes').and.returnValue(Observable.of('OPERATION.SUCCES.CONTENT.MOVE'));
|
|
||||||
spyOn(service, 'processResponse').and.returnValue(moveResponse);
|
|
||||||
|
|
||||||
component.selection = node;
|
|
||||||
|
|
||||||
fixture.detectChanges();
|
|
||||||
element.triggerEventHandler('click', null);
|
|
||||||
service.contentMoved.next(moveResponse);
|
|
||||||
|
|
||||||
expect(service.moveNodes).toHaveBeenCalled();
|
|
||||||
expect(snackBar.open['calls'].argsFor(0)[0]).toBe('APP.MESSAGES.INFO.NODE_MOVE.PARTIAL.SINGULAR');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('notifies partial move of multiple nodes', () => {
|
|
||||||
const nodes = [
|
|
||||||
{ entry: { id: '1', name: 'name' } },
|
|
||||||
{ entry: { id: '2', name: 'name2' } } ];
|
|
||||||
const moveResponse = {
|
|
||||||
succeeded: [],
|
|
||||||
failed: [],
|
|
||||||
partiallySucceeded: nodes
|
|
||||||
};
|
|
||||||
|
|
||||||
spyOn(service, 'moveNodes').and.returnValue(Observable.of('OPERATION.SUCCES.CONTENT.MOVE'));
|
|
||||||
spyOn(service, 'processResponse').and.returnValue(moveResponse);
|
|
||||||
|
|
||||||
component.selection = nodes;
|
|
||||||
|
|
||||||
fixture.detectChanges();
|
|
||||||
element.triggerEventHandler('click', null);
|
|
||||||
service.contentMoved.next(moveResponse);
|
|
||||||
|
|
||||||
expect(service.moveNodes).toHaveBeenCalled();
|
|
||||||
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', () => {
|
|
||||||
const nodes = [ { entry: { id: '1', name: 'name' } },
|
|
||||||
{ entry: { id: '2', name: 'name2' } } ];
|
|
||||||
const moveResponse = {
|
|
||||||
succeeded: [ nodes[0] ],
|
|
||||||
failed: [ nodes[1] ],
|
|
||||||
partiallySucceeded: []
|
|
||||||
};
|
|
||||||
|
|
||||||
spyOn(service, 'moveNodes').and.returnValue(Observable.of('OPERATION.SUCCES.CONTENT.MOVE'));
|
|
||||||
spyOn(service, 'processResponse').and.returnValue(moveResponse);
|
|
||||||
|
|
||||||
component.selection = nodes;
|
|
||||||
|
|
||||||
fixture.detectChanges();
|
|
||||||
element.triggerEventHandler('click', null);
|
|
||||||
service.contentMoved.next(moveResponse);
|
|
||||||
|
|
||||||
expect(service.moveNodes).toHaveBeenCalled();
|
|
||||||
expect(snackBar.open['calls'].argsFor(0)[0])
|
|
||||||
.toBe('APP.MESSAGES.INFO.NODE_MOVE.SINGULAR APP.MESSAGES.INFO.NODE_MOVE.PARTIAL.FAIL');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('notifies successful move and the number of partially moved ones', () => {
|
|
||||||
const nodes = [ { entry: { id: '1', name: 'name' } },
|
|
||||||
{ entry: { id: '2', name: 'name2' } } ];
|
|
||||||
const moveResponse = {
|
|
||||||
succeeded: [ nodes[0] ],
|
|
||||||
failed: [],
|
|
||||||
partiallySucceeded: [ nodes[1] ]
|
|
||||||
};
|
|
||||||
|
|
||||||
spyOn(service, 'moveNodes').and.returnValue(Observable.of('OPERATION.SUCCES.CONTENT.MOVE'));
|
|
||||||
spyOn(service, 'processResponse').and.returnValue(moveResponse);
|
|
||||||
|
|
||||||
component.selection = nodes;
|
|
||||||
|
|
||||||
fixture.detectChanges();
|
|
||||||
element.triggerEventHandler('click', null);
|
|
||||||
service.contentMoved.next(moveResponse);
|
|
||||||
|
|
||||||
expect(service.moveNodes).toHaveBeenCalled();
|
|
||||||
expect(snackBar.open['calls'].argsFor(0)[0])
|
|
||||||
.toBe('APP.MESSAGES.INFO.NODE_MOVE.SINGULAR APP.MESSAGES.INFO.NODE_MOVE.PARTIAL.SINGULAR');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('notifies error if success message was not emitted', () => {
|
|
||||||
const node = { entry: { id: 'node-to-move-id', name: 'name' } };
|
|
||||||
const moveResponse = {
|
|
||||||
succeeded: [],
|
|
||||||
failed: [],
|
|
||||||
partiallySucceeded: []
|
|
||||||
};
|
|
||||||
|
|
||||||
spyOn(service, 'moveNodes').and.returnValue(Observable.of(''));
|
|
||||||
|
|
||||||
component.selection = [ node ];
|
|
||||||
|
|
||||||
fixture.detectChanges();
|
|
||||||
element.triggerEventHandler('click', null);
|
|
||||||
service.contentMoved.next(moveResponse);
|
|
||||||
|
|
||||||
expect(service.moveNodes).toHaveBeenCalled();
|
|
||||||
expect(snackBar.open['calls'].argsFor(0)[0]).toBe('APP.MESSAGES.ERRORS.GENERIC');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('notifies permission error on move of node', () => {
|
|
||||||
spyOn(service, 'moveNodes').and.returnValue(Observable.throw(new Error(JSON.stringify({error: {statusCode: 403}}))));
|
|
||||||
|
|
||||||
component.selection = [{ entry: { id: '1', name: 'name' } }];
|
|
||||||
|
|
||||||
fixture.detectChanges();
|
|
||||||
element.triggerEventHandler('click', null);
|
|
||||||
|
|
||||||
expect(service.moveNodes).toHaveBeenCalled();
|
|
||||||
expect(snackBar.open['calls'].argsFor(0)[0]).toBe('APP.MESSAGES.ERRORS.PERMISSION');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('notifies generic error message on all errors, but 403', () => {
|
|
||||||
spyOn(service, 'moveNodes').and.returnValue(Observable.throw(new Error(JSON.stringify({error: {statusCode: 404}}))));
|
|
||||||
|
|
||||||
component.selection = [{ entry: { id: '1', name: 'name' } }];
|
|
||||||
|
|
||||||
fixture.detectChanges();
|
|
||||||
element.triggerEventHandler('click', null);
|
|
||||||
|
|
||||||
expect(service.moveNodes).toHaveBeenCalled();
|
|
||||||
expect(snackBar.open['calls'].argsFor(0)[0]).toBe('APP.MESSAGES.ERRORS.GENERIC');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('notifies conflict error message on 409', () => {
|
|
||||||
spyOn(service, 'moveNodes').and.returnValue(Observable.throw(new Error(JSON.stringify({error: {statusCode: 409}}))));
|
|
||||||
|
|
||||||
component.selection = [{ entry: { id: '1', name: 'name' } }];
|
|
||||||
|
|
||||||
fixture.detectChanges();
|
|
||||||
element.triggerEventHandler('click', null);
|
|
||||||
|
|
||||||
expect(service.moveNodes).toHaveBeenCalled();
|
|
||||||
expect(snackBar.open['calls'].argsFor(0)[0]).toBe('APP.MESSAGES.ERRORS.NODE_MOVE');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('notifies error if move response has only failed items', () => {
|
|
||||||
const node = [ { entry: { id: '1', name: 'name' } } ];
|
|
||||||
const moveResponse = {
|
|
||||||
succeeded: [],
|
|
||||||
failed: [ {} ],
|
|
||||||
partiallySucceeded: []
|
|
||||||
};
|
|
||||||
|
|
||||||
spyOn(service, 'moveNodes').and.returnValue(Observable.of('OPERATION.SUCCES.CONTENT.MOVE'));
|
|
||||||
spyOn(service, 'processResponse').and.returnValue(moveResponse);
|
|
||||||
|
|
||||||
component.selection = node;
|
|
||||||
|
|
||||||
fixture.detectChanges();
|
|
||||||
element.triggerEventHandler('click', null);
|
|
||||||
service.contentMoved.next(moveResponse);
|
|
||||||
|
|
||||||
expect(service.moveNodes).toHaveBeenCalled();
|
|
||||||
expect(snackBar.open['calls'].argsFor(0)[0]).toBe('APP.MESSAGES.ERRORS.GENERIC');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('Undo Move action', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
spyOn(service, 'moveNodes').and.returnValue(Observable.of('OPERATION.SUCCES.CONTENT.MOVE'));
|
|
||||||
|
|
||||||
spyOn(snackBar, 'open').and.returnValue({
|
|
||||||
onAction: () => Observable.of({})
|
|
||||||
});
|
|
||||||
|
|
||||||
// spyOn(snackBar, 'open').and.callThrough();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should move node back to initial parent, after succeeded move', () => {
|
|
||||||
const initialParent = 'parent-id-0';
|
|
||||||
const node = { entry: { id: 'node-to-move-id', name: 'name', parentId: initialParent } };
|
|
||||||
component.selection = [ node ];
|
|
||||||
|
|
||||||
spyOn(service, 'moveNodeAction').and.returnValue(Observable.of({}));
|
|
||||||
|
|
||||||
fixture.detectChanges();
|
|
||||||
element.triggerEventHandler('click', null);
|
|
||||||
const movedItems = {
|
|
||||||
failed: [],
|
|
||||||
partiallySucceeded: [],
|
|
||||||
succeeded: [ { itemMoved: node, initialParentId: initialParent} ]
|
|
||||||
};
|
|
||||||
service.contentMoved.next(<any>movedItems);
|
|
||||||
|
|
||||||
expect(service.moveNodeAction)
|
|
||||||
.toHaveBeenCalledWith(movedItems.succeeded[0].itemMoved.entry, 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', () => {
|
|
||||||
const initialParent = 'parent-id-0';
|
|
||||||
const node = { entry: { id: 'node-to-move-id', name: 'name', isFolder: false, parentId: initialParent } };
|
|
||||||
component.selection = [ node ];
|
|
||||||
|
|
||||||
spyOn(service, 'moveNodeAction').and.returnValue(Observable.of({}));
|
|
||||||
|
|
||||||
const movedItems = {
|
|
||||||
failed: [],
|
|
||||||
partiallySucceeded: [],
|
|
||||||
succeeded: [ node ]
|
|
||||||
};
|
|
||||||
|
|
||||||
fixture.detectChanges();
|
|
||||||
element.triggerEventHandler('click', null);
|
|
||||||
service.contentMoved.next(<any>movedItems);
|
|
||||||
|
|
||||||
expect(service.moveNodeAction).toHaveBeenCalledWith(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', () => {
|
|
||||||
// when folder was deleted after all its children were moved to a folder with the same name from destination
|
|
||||||
spyOn(contentApi, 'restoreNode').and.returnValue(Observable.of(null));
|
|
||||||
|
|
||||||
const initialParent = 'parent-id-0';
|
|
||||||
const node = { entry: { id: 'folder-to-move-id', name: 'conflicting-name', parentId: initialParent, isFolder: true } };
|
|
||||||
component.selection = [ node ];
|
|
||||||
|
|
||||||
const itemMoved = {}; // folder was empty
|
|
||||||
service.moveDeletedEntries = [ node ]; // folder got deleted
|
|
||||||
|
|
||||||
const movedItems = {
|
|
||||||
failed: [],
|
|
||||||
partiallySucceeded: [],
|
|
||||||
succeeded: [ [ itemMoved ] ]
|
|
||||||
};
|
|
||||||
|
|
||||||
fixture.detectChanges();
|
|
||||||
element.triggerEventHandler('click', null);
|
|
||||||
service.contentMoved.next(<any>movedItems);
|
|
||||||
|
|
||||||
expect(contentApi.restoreNode).toHaveBeenCalled();
|
|
||||||
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 => {
|
|
||||||
spyOn(contentApi, 'restoreNode').and.returnValue(Observable.throw(null));
|
|
||||||
|
|
||||||
actions$.pipe(
|
|
||||||
ofType<SnackbarErrorAction>(SNACKBAR_ERROR),
|
|
||||||
map(action => done())
|
|
||||||
);
|
|
||||||
|
|
||||||
const initialParent = 'parent-id-0';
|
|
||||||
const node = { entry: { id: 'node-to-move-id', name: 'conflicting-name', parentId: initialParent } };
|
|
||||||
component.selection = [node];
|
|
||||||
|
|
||||||
const afterMoveParentId = 'parent-id-1';
|
|
||||||
const childMoved = { entry: { id: 'child-of-node-to-move-id', name: 'child-name', parentId: afterMoveParentId } };
|
|
||||||
service.moveDeletedEntries = [ node ]; // folder got deleted
|
|
||||||
|
|
||||||
const movedItems = {
|
|
||||||
failed: [],
|
|
||||||
partiallySucceeded: [],
|
|
||||||
succeeded: [{ itemMoved: childMoved, initialParentId: initialParent }]
|
|
||||||
};
|
|
||||||
|
|
||||||
fixture.detectChanges();
|
|
||||||
element.triggerEventHandler('click', null);
|
|
||||||
service.contentMoved.next(<any>movedItems);
|
|
||||||
|
|
||||||
expect(contentApi.restoreNode).toHaveBeenCalled();
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('should notify when some error of type Error occurs on Undo Move action', fakeAsync(done => {
|
|
||||||
spyOn(contentApi, 'restoreNode').and.returnValue(Observable.throw(new Error('oops!')));
|
|
||||||
|
|
||||||
actions$.pipe(
|
|
||||||
ofType<SnackbarErrorAction>(SNACKBAR_ERROR),
|
|
||||||
map(action => done())
|
|
||||||
);
|
|
||||||
|
|
||||||
const initialParent = 'parent-id-0';
|
|
||||||
const node = { entry: { id: 'node-to-move-id', name: 'name', parentId: initialParent } };
|
|
||||||
component.selection = [ node ];
|
|
||||||
|
|
||||||
const childMoved = { entry: { id: 'child-of-node-to-move-id', name: 'child-name' } };
|
|
||||||
service.moveDeletedEntries = [ node ]; // folder got deleted
|
|
||||||
|
|
||||||
const movedItems = {
|
|
||||||
failed: [],
|
|
||||||
partiallySucceeded: [],
|
|
||||||
succeeded: [{ itemMoved: childMoved, initialParentId: initialParent }]
|
|
||||||
};
|
|
||||||
|
|
||||||
fixture.detectChanges();
|
|
||||||
element.triggerEventHandler('click', null);
|
|
||||||
service.contentMoved.next(<any>movedItems);
|
|
||||||
|
|
||||||
expect(contentApi.restoreNode).toHaveBeenCalled();
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('should notify permission error when it occurs on Undo Move action', fakeAsync(done => {
|
|
||||||
spyOn(contentApi, 'restoreNode').and.returnValue(Observable.throw(new Error(JSON.stringify({error: {statusCode: 403}}))));
|
|
||||||
|
|
||||||
actions$.pipe(
|
|
||||||
ofType<SnackbarErrorAction>(SNACKBAR_ERROR),
|
|
||||||
map(action => done())
|
|
||||||
);
|
|
||||||
|
|
||||||
const initialParent = 'parent-id-0';
|
|
||||||
const node = { entry: { id: 'node-to-move-id', name: 'name', parentId: initialParent } };
|
|
||||||
component.selection = [ node ];
|
|
||||||
|
|
||||||
const childMoved = { entry: { id: 'child-of-node-to-move-id', name: 'child-name' } };
|
|
||||||
service.moveDeletedEntries = [ node ]; // folder got deleted
|
|
||||||
|
|
||||||
const movedItems = {
|
|
||||||
failed: [],
|
|
||||||
partiallySucceeded: [],
|
|
||||||
succeeded: [{ itemMoved: childMoved, initialParentId: initialParent }]
|
|
||||||
};
|
|
||||||
|
|
||||||
fixture.detectChanges();
|
|
||||||
element.triggerEventHandler('click', null);
|
|
||||||
service.contentMoved.next(<any>movedItems);
|
|
||||||
|
|
||||||
expect(service.moveNodes).toHaveBeenCalled();
|
|
||||||
expect(contentApi.restoreNode).toHaveBeenCalled();
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
@ -1,223 +0,0 @@
|
|||||||
/*!
|
|
||||||
* @license
|
|
||||||
* Alfresco Example Content Application
|
|
||||||
*
|
|
||||||
* Copyright (C) 2005 - 2018 Alfresco Software Limited
|
|
||||||
*
|
|
||||||
* This file is part of the Alfresco Example Content Application.
|
|
||||||
* If the software was purchased under a paid Alfresco license, the terms of
|
|
||||||
* the paid license agreement will prevail. Otherwise, the software is
|
|
||||||
* provided under the following open source license terms:
|
|
||||||
*
|
|
||||||
* The Alfresco Example Content Application is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU Lesser General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* The Alfresco Example Content Application is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU Lesser General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU Lesser General Public License
|
|
||||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { Directive, HostListener, Input } from '@angular/core';
|
|
||||||
|
|
||||||
import { TranslationService } from '@alfresco/adf-core';
|
|
||||||
import { MinimalNodeEntity } from 'alfresco-js-api';
|
|
||||||
import { MatSnackBar } from '@angular/material';
|
|
||||||
|
|
||||||
import { ContentManagementService } from '../services/content-management.service';
|
|
||||||
import { NodeActionsService } from '../services/node-actions.service';
|
|
||||||
import { Observable } from 'rxjs/Rx';
|
|
||||||
import { Store } from '@ngrx/store';
|
|
||||||
import { AppStore } from '../store/states/app.state';
|
|
||||||
import { SnackbarErrorAction } from '../store/actions';
|
|
||||||
import { ContentApiService } from '../services/content-api.service';
|
|
||||||
|
|
||||||
@Directive({
|
|
||||||
selector: '[acaMoveNode]'
|
|
||||||
})
|
|
||||||
|
|
||||||
export class NodeMoveDirective {
|
|
||||||
// tslint:disable-next-line:no-input-rename
|
|
||||||
@Input('acaMoveNode')
|
|
||||||
selection: MinimalNodeEntity[];
|
|
||||||
|
|
||||||
@HostListener('click')
|
|
||||||
onClick() {
|
|
||||||
this.moveSelected();
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
private store: Store<AppStore>,
|
|
||||||
private contentApi: ContentApiService,
|
|
||||||
private content: ContentManagementService,
|
|
||||||
private nodeActionsService: NodeActionsService,
|
|
||||||
private translation: TranslationService,
|
|
||||||
private snackBar: MatSnackBar
|
|
||||||
) {}
|
|
||||||
|
|
||||||
moveSelected() {
|
|
||||||
const permissionForMove = '!';
|
|
||||||
|
|
||||||
Observable.zip(
|
|
||||||
this.nodeActionsService.moveNodes(this.selection, permissionForMove),
|
|
||||||
this.nodeActionsService.contentMoved
|
|
||||||
).subscribe(
|
|
||||||
(result) => {
|
|
||||||
const [ operationResult, moveResponse ] = result;
|
|
||||||
this.toastMessage(operationResult, moveResponse);
|
|
||||||
|
|
||||||
this.content.nodesMoved.next(null);
|
|
||||||
},
|
|
||||||
(error) => {
|
|
||||||
this.toastMessage(error);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private toastMessage(info: any, 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 partialSuccessMessage = '';
|
|
||||||
let failedMessage = '';
|
|
||||||
let errorMessage = '';
|
|
||||||
|
|
||||||
if (typeof info === 'string') {
|
|
||||||
|
|
||||||
// in case of success
|
|
||||||
if (info.toLowerCase().indexOf('succes') !== -1) {
|
|
||||||
const i18nMessageString = 'APP.MESSAGES.INFO.NODE_MOVE.';
|
|
||||||
let i18MessageSuffix = '';
|
|
||||||
|
|
||||||
if (succeeded) {
|
|
||||||
i18MessageSuffix = ( succeeded === 1 ) ? 'SINGULAR' : 'PLURAL';
|
|
||||||
successMessage = `${i18nMessageString}${i18MessageSuffix}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (partiallySucceeded) {
|
|
||||||
i18MessageSuffix = ( partiallySucceeded === 1 ) ? 'PARTIAL.SINGULAR' : 'PARTIAL.PLURAL';
|
|
||||||
partialSuccessMessage = `${i18nMessageString}${i18MessageSuffix}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (failures) {
|
|
||||||
// if moving failed for ALL nodes, emit error
|
|
||||||
if (failures === this.selection.length) {
|
|
||||||
const errors = this.nodeActionsService.flatten(moveResponse['failed']);
|
|
||||||
errorMessage = this.getErrorMessage(errors[0]);
|
|
||||||
|
|
||||||
} else {
|
|
||||||
i18MessageSuffix = 'PARTIAL.FAIL';
|
|
||||||
failedMessage = `${i18nMessageString}${i18MessageSuffix}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
errorMessage = 'APP.MESSAGES.ERRORS.GENERIC';
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
errorMessage = this.getErrorMessage(info);
|
|
||||||
}
|
|
||||||
|
|
||||||
const undo = (succeeded + partiallySucceeded > 0) ? this.translation.instant('APP.ACTIONS.UNDO') : '';
|
|
||||||
failedMessage = errorMessage ? errorMessage : failedMessage;
|
|
||||||
|
|
||||||
const beforePartialSuccessMessage = (successMessage && partialSuccessMessage) ? ' ' : '';
|
|
||||||
const beforeFailedMessage = ((successMessage || partialSuccessMessage) && failedMessage) ? ' ' : '';
|
|
||||||
|
|
||||||
const initialParentId = this.nodeActionsService.getEntryParentId(this.selection[0].entry);
|
|
||||||
|
|
||||||
const messages = this.translation.instant(
|
|
||||||
[successMessage, partialSuccessMessage, failedMessage],
|
|
||||||
{ success: succeeded, failed: failures, partially: partiallySucceeded}
|
|
||||||
);
|
|
||||||
|
|
||||||
// TODO: review in terms of i18n
|
|
||||||
this.snackBar
|
|
||||||
.open(
|
|
||||||
messages[successMessage]
|
|
||||||
+ beforePartialSuccessMessage + messages[partialSuccessMessage]
|
|
||||||
+ beforeFailedMessage + messages[failedMessage]
|
|
||||||
, undo, {
|
|
||||||
panelClass: 'info-snackbar',
|
|
||||||
duration: 3000
|
|
||||||
})
|
|
||||||
.onAction()
|
|
||||||
.subscribe(() => this.revertMoving(moveResponse, initialParentId));
|
|
||||||
}
|
|
||||||
|
|
||||||
getErrorMessage(errorObject): string {
|
|
||||||
let i18nMessageString = 'APP.MESSAGES.ERRORS.GENERIC';
|
|
||||||
|
|
||||||
try {
|
|
||||||
const { error: { statusCode } } = JSON.parse(errorObject.message);
|
|
||||||
|
|
||||||
if (statusCode === 409) {
|
|
||||||
i18nMessageString = 'APP.MESSAGES.ERRORS.NODE_MOVE';
|
|
||||||
|
|
||||||
} else if (statusCode === 403) {
|
|
||||||
i18nMessageString = 'APP.MESSAGES.ERRORS.PERMISSION';
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (err) { /* Do nothing, keep the original message */ }
|
|
||||||
|
|
||||||
return i18nMessageString;
|
|
||||||
}
|
|
||||||
|
|
||||||
private revertMoving(moveResponse, selectionParentId) {
|
|
||||||
const movedNodes = (moveResponse && moveResponse['succeeded']) ? moveResponse['succeeded'] : [];
|
|
||||||
const partiallyMovedNodes = (moveResponse && moveResponse['partiallySucceeded']) ? moveResponse['partiallySucceeded'] : [];
|
|
||||||
|
|
||||||
const restoreDeletedNodesBatch = this.nodeActionsService.moveDeletedEntries
|
|
||||||
.map((folderEntry) => {
|
|
||||||
return this.contentApi
|
|
||||||
.restoreNode(folderEntry.nodeId || folderEntry.id)
|
|
||||||
.map(node => node.entry);
|
|
||||||
});
|
|
||||||
|
|
||||||
Observable.zip(...restoreDeletedNodesBatch, Observable.of(null))
|
|
||||||
.flatMap(() => {
|
|
||||||
|
|
||||||
const nodesToBeMovedBack = [...partiallyMovedNodes, ...movedNodes];
|
|
||||||
|
|
||||||
const revertMoveBatch = this.nodeActionsService
|
|
||||||
.flatten(nodesToBeMovedBack)
|
|
||||||
.filter(node => node.entry || (node.itemMoved && node.itemMoved.entry))
|
|
||||||
.map((node) => {
|
|
||||||
if (node.itemMoved) {
|
|
||||||
return this.nodeActionsService.moveNodeAction(node.itemMoved.entry, node.initialParentId);
|
|
||||||
} else {
|
|
||||||
return this.nodeActionsService.moveNodeAction(node.entry, selectionParentId);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return Observable.zip(...revertMoveBatch, Observable.of(null));
|
|
||||||
})
|
|
||||||
.subscribe(
|
|
||||||
() => {
|
|
||||||
this.content.nodesMoved.next(null);
|
|
||||||
},
|
|
||||||
error => {
|
|
||||||
let message = 'APP.MESSAGES.ERRORS.GENERIC';
|
|
||||||
|
|
||||||
let errorJson = null;
|
|
||||||
try {
|
|
||||||
errorJson = JSON.parse(error.message);
|
|
||||||
} catch {}
|
|
||||||
|
|
||||||
if (errorJson && errorJson.error && errorJson.error.statusCode === 403) {
|
|
||||||
message = 'APP.MESSAGES.ERRORS.PERMISSION';
|
|
||||||
}
|
|
||||||
|
|
||||||
this.store.dispatch(new SnackbarErrorAction(message));
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,272 +0,0 @@
|
|||||||
/*!
|
|
||||||
* @license
|
|
||||||
* Alfresco Example Content Application
|
|
||||||
*
|
|
||||||
* Copyright (C) 2005 - 2018 Alfresco Software Limited
|
|
||||||
*
|
|
||||||
* This file is part of the Alfresco Example Content Application.
|
|
||||||
* If the software was purchased under a paid Alfresco license, the terms of
|
|
||||||
* the paid license agreement will prevail. Otherwise, the software is
|
|
||||||
* provided under the following open source license terms:
|
|
||||||
*
|
|
||||||
* The Alfresco Example Content Application is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU Lesser General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* The Alfresco Example Content Application is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU Lesser General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU Lesser General Public License
|
|
||||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { Component, DebugElement } from '@angular/core';
|
|
||||||
import { TestBed, ComponentFixture, fakeAsync, tick } from '@angular/core/testing';
|
|
||||||
import { By } from '@angular/platform-browser';
|
|
||||||
import { Observable } from 'rxjs/Rx';
|
|
||||||
|
|
||||||
import { NodePermanentDeleteDirective } from './node-permanent-delete.directive';
|
|
||||||
import { MatDialog } from '@angular/material';
|
|
||||||
import { Actions, ofType, EffectsModule } from '@ngrx/effects';
|
|
||||||
import {
|
|
||||||
SNACKBAR_INFO, SnackbarWarningAction, SnackbarInfoAction,
|
|
||||||
SnackbarErrorAction, SNACKBAR_ERROR, SNACKBAR_WARNING
|
|
||||||
} from '../store/actions';
|
|
||||||
import { map } from 'rxjs/operators';
|
|
||||||
import { NodeEffects } from '../store/effects/node.effects';
|
|
||||||
import { AppTestingModule } from '../testing/app-testing.module';
|
|
||||||
import { ContentApiService } from '../services/content-api.service';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
template: `<div [acaPermanentDelete]="selection"></div>`
|
|
||||||
})
|
|
||||||
class TestComponent {
|
|
||||||
selection = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('NodePermanentDeleteDirective', () => {
|
|
||||||
let fixture: ComponentFixture<TestComponent>;
|
|
||||||
let element: DebugElement;
|
|
||||||
let component: TestComponent;
|
|
||||||
let dialog: MatDialog;
|
|
||||||
let actions$: Actions;
|
|
||||||
let contentApi: ContentApiService;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
TestBed.configureTestingModule({
|
|
||||||
imports: [
|
|
||||||
AppTestingModule,
|
|
||||||
EffectsModule.forRoot([NodeEffects])
|
|
||||||
],
|
|
||||||
declarations: [
|
|
||||||
NodePermanentDeleteDirective,
|
|
||||||
TestComponent
|
|
||||||
]
|
|
||||||
});
|
|
||||||
|
|
||||||
contentApi = TestBed.get(ContentApiService);
|
|
||||||
actions$ = TestBed.get(Actions);
|
|
||||||
|
|
||||||
fixture = TestBed.createComponent(TestComponent);
|
|
||||||
component = fixture.componentInstance;
|
|
||||||
element = fixture.debugElement.query(By.directive(NodePermanentDeleteDirective));
|
|
||||||
|
|
||||||
dialog = TestBed.get(MatDialog);
|
|
||||||
spyOn(dialog, 'open').and.returnValue({
|
|
||||||
afterClosed() {
|
|
||||||
return Observable.of(true);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('does not purge nodes if no selection', () => {
|
|
||||||
spyOn(contentApi, 'purgeDeletedNode');
|
|
||||||
|
|
||||||
component.selection = [];
|
|
||||||
|
|
||||||
fixture.detectChanges();
|
|
||||||
element.triggerEventHandler('click', null);
|
|
||||||
|
|
||||||
expect(contentApi.purgeDeletedNode).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('call purge nodes if selection is not empty', fakeAsync(() => {
|
|
||||||
spyOn(contentApi, 'purgeDeletedNode').and.returnValue(Observable.of({}));
|
|
||||||
|
|
||||||
component.selection = [ { entry: { id: '1' } } ];
|
|
||||||
|
|
||||||
fixture.detectChanges();
|
|
||||||
element.triggerEventHandler('click', null);
|
|
||||||
tick();
|
|
||||||
|
|
||||||
expect(contentApi.purgeDeletedNode).toHaveBeenCalled();
|
|
||||||
}));
|
|
||||||
|
|
||||||
describe('notification', () => {
|
|
||||||
it('raises warning on multiple fail and one success', fakeAsync(done => {
|
|
||||||
actions$.pipe(
|
|
||||||
ofType<SnackbarWarningAction>(SNACKBAR_WARNING),
|
|
||||||
map((action: SnackbarWarningAction) => {
|
|
||||||
done();
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
spyOn(contentApi, 'purgeDeletedNode').and.callFake((id) => {
|
|
||||||
if (id === '1') {
|
|
||||||
return Observable.of({});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (id === '2') {
|
|
||||||
return Observable.throw({});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (id === '3') {
|
|
||||||
return Observable.throw({});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
component.selection = [
|
|
||||||
{ entry: { id: '1', name: 'name1' } },
|
|
||||||
{ entry: { id: '2', name: 'name2' } },
|
|
||||||
{ entry: { id: '3', name: 'name3' } }
|
|
||||||
];
|
|
||||||
|
|
||||||
fixture.detectChanges();
|
|
||||||
element.triggerEventHandler('click', null);
|
|
||||||
tick();
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('raises warning on multiple success and multiple fail', fakeAsync(done => {
|
|
||||||
actions$.pipe(
|
|
||||||
ofType<SnackbarWarningAction>(SNACKBAR_WARNING),
|
|
||||||
map((action: SnackbarWarningAction) => {
|
|
||||||
done();
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
spyOn(contentApi, 'purgeDeletedNode').and.callFake((id) => {
|
|
||||||
if (id === '1') {
|
|
||||||
return Observable.of({});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (id === '2') {
|
|
||||||
return Observable.throw({});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (id === '3') {
|
|
||||||
return Observable.throw({});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (id === '4') {
|
|
||||||
return Observable.of({});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
component.selection = [
|
|
||||||
{ entry: { id: '1', name: 'name1' } },
|
|
||||||
{ entry: { id: '2', name: 'name2' } },
|
|
||||||
{ entry: { id: '3', name: 'name3' } },
|
|
||||||
{ entry: { id: '4', name: 'name4' } }
|
|
||||||
];
|
|
||||||
|
|
||||||
fixture.detectChanges();
|
|
||||||
element.triggerEventHandler('click', null);
|
|
||||||
tick();
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('raises info on one selected node success', fakeAsync(done => {
|
|
||||||
actions$.pipe(
|
|
||||||
ofType<SnackbarInfoAction>(SNACKBAR_INFO),
|
|
||||||
map((action: SnackbarInfoAction) => {
|
|
||||||
done();
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
spyOn(contentApi, 'purgeDeletedNode').and.returnValue(Observable.of({}));
|
|
||||||
|
|
||||||
component.selection = [
|
|
||||||
{ entry: { id: '1', name: 'name1' } }
|
|
||||||
];
|
|
||||||
|
|
||||||
fixture.detectChanges();
|
|
||||||
element.triggerEventHandler('click', null);
|
|
||||||
tick();
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('raises error on one selected node fail', fakeAsync(done => {
|
|
||||||
actions$.pipe(
|
|
||||||
ofType<SnackbarErrorAction>(SNACKBAR_ERROR),
|
|
||||||
map((action: SnackbarErrorAction) => {
|
|
||||||
done();
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
spyOn(contentApi, 'purgeDeletedNode').and.returnValue(Observable.throw({}));
|
|
||||||
|
|
||||||
component.selection = [
|
|
||||||
{ entry: { id: '1', name: 'name1' } }
|
|
||||||
];
|
|
||||||
|
|
||||||
fixture.detectChanges();
|
|
||||||
element.triggerEventHandler('click', null);
|
|
||||||
tick();
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('raises info on all nodes success', fakeAsync(done => {
|
|
||||||
actions$.pipe(
|
|
||||||
ofType<SnackbarInfoAction>(SNACKBAR_INFO),
|
|
||||||
map((action: SnackbarInfoAction) => {
|
|
||||||
done();
|
|
||||||
})
|
|
||||||
);
|
|
||||||
spyOn(contentApi, 'purgeDeletedNode').and.callFake((id) => {
|
|
||||||
if (id === '1') {
|
|
||||||
return Observable.of({});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (id === '2') {
|
|
||||||
return Observable.of({});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
component.selection = [
|
|
||||||
{ entry: { id: '1', name: 'name1' } },
|
|
||||||
{ entry: { id: '2', name: 'name2' } }
|
|
||||||
];
|
|
||||||
|
|
||||||
fixture.detectChanges();
|
|
||||||
element.triggerEventHandler('click', null);
|
|
||||||
tick();
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('raises error on all nodes fail', fakeAsync(done => {
|
|
||||||
actions$.pipe(
|
|
||||||
ofType<SnackbarErrorAction>(SNACKBAR_ERROR),
|
|
||||||
map((action: SnackbarErrorAction) => {
|
|
||||||
done();
|
|
||||||
})
|
|
||||||
);
|
|
||||||
spyOn(contentApi, 'purgeDeletedNode').and.callFake((id) => {
|
|
||||||
if (id === '1') {
|
|
||||||
return Observable.throw({});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (id === '2') {
|
|
||||||
return Observable.throw({});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
component.selection = [
|
|
||||||
{ entry: { id: '1', name: 'name1' } },
|
|
||||||
{ entry: { id: '2', name: 'name2' } }
|
|
||||||
];
|
|
||||||
|
|
||||||
fixture.detectChanges();
|
|
||||||
element.triggerEventHandler('click', null);
|
|
||||||
tick();
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,80 +0,0 @@
|
|||||||
/*!
|
|
||||||
* @license
|
|
||||||
* Alfresco Example Content Application
|
|
||||||
*
|
|
||||||
* Copyright (C) 2005 - 2018 Alfresco Software Limited
|
|
||||||
*
|
|
||||||
* This file is part of the Alfresco Example Content Application.
|
|
||||||
* If the software was purchased under a paid Alfresco license, the terms of
|
|
||||||
* the paid license agreement will prevail. Otherwise, the software is
|
|
||||||
* provided under the following open source license terms:
|
|
||||||
*
|
|
||||||
* The Alfresco Example Content Application is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU Lesser General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* The Alfresco Example Content Application is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU Lesser General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU Lesser General Public License
|
|
||||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { Directive, HostListener, Input } from '@angular/core';
|
|
||||||
import { MinimalNodeEntity } from 'alfresco-js-api';
|
|
||||||
import { MatDialog } from '@angular/material';
|
|
||||||
import { Store } from '@ngrx/store';
|
|
||||||
import { AppStore } from '../store/states/app.state';
|
|
||||||
import { SnackbarErrorAction } from '../store/actions';
|
|
||||||
import { NodePermissionsDialogComponent } from '../dialogs/node-permissions/node-permissions.dialog';
|
|
||||||
|
|
||||||
@Directive({
|
|
||||||
selector: '[acaNodePermissions]'
|
|
||||||
})
|
|
||||||
export class NodePermissionsDirective {
|
|
||||||
// tslint:disable-next-line:no-input-rename
|
|
||||||
@Input('acaNodePermissions') node: MinimalNodeEntity;
|
|
||||||
|
|
||||||
@HostListener('click')
|
|
||||||
onClick() {
|
|
||||||
this.showPermissions();
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
private store: Store<AppStore>,
|
|
||||||
private dialog: MatDialog
|
|
||||||
) {}
|
|
||||||
|
|
||||||
showPermissions() {
|
|
||||||
if (this.node) {
|
|
||||||
let entry;
|
|
||||||
if (this.node.entry) {
|
|
||||||
entry = this.node.entry;
|
|
||||||
|
|
||||||
} else {
|
|
||||||
entry = this.node;
|
|
||||||
}
|
|
||||||
|
|
||||||
const entryId = entry.nodeId || (<any>entry).guid || entry.id;
|
|
||||||
this.openPermissionsDialog(entryId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
openPermissionsDialog(nodeId: string) {
|
|
||||||
// workaround Shared
|
|
||||||
if (nodeId) {
|
|
||||||
this.dialog.open(NodePermissionsDialogComponent, {
|
|
||||||
data: { nodeId },
|
|
||||||
panelClass: 'aca-permissions-dialog-panel',
|
|
||||||
width: '730px'
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
this.store.dispatch(
|
|
||||||
new SnackbarErrorAction('APP.MESSAGES.ERRORS.PERMISSION')
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,390 +0,0 @@
|
|||||||
/*!
|
|
||||||
* @license
|
|
||||||
* Alfresco Example Content Application
|
|
||||||
*
|
|
||||||
* Copyright (C) 2005 - 2018 Alfresco Software Limited
|
|
||||||
*
|
|
||||||
* This file is part of the Alfresco Example Content Application.
|
|
||||||
* If the software was purchased under a paid Alfresco license, the terms of
|
|
||||||
* the paid license agreement will prevail. Otherwise, the software is
|
|
||||||
* provided under the following open source license terms:
|
|
||||||
*
|
|
||||||
* The Alfresco Example Content Application is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU Lesser General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* The Alfresco Example Content Application is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU Lesser General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU Lesser General Public License
|
|
||||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { Component, DebugElement } from '@angular/core';
|
|
||||||
import { TestBed, ComponentFixture, fakeAsync, tick } from '@angular/core/testing';
|
|
||||||
import { By } from '@angular/platform-browser';
|
|
||||||
import { NodeRestoreDirective } from './node-restore.directive';
|
|
||||||
import { ContentManagementService } from '../services/content-management.service';
|
|
||||||
import { Actions, ofType, EffectsModule } from '@ngrx/effects';
|
|
||||||
import { SnackbarErrorAction,
|
|
||||||
SNACKBAR_ERROR, SnackbarInfoAction, SNACKBAR_INFO,
|
|
||||||
NavigateRouteAction, NAVIGATE_ROUTE } from '../store/actions';
|
|
||||||
import { map } from 'rxjs/operators';
|
|
||||||
import { AppTestingModule } from '../testing/app-testing.module';
|
|
||||||
import { ContentApiService } from '../services/content-api.service';
|
|
||||||
import { Observable } from 'rxjs/Rx';
|
|
||||||
import { NodeEffects } from '../store/effects';
|
|
||||||
import { MinimalNodeEntity } from 'alfresco-js-api';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
template: `<div [acaRestoreNode]="selection"></div>`
|
|
||||||
})
|
|
||||||
class TestComponent {
|
|
||||||
selection: Array<MinimalNodeEntity> = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('NodeRestoreDirective', () => {
|
|
||||||
let fixture: ComponentFixture<TestComponent>;
|
|
||||||
let element: DebugElement;
|
|
||||||
let component: TestComponent;
|
|
||||||
let contentManagementService: ContentManagementService;
|
|
||||||
let actions$: Actions;
|
|
||||||
let contentApi: ContentApiService;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
TestBed.configureTestingModule({
|
|
||||||
imports: [
|
|
||||||
AppTestingModule,
|
|
||||||
EffectsModule.forRoot([NodeEffects])
|
|
||||||
],
|
|
||||||
declarations: [
|
|
||||||
NodeRestoreDirective,
|
|
||||||
TestComponent
|
|
||||||
]
|
|
||||||
});
|
|
||||||
|
|
||||||
actions$ = TestBed.get(Actions);
|
|
||||||
|
|
||||||
fixture = TestBed.createComponent(TestComponent);
|
|
||||||
component = fixture.componentInstance;
|
|
||||||
element = fixture.debugElement.query(By.directive(NodeRestoreDirective));
|
|
||||||
|
|
||||||
contentManagementService = TestBed.get(ContentManagementService);
|
|
||||||
contentApi = TestBed.get(ContentApiService);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('does not restore nodes if no selection', () => {
|
|
||||||
spyOn(contentApi, 'restoreNode');
|
|
||||||
|
|
||||||
component.selection = [];
|
|
||||||
|
|
||||||
fixture.detectChanges();
|
|
||||||
element.triggerEventHandler('click', null);
|
|
||||||
|
|
||||||
expect(contentApi.restoreNode).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('does not restore nodes if selection has nodes without path', () => {
|
|
||||||
spyOn(contentApi, 'restoreNode');
|
|
||||||
|
|
||||||
component.selection = [ { entry: { id: '1' } } ];
|
|
||||||
|
|
||||||
fixture.detectChanges();
|
|
||||||
element.triggerEventHandler('click', null);
|
|
||||||
|
|
||||||
expect(contentApi.restoreNode).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('call restore nodes if selection has nodes with path', fakeAsync(() => {
|
|
||||||
spyOn(contentApi, 'restoreNode').and.returnValue(Observable.of({}));
|
|
||||||
spyOn(contentApi, 'getDeletedNodes').and.returnValue(Observable.of({
|
|
||||||
list: { entries: [] }
|
|
||||||
}));
|
|
||||||
|
|
||||||
const path = {
|
|
||||||
elements: [
|
|
||||||
{
|
|
||||||
id: '1-1',
|
|
||||||
name: 'somewhere-over-the-rainbow'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
};
|
|
||||||
|
|
||||||
component.selection = [
|
|
||||||
{
|
|
||||||
entry: {
|
|
||||||
id: '1',
|
|
||||||
path
|
|
||||||
}
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
fixture.detectChanges();
|
|
||||||
element.triggerEventHandler('click', null);
|
|
||||||
tick();
|
|
||||||
|
|
||||||
expect(contentApi.restoreNode).toHaveBeenCalled();
|
|
||||||
}));
|
|
||||||
|
|
||||||
describe('refresh()', () => {
|
|
||||||
it('dispatch event on finish', fakeAsync(done => {
|
|
||||||
spyOn(contentApi, 'restoreNode').and.returnValue(Observable.of({}));
|
|
||||||
spyOn(contentApi, 'getDeletedNodes').and.returnValue(Observable.of({
|
|
||||||
list: { entries: [] }
|
|
||||||
}));
|
|
||||||
|
|
||||||
const path = {
|
|
||||||
elements: [
|
|
||||||
{
|
|
||||||
id: '1-1',
|
|
||||||
name: 'somewhere-over-the-rainbow'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
};
|
|
||||||
|
|
||||||
component.selection = [
|
|
||||||
{
|
|
||||||
entry: {
|
|
||||||
id: '1',
|
|
||||||
path
|
|
||||||
}
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
fixture.detectChanges();
|
|
||||||
element.triggerEventHandler('click', null);
|
|
||||||
tick();
|
|
||||||
|
|
||||||
contentManagementService.nodesRestored.subscribe(() => done());
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('notification', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
spyOn(contentApi, 'getDeletedNodes').and.returnValue(Observable.of({
|
|
||||||
list: { entries: [] }
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should raise error message on partial multiple fail ', fakeAsync(done => {
|
|
||||||
const error = { message: '{ "error": {} }' };
|
|
||||||
|
|
||||||
actions$.pipe(
|
|
||||||
ofType<SnackbarErrorAction>(SNACKBAR_ERROR),
|
|
||||||
map(action => done())
|
|
||||||
);
|
|
||||||
|
|
||||||
spyOn(contentApi, 'restoreNode').and.callFake((id) => {
|
|
||||||
if (id === '1') {
|
|
||||||
return Observable.of({});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (id === '2') {
|
|
||||||
return Observable.throw(error);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (id === '3') {
|
|
||||||
return Observable.throw(error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const path = {
|
|
||||||
elements: [
|
|
||||||
{
|
|
||||||
id: '1-1',
|
|
||||||
name: 'somewhere-over-the-rainbow'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
};
|
|
||||||
|
|
||||||
component.selection = [
|
|
||||||
{ entry: { id: '1', name: 'name1', path } },
|
|
||||||
{ entry: { id: '2', name: 'name2', path } },
|
|
||||||
{ entry: { id: '3', name: 'name3', path } }
|
|
||||||
];
|
|
||||||
|
|
||||||
fixture.detectChanges();
|
|
||||||
element.triggerEventHandler('click', null);
|
|
||||||
tick();
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('should raise error message when restored node exist, error 409', fakeAsync(done => {
|
|
||||||
const error = { message: '{ "error": { "statusCode": 409 } }' };
|
|
||||||
spyOn(contentApi, 'restoreNode').and.returnValue(Observable.throw(error));
|
|
||||||
|
|
||||||
actions$.pipe(
|
|
||||||
ofType<SnackbarErrorAction>(SNACKBAR_ERROR),
|
|
||||||
map(action => done())
|
|
||||||
);
|
|
||||||
|
|
||||||
const path = {
|
|
||||||
elements: [
|
|
||||||
{
|
|
||||||
id: '1-1',
|
|
||||||
name: 'somewhere-over-the-rainbow'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
};
|
|
||||||
|
|
||||||
component.selection = [
|
|
||||||
{ entry: { id: '1', name: 'name1', path } }
|
|
||||||
];
|
|
||||||
|
|
||||||
fixture.detectChanges();
|
|
||||||
element.triggerEventHandler('click', null);
|
|
||||||
tick();
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('should raise error message when restored node returns different statusCode', fakeAsync(done => {
|
|
||||||
const error = { message: '{ "error": { "statusCode": 404 } }' };
|
|
||||||
|
|
||||||
spyOn(contentApi, 'restoreNode').and.returnValue(Observable.throw(error));
|
|
||||||
|
|
||||||
actions$.pipe(
|
|
||||||
ofType<SnackbarErrorAction>(SNACKBAR_ERROR),
|
|
||||||
map(action => done())
|
|
||||||
);
|
|
||||||
|
|
||||||
const path = {
|
|
||||||
elements: [
|
|
||||||
{
|
|
||||||
id: '1-1',
|
|
||||||
name: 'somewhere-over-the-rainbow'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
};
|
|
||||||
|
|
||||||
component.selection = [
|
|
||||||
{ entry: { id: '1', name: 'name1', path } }
|
|
||||||
];
|
|
||||||
|
|
||||||
fixture.detectChanges();
|
|
||||||
element.triggerEventHandler('click', null);
|
|
||||||
tick();
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('should raise error message when restored node location is missing', fakeAsync(done => {
|
|
||||||
const error = { message: '{ "error": { } }' };
|
|
||||||
|
|
||||||
spyOn(contentApi, 'restoreNode').and.returnValue(Observable.throw(error));
|
|
||||||
|
|
||||||
actions$.pipe(
|
|
||||||
ofType<SnackbarErrorAction>(SNACKBAR_ERROR),
|
|
||||||
map(action => done())
|
|
||||||
);
|
|
||||||
|
|
||||||
const path = {
|
|
||||||
elements: [
|
|
||||||
{
|
|
||||||
id: '1-1',
|
|
||||||
name: 'somewhere-over-the-rainbow'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
};
|
|
||||||
|
|
||||||
component.selection = [
|
|
||||||
{ entry: { id: '1', name: 'name1', path } }
|
|
||||||
];
|
|
||||||
|
|
||||||
fixture.detectChanges();
|
|
||||||
element.triggerEventHandler('click', null);
|
|
||||||
tick();
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('should raise info message when restore multiple nodes', fakeAsync(done => {
|
|
||||||
spyOn(contentApi, 'restoreNode').and.callFake((id) => {
|
|
||||||
if (id === '1') {
|
|
||||||
return Observable.of({});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (id === '2') {
|
|
||||||
return Observable.of({});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
actions$.pipe(
|
|
||||||
ofType<SnackbarInfoAction>(SNACKBAR_INFO),
|
|
||||||
map(action => done())
|
|
||||||
);
|
|
||||||
|
|
||||||
const path = {
|
|
||||||
elements: [
|
|
||||||
{
|
|
||||||
id: '1-1',
|
|
||||||
name: 'somewhere-over-the-rainbow'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
};
|
|
||||||
|
|
||||||
component.selection = [
|
|
||||||
{ entry: { id: '1', name: 'name1', path } },
|
|
||||||
{ entry: { id: '2', name: 'name2', path } }
|
|
||||||
];
|
|
||||||
|
|
||||||
fixture.detectChanges();
|
|
||||||
element.triggerEventHandler('click', null);
|
|
||||||
tick();
|
|
||||||
}));
|
|
||||||
|
|
||||||
xit('should raise info message when restore selected node', fakeAsync(done => {
|
|
||||||
spyOn(contentApi, 'restoreNode').and.returnValue(Observable.of({}));
|
|
||||||
|
|
||||||
actions$.pipe(
|
|
||||||
ofType<SnackbarInfoAction>(SNACKBAR_INFO),
|
|
||||||
map(action => done())
|
|
||||||
);
|
|
||||||
|
|
||||||
const path = {
|
|
||||||
elements: [
|
|
||||||
{
|
|
||||||
id: '1-1',
|
|
||||||
name: 'somewhere-over-the-rainbow'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
};
|
|
||||||
|
|
||||||
component.selection = [
|
|
||||||
{ entry: { id: '1', name: 'name1', path } }
|
|
||||||
];
|
|
||||||
|
|
||||||
fixture.detectChanges();
|
|
||||||
element.triggerEventHandler('click', null);
|
|
||||||
tick();
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('navigate to restore selected node location onAction', fakeAsync(done => {
|
|
||||||
spyOn(contentApi, 'restoreNode').and.returnValue(Observable.of({}));
|
|
||||||
|
|
||||||
actions$.pipe(
|
|
||||||
ofType<NavigateRouteAction>(NAVIGATE_ROUTE),
|
|
||||||
map(action => done())
|
|
||||||
);
|
|
||||||
|
|
||||||
const path = {
|
|
||||||
elements: [
|
|
||||||
{
|
|
||||||
id: '1-1',
|
|
||||||
name: 'somewhere-over-the-rainbow'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
};
|
|
||||||
|
|
||||||
component.selection = [
|
|
||||||
{
|
|
||||||
entry: {
|
|
||||||
id: '1',
|
|
||||||
name: 'name1',
|
|
||||||
path
|
|
||||||
}
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
fixture.detectChanges();
|
|
||||||
element.triggerEventHandler('click', null);
|
|
||||||
tick();
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,57 +0,0 @@
|
|||||||
/*!
|
|
||||||
* @license
|
|
||||||
* Alfresco Example Content Application
|
|
||||||
*
|
|
||||||
* Copyright (C) 2005 - 2018 Alfresco Software Limited
|
|
||||||
*
|
|
||||||
* This file is part of the Alfresco Example Content Application.
|
|
||||||
* If the software was purchased under a paid Alfresco license, the terms of
|
|
||||||
* the paid license agreement will prevail. Otherwise, the software is
|
|
||||||
* provided under the following open source license terms:
|
|
||||||
*
|
|
||||||
* The Alfresco Example Content Application is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU Lesser General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* The Alfresco Example Content Application is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU Lesser General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU Lesser General Public License
|
|
||||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { Directive, HostListener, Input } from '@angular/core';
|
|
||||||
import { MinimalNodeEntity } from 'alfresco-js-api';
|
|
||||||
import { ContentManagementService } from '../services/content-management.service';
|
|
||||||
import { ContentApiService } from '../services/content-api.service';
|
|
||||||
|
|
||||||
@Directive({
|
|
||||||
selector: '[acaUnshareNode]'
|
|
||||||
})
|
|
||||||
export class NodeUnshareDirective {
|
|
||||||
|
|
||||||
// tslint:disable-next-line:no-input-rename
|
|
||||||
@Input('acaUnshareNode')
|
|
||||||
selection: MinimalNodeEntity[];
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
private contentApi: ContentApiService,
|
|
||||||
private contentManagement: ContentManagementService) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@HostListener('click')
|
|
||||||
onClick() {
|
|
||||||
if (this.selection.length > 0) {
|
|
||||||
this.unshareLinks(this.selection);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async unshareLinks(links: MinimalNodeEntity[]) {
|
|
||||||
const promises = links.map(link => this.contentApi.deleteSharedLink(link.entry.id).toPromise());
|
|
||||||
await Promise.all(promises);
|
|
||||||
this.contentManagement.linksUnshared.next();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,84 +0,0 @@
|
|||||||
/*!
|
|
||||||
* @license
|
|
||||||
* Alfresco Example Content Application
|
|
||||||
*
|
|
||||||
* Copyright (C) 2005 - 2018 Alfresco Software Limited
|
|
||||||
*
|
|
||||||
* This file is part of the Alfresco Example Content Application.
|
|
||||||
* If the software was purchased under a paid Alfresco license, the terms of
|
|
||||||
* the paid license agreement will prevail. Otherwise, the software is
|
|
||||||
* provided under the following open source license terms:
|
|
||||||
*
|
|
||||||
* The Alfresco Example Content Application is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU Lesser General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* The Alfresco Example Content Application is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU Lesser General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU Lesser General Public License
|
|
||||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { Directive, HostListener, Input } from '@angular/core';
|
|
||||||
import { MinimalNodeEntity, MinimalNodeEntryEntity } from 'alfresco-js-api';
|
|
||||||
import { NodeVersionsDialogComponent } from '../dialogs/node-versions/node-versions.dialog';
|
|
||||||
import { MatDialog } from '@angular/material';
|
|
||||||
import { Store } from '@ngrx/store';
|
|
||||||
import { AppStore } from '../store/states/app.state';
|
|
||||||
import { SnackbarErrorAction } from '../store/actions';
|
|
||||||
import { ContentApiService } from '../services/content-api.service';
|
|
||||||
|
|
||||||
@Directive({
|
|
||||||
selector: '[acaNodeVersions]'
|
|
||||||
})
|
|
||||||
export class NodeVersionsDirective {
|
|
||||||
// tslint:disable-next-line:no-input-rename
|
|
||||||
@Input('acaNodeVersions') node: MinimalNodeEntity;
|
|
||||||
|
|
||||||
@HostListener('click')
|
|
||||||
onClick() {
|
|
||||||
this.onManageVersions();
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
private store: Store<AppStore>,
|
|
||||||
private contentApi: ContentApiService,
|
|
||||||
private dialog: MatDialog
|
|
||||||
) {}
|
|
||||||
|
|
||||||
async onManageVersions() {
|
|
||||||
if (this.node && this.node.entry) {
|
|
||||||
let entry = this.node.entry;
|
|
||||||
|
|
||||||
if (entry.nodeId || (<any>entry).guid) {
|
|
||||||
entry = await this.contentApi.getNodeInfo(
|
|
||||||
entry.nodeId || (<any>entry).id
|
|
||||||
).toPromise();
|
|
||||||
this.openVersionManagerDialog(entry);
|
|
||||||
} else {
|
|
||||||
this.openVersionManagerDialog(entry);
|
|
||||||
}
|
|
||||||
} else if (this.node) {
|
|
||||||
this.openVersionManagerDialog(<MinimalNodeEntryEntity>this.node);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
openVersionManagerDialog(node: MinimalNodeEntryEntity) {
|
|
||||||
// workaround Shared
|
|
||||||
if (node.isFile || node.nodeId) {
|
|
||||||
this.dialog.open(NodeVersionsDialogComponent, {
|
|
||||||
data: { node },
|
|
||||||
panelClass: 'adf-version-manager-dialog-panel',
|
|
||||||
width: '630px'
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
this.store.dispatch(
|
|
||||||
new SnackbarErrorAction('APP.MESSAGES.ERRORS.PERMISSION')
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -24,7 +24,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
export enum ContentActionType {
|
export enum ContentActionType {
|
||||||
default = 'button',
|
default = 'default',
|
||||||
button = 'button',
|
button = 'button',
|
||||||
separator = 'separator',
|
separator = 'separator',
|
||||||
menu = 'menu',
|
menu = 'menu',
|
||||||
@ -36,6 +36,7 @@ export interface ContentActionRef {
|
|||||||
type: ContentActionType;
|
type: ContentActionType;
|
||||||
|
|
||||||
title?: string;
|
title?: string;
|
||||||
|
description?: string;
|
||||||
order?: number;
|
order?: number;
|
||||||
icon?: string;
|
icon?: string;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
|
@ -1,37 +0,0 @@
|
|||||||
<ng-container [ngSwitch]="entry.type">
|
|
||||||
<button *ngSwitchCase="'button'"
|
|
||||||
mat-icon-button
|
|
||||||
color="primary"
|
|
||||||
title="{{ entry.title | translate }}"
|
|
||||||
(click)="runAction(entry.actions.click)">
|
|
||||||
<mat-icon>{{ entry.icon }}</mat-icon>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<adf-toolbar-divider *ngSwitchCase="'separator'"></adf-toolbar-divider>
|
|
||||||
|
|
||||||
<ng-container *ngSwitchCase="'menu'">
|
|
||||||
<button
|
|
||||||
color="primary"
|
|
||||||
mat-icon-button
|
|
||||||
title="{{ entry.title | translate }}"
|
|
||||||
[matMenuTriggerFor]="menu">
|
|
||||||
<mat-icon>{{ entry.icon }}</mat-icon>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<mat-menu #menu="matMenu"
|
|
||||||
[overlapTrigger]="false">
|
|
||||||
<ng-container *ngFor="let child of entry.children">
|
|
||||||
<button mat-menu-item
|
|
||||||
(click)="runAction(child.actions.click)">
|
|
||||||
<mat-icon>{{ child.icon }}</mat-icon>
|
|
||||||
<span>{{ child.title | translate }}</span>
|
|
||||||
</button>
|
|
||||||
</ng-container>
|
|
||||||
</mat-menu>
|
|
||||||
</ng-container>
|
|
||||||
|
|
||||||
<ng-container *ngSwitchCase="'custom'">
|
|
||||||
<app-custom-component [id]="entry.component"></app-custom-component>
|
|
||||||
</ng-container>
|
|
||||||
</ng-container>
|
|
||||||
|
|
@ -0,0 +1,39 @@
|
|||||||
|
<ng-container [ngSwitch]="entry.type">
|
||||||
|
<ng-container *ngSwitchCase="'button'">
|
||||||
|
<app-toolbar-button [type]="type" [actionRef]="entry"></app-toolbar-button>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<adf-toolbar-divider *ngSwitchCase="'separator'"
|
||||||
|
[id]="entry.id">
|
||||||
|
</adf-toolbar-divider>
|
||||||
|
|
||||||
|
<ng-container *ngSwitchCase="'menu'">
|
||||||
|
<button
|
||||||
|
[id]="entry.id"
|
||||||
|
color="primary"
|
||||||
|
mat-icon-button
|
||||||
|
[attr.title]="(entry.description || entry.title) | translate"
|
||||||
|
[matMenuTriggerFor]="menu">
|
||||||
|
<mat-icon>{{ entry.icon }}</mat-icon>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<mat-menu #menu="matMenu"
|
||||||
|
[overlapTrigger]="false">
|
||||||
|
<ng-container *ngFor="let child of entry.children; trackBy: trackByActionId">
|
||||||
|
<ng-container [ngSwitch]="child.type">
|
||||||
|
<ng-container *ngSwitchCase="'custom'">
|
||||||
|
<app-custom-component [id]="child.component"></app-custom-component>
|
||||||
|
</ng-container>
|
||||||
|
<ng-container *ngSwitchDefault>
|
||||||
|
<app-toolbar-button type="menu-item" [actionRef]="child"></app-toolbar-button>
|
||||||
|
</ng-container>
|
||||||
|
</ng-container>
|
||||||
|
</ng-container>
|
||||||
|
</mat-menu>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<ng-container *ngSwitchCase="'custom'">
|
||||||
|
<app-custom-component [id]="entry.component"></app-custom-component>
|
||||||
|
</ng-container>
|
||||||
|
</ng-container>
|
||||||
|
|
@ -27,16 +27,11 @@ import {
|
|||||||
Component,
|
Component,
|
||||||
ViewEncapsulation,
|
ViewEncapsulation,
|
||||||
ChangeDetectionStrategy,
|
ChangeDetectionStrategy,
|
||||||
Input,
|
Input
|
||||||
OnInit,
|
|
||||||
OnDestroy
|
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
import { AppStore, SelectionState } from '../../../store/states';
|
import { AppStore } from '../../../store/states';
|
||||||
import { Store } from '@ngrx/store';
|
import { Store } from '@ngrx/store';
|
||||||
import { ExtensionService } from '../../extension.service';
|
import { ExtensionService } from '../../extension.service';
|
||||||
import { appSelection } from '../../../store/selectors/app.selectors';
|
|
||||||
import { Subject } from 'rxjs/Rx';
|
|
||||||
import { takeUntil } from 'rxjs/operators';
|
|
||||||
import { ContentActionRef } from '../../action.extensions';
|
import { ContentActionRef } from '../../action.extensions';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@ -46,36 +41,16 @@ import { ContentActionRef } from '../../action.extensions';
|
|||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
host: { class: 'aca-toolbar-action' }
|
host: { class: 'aca-toolbar-action' }
|
||||||
})
|
})
|
||||||
export class ToolbarActionComponent implements OnInit, OnDestroy {
|
export class ToolbarActionComponent {
|
||||||
|
@Input() type = 'icon-button';
|
||||||
@Input() entry: ContentActionRef;
|
@Input() entry: ContentActionRef;
|
||||||
|
|
||||||
selection: SelectionState;
|
|
||||||
onDestroy$: Subject<boolean> = new Subject<boolean>();
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
protected store: Store<AppStore>,
|
protected store: Store<AppStore>,
|
||||||
protected extensions: ExtensionService
|
protected extensions: ExtensionService
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
ngOnInit() {
|
trackByActionId(index: number, action: ContentActionRef) {
|
||||||
this.store
|
return action.id;
|
||||||
.select(appSelection)
|
|
||||||
.pipe(takeUntil(this.onDestroy$))
|
|
||||||
.subscribe(selection => {
|
|
||||||
this.selection = selection;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
ngOnDestroy() {
|
|
||||||
this.onDestroy$.next(true);
|
|
||||||
this.onDestroy$.complete();
|
|
||||||
}
|
|
||||||
|
|
||||||
runAction(actionId: string) {
|
|
||||||
const context = {
|
|
||||||
selection: this.selection
|
|
||||||
};
|
|
||||||
|
|
||||||
this.extensions.runActionById(actionId, context);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -0,0 +1,90 @@
|
|||||||
|
/*!
|
||||||
|
* @license
|
||||||
|
* Alfresco Example Content Application
|
||||||
|
*
|
||||||
|
* Copyright (C) 2005 - 2018 Alfresco Software Limited
|
||||||
|
*
|
||||||
|
* This file is part of the Alfresco Example Content Application.
|
||||||
|
* If the software was purchased under a paid Alfresco license, the terms of
|
||||||
|
* the paid license agreement will prevail. Otherwise, the software is
|
||||||
|
* provided under the following open source license terms:
|
||||||
|
*
|
||||||
|
* The Alfresco Example Content Application is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* The Alfresco Example Content Application is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public License
|
||||||
|
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Component, Input } from '@angular/core';
|
||||||
|
import { ContentActionRef } from '../../action.extensions';
|
||||||
|
import { ExtensionService } from '../../extension.service';
|
||||||
|
import { Store } from '@ngrx/store';
|
||||||
|
import { AppStore } from '../../../store/states';
|
||||||
|
import { appSelection } from '../../../store/selectors/app.selectors';
|
||||||
|
|
||||||
|
export enum ToolbarButtonType {
|
||||||
|
ICON_BUTTON = 'icon-button',
|
||||||
|
MENU_ITEM = 'menu-item'
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-toolbar-button',
|
||||||
|
template: `
|
||||||
|
<ng-container [ngSwitch]="type">
|
||||||
|
<ng-container *ngSwitchCase="'icon-button'">
|
||||||
|
<button
|
||||||
|
[id]="actionRef.id"
|
||||||
|
mat-icon-button
|
||||||
|
color="primary"
|
||||||
|
[attr.title]="(actionRef.description || actionRef.title) | translate"
|
||||||
|
(click)="runAction()">
|
||||||
|
<mat-icon>{{ actionRef.icon }}</mat-icon>
|
||||||
|
</button>
|
||||||
|
</ng-container>
|
||||||
|
<ng-container *ngSwitchCase="'menu-item'">
|
||||||
|
<button
|
||||||
|
[id]="actionRef.id"
|
||||||
|
mat-menu-item
|
||||||
|
color="primary"
|
||||||
|
[disabled]="actionRef.disabled"
|
||||||
|
[attr.title]="(
|
||||||
|
actionRef.disabled
|
||||||
|
? actionRef['description-disabled']
|
||||||
|
: actionRef.description || actionRef.title
|
||||||
|
) | translate"
|
||||||
|
(click)="runAction()">
|
||||||
|
<mat-icon>{{ actionRef.icon }}</mat-icon>
|
||||||
|
<span>{{ actionRef.title | translate }}</span>
|
||||||
|
</button>
|
||||||
|
</ng-container>
|
||||||
|
</ng-container>
|
||||||
|
`
|
||||||
|
})
|
||||||
|
export class ToolbarButtonComponent {
|
||||||
|
@Input() type: ToolbarButtonType = ToolbarButtonType.ICON_BUTTON;
|
||||||
|
@Input() actionRef: ContentActionRef;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
protected store: Store<AppStore>,
|
||||||
|
private extensions: ExtensionService
|
||||||
|
) {}
|
||||||
|
|
||||||
|
runAction() {
|
||||||
|
this.store
|
||||||
|
.select(appSelection)
|
||||||
|
.take(1)
|
||||||
|
.subscribe(selection => {
|
||||||
|
this.extensions.runActionById(this.actionRef.actions.click, {
|
||||||
|
selection
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -28,11 +28,14 @@ import { CommonModule } from '@angular/common';
|
|||||||
import { APP_INITIALIZER, ModuleWithProviders, NgModule } from '@angular/core';
|
import { APP_INITIALIZER, ModuleWithProviders, NgModule } from '@angular/core';
|
||||||
import { LayoutComponent } from '../components/layout/layout.component';
|
import { LayoutComponent } from '../components/layout/layout.component';
|
||||||
import { TrashcanComponent } from '../components/trashcan/trashcan.component';
|
import { TrashcanComponent } from '../components/trashcan/trashcan.component';
|
||||||
import { ToolbarActionComponent } from './components/toolbar-action/toolbar-action.component';
|
import { ToolbarActionComponent } from './components/toolbar/toolbar-action.component';
|
||||||
import * as app from './evaluators/app.evaluators';
|
import * as app from './evaluators/app.evaluators';
|
||||||
|
import * as nav from './evaluators/navigation.evaluators';
|
||||||
import { ExtensionService } from './extension.service';
|
import { ExtensionService } from './extension.service';
|
||||||
import { CustomExtensionComponent } from './components/custom-component/custom.component';
|
import { CustomExtensionComponent } from './components/custom-component/custom.component';
|
||||||
import { DemoButtonComponent } from './components/custom-component/demo.button';
|
import { ToggleInfoDrawerComponent } from '../components/toolbar/toggle-info-drawer/toggle-info-drawer.component';
|
||||||
|
import { ToggleFavoriteComponent } from '../components/toolbar/toggle-favorite/toggle-favorite.component';
|
||||||
|
import { ToolbarButtonComponent } from './components/toolbar/toolbar-button.component';
|
||||||
|
|
||||||
export function setupExtensions(extensions: ExtensionService): Function {
|
export function setupExtensions(extensions: ExtensionService): Function {
|
||||||
return () =>
|
return () =>
|
||||||
@ -40,7 +43,8 @@ export function setupExtensions(extensions: ExtensionService): Function {
|
|||||||
extensions.setComponents({
|
extensions.setComponents({
|
||||||
'app.layout.main': LayoutComponent,
|
'app.layout.main': LayoutComponent,
|
||||||
'app.components.trashcan': TrashcanComponent,
|
'app.components.trashcan': TrashcanComponent,
|
||||||
'app.demo.button': DemoButtonComponent
|
'app.toolbar.toggleInfoDrawer': ToggleInfoDrawerComponent,
|
||||||
|
'app.toolbar.toggleFavorite': ToggleFavoriteComponent
|
||||||
});
|
});
|
||||||
|
|
||||||
extensions.setAuthGuards({
|
extensions.setAuthGuards({
|
||||||
@ -48,14 +52,33 @@ export function setupExtensions(extensions: ExtensionService): Function {
|
|||||||
});
|
});
|
||||||
|
|
||||||
extensions.setEvaluators({
|
extensions.setEvaluators({
|
||||||
|
'app.selection.canDelete': app.canDeleteSelection,
|
||||||
'app.selection.canDownload': app.canDownloadSelection,
|
'app.selection.canDownload': app.canDownloadSelection,
|
||||||
'app.selection.notEmpty': app.hasSelection,
|
'app.selection.notEmpty': app.hasSelection,
|
||||||
|
'app.selection.canUnshare': app.canUnshareNodes,
|
||||||
|
'app.selection.canAddFavorite': app.canAddFavorite,
|
||||||
|
'app.selection.canRemoveFavorite': app.canRemoveFavorite,
|
||||||
|
'app.selection.first.canUpdate': app.canUpdateSelectedNode,
|
||||||
'app.selection.file': app.hasFileSelected,
|
'app.selection.file': app.hasFileSelected,
|
||||||
|
'app.selection.file.canShare': app.canShareFile,
|
||||||
|
'app.selection.library': app.hasLibrarySelected,
|
||||||
'app.selection.folder': app.hasFolderSelected,
|
'app.selection.folder': app.hasFolderSelected,
|
||||||
'app.selection.folder.canUpdate': app.canUpdateSelectedFolder,
|
'app.selection.folder.canUpdate': app.canUpdateSelectedFolder,
|
||||||
|
|
||||||
'app.navigation.folder.canCreate': app.canCreateFolder,
|
'app.navigation.folder.canCreate': app.canCreateFolder,
|
||||||
'app.navigation.isTrashcan': app.isTrashcan,
|
'app.navigation.folder.canUpload': app.canUpload,
|
||||||
'app.navigation.isNotTrashcan': app.isNotTrashcan
|
'app.navigation.isTrashcan': nav.isTrashcan,
|
||||||
|
'app.navigation.isNotTrashcan': nav.isNotTrashcan,
|
||||||
|
'app.navigation.isLibraries': nav.isLibraries,
|
||||||
|
'app.navigation.isNotLibraries': nav.isNotLibraries,
|
||||||
|
'app.navigation.isSharedFiles': nav.isSharedFiles,
|
||||||
|
'app.navigation.isNotSharedFiles': nav.isNotSharedFiles,
|
||||||
|
'app.navigation.isFavorites': nav.isFavorites,
|
||||||
|
'app.navigation.isNotFavorites': nav.isNotFavorites,
|
||||||
|
'app.navigation.isRecentFiles': nav.isRecentFiles,
|
||||||
|
'app.navigation.isNotRecentFiles': nav.isNotRecentFiles,
|
||||||
|
'app.navigation.isSearchResults': nav.isSearchResults,
|
||||||
|
'app.navigation.isNotSearchResults': nav.isNotSearchResults
|
||||||
});
|
});
|
||||||
|
|
||||||
resolve(true);
|
resolve(true);
|
||||||
@ -66,15 +89,13 @@ export function setupExtensions(extensions: ExtensionService): Function {
|
|||||||
imports: [CommonModule, CoreModule.forChild()],
|
imports: [CommonModule, CoreModule.forChild()],
|
||||||
declarations: [
|
declarations: [
|
||||||
ToolbarActionComponent,
|
ToolbarActionComponent,
|
||||||
CustomExtensionComponent,
|
ToolbarButtonComponent,
|
||||||
DemoButtonComponent
|
CustomExtensionComponent
|
||||||
],
|
],
|
||||||
exports: [
|
exports: [
|
||||||
ToolbarActionComponent,
|
ToolbarActionComponent,
|
||||||
|
ToolbarButtonComponent,
|
||||||
CustomExtensionComponent
|
CustomExtensionComponent
|
||||||
],
|
|
||||||
entryComponents: [
|
|
||||||
DemoButtonComponent
|
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
export class CoreExtensionsModule {
|
export class CoreExtensionsModule {
|
||||||
|
@ -23,63 +23,201 @@
|
|||||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Node } from 'alfresco-js-api';
|
|
||||||
import { RuleContext, RuleParameter } from '../rule.extensions';
|
import { RuleContext, RuleParameter } from '../rule.extensions';
|
||||||
|
import {
|
||||||
|
isNotTrashcan,
|
||||||
|
isNotSharedFiles,
|
||||||
|
isNotLibraries,
|
||||||
|
isFavorites,
|
||||||
|
isLibraries,
|
||||||
|
isTrashcan,
|
||||||
|
isSharedFiles,
|
||||||
|
isNotSearchResults
|
||||||
|
} from './navigation.evaluators';
|
||||||
|
|
||||||
export function isTrashcan(context: RuleContext, ...args: RuleParameter[]): boolean {
|
export function canAddFavorite(
|
||||||
const { url } = context.navigation;
|
context: RuleContext,
|
||||||
return url && url.startsWith('/trashcan');
|
...args: RuleParameter[]
|
||||||
|
): boolean {
|
||||||
|
if (!context.selection.isEmpty) {
|
||||||
|
if (
|
||||||
|
isFavorites(context, ...args) ||
|
||||||
|
isLibraries(context, ...args) ||
|
||||||
|
isTrashcan(context, ...args)
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
return context.selection.nodes.some(node => !node.entry.isFavorite);
|
||||||
export function isNotTrashcan(context: RuleContext, ...args: RuleParameter[]): boolean {
|
|
||||||
return !isTrashcan(context, ...args);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function hasSelection(context: RuleContext, ...args: RuleParameter[]): boolean {
|
|
||||||
const { selection } = context;
|
|
||||||
return selection && !selection.isEmpty;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function canCreateFolder(context: RuleContext, ...args: RuleParameter[]): boolean {
|
|
||||||
const folder = context.navigation.currentFolder;
|
|
||||||
if (folder) {
|
|
||||||
return nodeHasPermission(folder, 'create');
|
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function canDownloadSelection(context: RuleContext, ...args: RuleParameter[]): boolean {
|
export function canRemoveFavorite(
|
||||||
|
context: RuleContext,
|
||||||
|
...args: RuleParameter[]
|
||||||
|
): boolean {
|
||||||
|
if (!context.selection.isEmpty && !isTrashcan(context, ...args)) {
|
||||||
|
if (isFavorites(context, ...args)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return context.selection.nodes.every(node => node.entry.isFavorite);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function canShareFile(
|
||||||
|
context: RuleContext,
|
||||||
|
...args: RuleParameter[]
|
||||||
|
): boolean {
|
||||||
|
if (
|
||||||
|
isNotTrashcan(context, ...args) &&
|
||||||
|
isNotSharedFiles(context, ...args) &&
|
||||||
|
context.selection.file
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function canDeleteSelection(
|
||||||
|
context: RuleContext,
|
||||||
|
...args: RuleParameter[]
|
||||||
|
): boolean {
|
||||||
|
if (
|
||||||
|
isNotTrashcan(context, ...args) &&
|
||||||
|
isNotLibraries(context, ...args) &&
|
||||||
|
isNotSearchResults(context, ...args) &&
|
||||||
|
!context.selection.isEmpty
|
||||||
|
) {
|
||||||
|
// temp workaround for Search api
|
||||||
|
if (isFavorites(context, ...args)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// workaround for Shared Files
|
||||||
|
if (isSharedFiles(context, ...args)) {
|
||||||
|
return context.permissions.check(
|
||||||
|
context.selection.nodes,
|
||||||
|
['delete'],
|
||||||
|
{ target: 'allowableOperationsOnTarget' });
|
||||||
|
}
|
||||||
|
|
||||||
|
return context.permissions.check(context.selection.nodes, ['delete']);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function canUnshareNodes(
|
||||||
|
context: RuleContext,
|
||||||
|
...args: RuleParameter[]
|
||||||
|
): boolean {
|
||||||
if (!context.selection.isEmpty) {
|
if (!context.selection.isEmpty) {
|
||||||
return context.selection.nodes.every(node => {
|
return context.permissions.check(context.selection.nodes, ['delete'], {
|
||||||
return node.entry && (node.entry.isFile || node.entry.isFolder || !!node.entry.nodeId);
|
target: 'allowableOperationsOnTarget'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function hasFolderSelected(context: RuleContext, ...args: RuleParameter[]): boolean {
|
export function hasSelection(
|
||||||
|
context: RuleContext,
|
||||||
|
...args: RuleParameter[]
|
||||||
|
): boolean {
|
||||||
|
return !context.selection.isEmpty;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function canCreateFolder(
|
||||||
|
context: RuleContext,
|
||||||
|
...args: RuleParameter[]
|
||||||
|
): boolean {
|
||||||
|
const { currentFolder } = context.navigation;
|
||||||
|
if (currentFolder) {
|
||||||
|
return context.permissions.check(currentFolder, ['create']);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function canUpload(
|
||||||
|
context: RuleContext,
|
||||||
|
...args: RuleParameter[]
|
||||||
|
): boolean {
|
||||||
|
const { currentFolder } = context.navigation;
|
||||||
|
if (currentFolder) {
|
||||||
|
return context.permissions.check(currentFolder, ['create']);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function canDownloadSelection(
|
||||||
|
context: RuleContext,
|
||||||
|
...args: RuleParameter[]
|
||||||
|
): boolean {
|
||||||
|
if (!context.selection.isEmpty) {
|
||||||
|
return context.selection.nodes.every(node => {
|
||||||
|
return (
|
||||||
|
node.entry &&
|
||||||
|
(node.entry.isFile ||
|
||||||
|
node.entry.isFolder ||
|
||||||
|
!!node.entry.nodeId)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function hasFolderSelected(
|
||||||
|
context: RuleContext,
|
||||||
|
...args: RuleParameter[]
|
||||||
|
): boolean {
|
||||||
const folder = context.selection.folder;
|
const folder = context.selection.folder;
|
||||||
return folder ? true : false;
|
return folder ? true : false;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function hasFileSelected(context: RuleContext, ...args: RuleParameter[]): boolean {
|
export function hasLibrarySelected(
|
||||||
|
context: RuleContext,
|
||||||
|
...args: RuleParameter[]
|
||||||
|
): boolean {
|
||||||
|
const library = context.selection.library;
|
||||||
|
return library ? true : false;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function hasFileSelected(
|
||||||
|
context: RuleContext,
|
||||||
|
...args: RuleParameter[]
|
||||||
|
): boolean {
|
||||||
const file = context.selection.file;
|
const file = context.selection.file;
|
||||||
return file ? true : false;
|
return file ? true : false;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function canUpdateSelectedFolder(context: RuleContext, ...args: RuleParameter[]): boolean {
|
export function canUpdateSelectedNode(
|
||||||
const folder = context.selection.folder;
|
context: RuleContext,
|
||||||
if (folder && folder.entry) {
|
...args: RuleParameter[]
|
||||||
return nodeHasPermission(folder.entry, 'update');
|
): boolean {
|
||||||
|
if (context.selection && !context.selection.isEmpty) {
|
||||||
|
const node = context.selection.first;
|
||||||
|
|
||||||
|
if (node.entry.hasOwnProperty('allowableOperationsOnTarget')) {
|
||||||
|
return context.permissions.check(node, ['update'], {
|
||||||
|
target: 'allowableOperationsOnTarget'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return context.permissions.check(node, ['update']);
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function nodeHasPermission(node: Node, permission: string): boolean {
|
export function canUpdateSelectedFolder(
|
||||||
if (node && permission) {
|
context: RuleContext,
|
||||||
const allowableOperations = node.allowableOperations || [];
|
...args: RuleParameter[]
|
||||||
return allowableOperations.includes(permission);
|
): boolean {
|
||||||
|
const { folder } = context.selection;
|
||||||
|
if (folder) {
|
||||||
|
return (
|
||||||
|
// workaround for Search Api
|
||||||
|
isFavorites(context, ...args) ||
|
||||||
|
context.permissions.check(folder.entry, ['update'])
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
116
src/app/extensions/evaluators/navigation.evaluators.ts
Normal file
116
src/app/extensions/evaluators/navigation.evaluators.ts
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
/*!
|
||||||
|
* @license
|
||||||
|
* Alfresco Example Content Application
|
||||||
|
*
|
||||||
|
* Copyright (C) 2005 - 2018 Alfresco Software Limited
|
||||||
|
*
|
||||||
|
* This file is part of the Alfresco Example Content Application.
|
||||||
|
* If the software was purchased under a paid Alfresco license, the terms of
|
||||||
|
* the paid license agreement will prevail. Otherwise, the software is
|
||||||
|
* provided under the following open source license terms:
|
||||||
|
*
|
||||||
|
* The Alfresco Example Content Application is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* The Alfresco Example Content Application is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public License
|
||||||
|
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { RuleContext, RuleParameter } from '../rule.extensions';
|
||||||
|
|
||||||
|
export function isFavorites(
|
||||||
|
context: RuleContext,
|
||||||
|
...args: RuleParameter[]
|
||||||
|
): boolean {
|
||||||
|
const { url } = context.navigation;
|
||||||
|
return url && url.startsWith('/favorites');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isNotFavorites(
|
||||||
|
context: RuleContext,
|
||||||
|
...args: RuleParameter[]
|
||||||
|
): boolean {
|
||||||
|
return !isFavorites(context, ...args);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isSharedFiles(
|
||||||
|
context: RuleContext,
|
||||||
|
...args: RuleParameter[]
|
||||||
|
): boolean {
|
||||||
|
const { url } = context.navigation;
|
||||||
|
return url && url.startsWith('/shared');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isNotSharedFiles(
|
||||||
|
context: RuleContext,
|
||||||
|
...args: RuleParameter[]
|
||||||
|
): boolean {
|
||||||
|
return !isSharedFiles(context, ...args);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isTrashcan(
|
||||||
|
context: RuleContext,
|
||||||
|
...args: RuleParameter[]
|
||||||
|
): boolean {
|
||||||
|
const { url } = context.navigation;
|
||||||
|
return url && url.startsWith('/trashcan');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isNotTrashcan(
|
||||||
|
context: RuleContext,
|
||||||
|
...args: RuleParameter[]
|
||||||
|
): boolean {
|
||||||
|
return !isTrashcan(context, ...args);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isLibraries(
|
||||||
|
context: RuleContext,
|
||||||
|
...args: RuleParameter[]
|
||||||
|
): boolean {
|
||||||
|
const { url } = context.navigation;
|
||||||
|
return url && url.endsWith('/libraries');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isNotLibraries(
|
||||||
|
context: RuleContext,
|
||||||
|
...args: RuleParameter[]
|
||||||
|
): boolean {
|
||||||
|
return !isLibraries(context, ...args);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isRecentFiles(
|
||||||
|
context: RuleContext,
|
||||||
|
...args: RuleParameter[]
|
||||||
|
): boolean {
|
||||||
|
const { url } = context.navigation;
|
||||||
|
return url && url.startsWith('/recent-files');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isNotRecentFiles(
|
||||||
|
context: RuleContext,
|
||||||
|
...args: RuleParameter[]
|
||||||
|
): boolean {
|
||||||
|
return !isRecentFiles(context, ...args);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isSearchResults(
|
||||||
|
context: RuleContext,
|
||||||
|
...args: RuleParameter[]
|
||||||
|
): boolean {
|
||||||
|
const { url } = context.navigation;
|
||||||
|
return url && url.startsWith('/search');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isNotSearchResults(
|
||||||
|
context: RuleContext,
|
||||||
|
...args: RuleParameter[]
|
||||||
|
): boolean {
|
||||||
|
return !isSearchResults(context, ...args);
|
||||||
|
}
|
@ -41,6 +41,7 @@ export interface ExtensionConfig {
|
|||||||
create?: Array<ContentActionRef>;
|
create?: Array<ContentActionRef>;
|
||||||
viewer?: {
|
viewer?: {
|
||||||
openWith?: Array<ContentActionRef>;
|
openWith?: Array<ContentActionRef>;
|
||||||
|
actions?: Array<ContentActionRef>;
|
||||||
};
|
};
|
||||||
navbar?: Array<NavBarGroupRef>;
|
navbar?: Array<NavBarGroupRef>;
|
||||||
content?: {
|
content?: {
|
||||||
|
@ -36,6 +36,7 @@ import { RouteRef } from './routing.extensions';
|
|||||||
import { RuleContext, RuleRef, RuleEvaluator } from './rule.extensions';
|
import { RuleContext, RuleRef, RuleEvaluator } from './rule.extensions';
|
||||||
import { ActionRef, ContentActionRef, ContentActionType } from './action.extensions';
|
import { ActionRef, ContentActionRef, ContentActionType } from './action.extensions';
|
||||||
import * as core from './evaluators/core.evaluators';
|
import * as core from './evaluators/core.evaluators';
|
||||||
|
import { NodePermissionService } from '../services/node-permission.service';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ExtensionService implements RuleContext {
|
export class ExtensionService implements RuleContext {
|
||||||
@ -52,6 +53,7 @@ export class ExtensionService implements RuleContext {
|
|||||||
actions: Array<ActionRef> = [];
|
actions: Array<ActionRef> = [];
|
||||||
|
|
||||||
contentActions: Array<ContentActionRef> = [];
|
contentActions: Array<ContentActionRef> = [];
|
||||||
|
viewerActions: Array<ContentActionRef> = [];
|
||||||
openWithActions: Array<ContentActionRef> = [];
|
openWithActions: Array<ContentActionRef> = [];
|
||||||
createActions: Array<ContentActionRef> = [];
|
createActions: Array<ContentActionRef> = [];
|
||||||
navbar: Array<NavBarGroupRef> = [];
|
navbar: Array<NavBarGroupRef> = [];
|
||||||
@ -63,7 +65,10 @@ export class ExtensionService implements RuleContext {
|
|||||||
selection: SelectionState;
|
selection: SelectionState;
|
||||||
navigation: NavigationState;
|
navigation: NavigationState;
|
||||||
|
|
||||||
constructor(private http: HttpClient, private store: Store<AppStore>) {
|
constructor(
|
||||||
|
private http: HttpClient,
|
||||||
|
private store: Store<AppStore>,
|
||||||
|
public permissions: NodePermissionService) {
|
||||||
|
|
||||||
this.evaluators = {
|
this.evaluators = {
|
||||||
'core.every': core.every,
|
'core.every': core.every,
|
||||||
@ -118,6 +123,7 @@ export class ExtensionService implements RuleContext {
|
|||||||
this.actions = this.loadActions(config);
|
this.actions = this.loadActions(config);
|
||||||
this.routes = this.loadRoutes(config);
|
this.routes = this.loadRoutes(config);
|
||||||
this.contentActions = this.loadContentActions(config);
|
this.contentActions = this.loadContentActions(config);
|
||||||
|
this.viewerActions = this.loadViewerActions(config);
|
||||||
this.openWithActions = this.loadViewerOpenWith(config);
|
this.openWithActions = this.loadViewerOpenWith(config);
|
||||||
this.createActions = this.loadCreateActions(config);
|
this.createActions = this.loadCreateActions(config);
|
||||||
this.navbar = this.loadNavBar(config);
|
this.navbar = this.loadNavBar(config);
|
||||||
@ -158,6 +164,15 @@ export class ExtensionService implements RuleContext {
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected loadViewerActions(config: ExtensionConfig) {
|
||||||
|
if (config && config.features && config.features.viewer) {
|
||||||
|
return (config.features.viewer.actions || []).sort(
|
||||||
|
this.sortByOrder
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
protected loadNavBar(config: ExtensionConfig): any {
|
protected loadNavBar(config: ExtensionConfig): any {
|
||||||
if (config && config.features) {
|
if (config && config.features) {
|
||||||
return (config.features.navbar || [])
|
return (config.features.navbar || [])
|
||||||
@ -296,7 +311,6 @@ export class ExtensionService implements RuleContext {
|
|||||||
return this.contentActions
|
return this.contentActions
|
||||||
.filter(this.filterEnabled)
|
.filter(this.filterEnabled)
|
||||||
.filter(action => this.filterByRules(action))
|
.filter(action => this.filterByRules(action))
|
||||||
.reduce(this.reduceSeparators, [])
|
|
||||||
.map(action => {
|
.map(action => {
|
||||||
if (action.type === ContentActionType.menu) {
|
if (action.type === ContentActionType.menu) {
|
||||||
const copy = this.copyAction(action);
|
const copy = this.copyAction(action);
|
||||||
@ -311,7 +325,14 @@ export class ExtensionService implements RuleContext {
|
|||||||
}
|
}
|
||||||
return action;
|
return action;
|
||||||
})
|
})
|
||||||
.reduce(this.reduceEmptyMenus, []);
|
.reduce(this.reduceEmptyMenus, [])
|
||||||
|
.reduce(this.reduceSeparators, []);
|
||||||
|
}
|
||||||
|
|
||||||
|
getViewerActions(): Array<ContentActionRef> {
|
||||||
|
return this.viewerActions
|
||||||
|
.filter(this.filterEnabled)
|
||||||
|
.filter(action => this.filterByRules(action));
|
||||||
}
|
}
|
||||||
|
|
||||||
reduceSeparators(
|
reduceSeparators(
|
||||||
@ -320,6 +341,12 @@ export class ExtensionService implements RuleContext {
|
|||||||
i: number,
|
i: number,
|
||||||
arr: ContentActionRef[]
|
arr: ContentActionRef[]
|
||||||
): ContentActionRef[] {
|
): ContentActionRef[] {
|
||||||
|
// remove leading separator
|
||||||
|
if (i === 0) {
|
||||||
|
if (arr[i].type === ContentActionType.separator) {
|
||||||
|
return acc;
|
||||||
|
}
|
||||||
|
}
|
||||||
// remove duplicate separators
|
// remove duplicate separators
|
||||||
if (i > 0) {
|
if (i > 0) {
|
||||||
const prev = arr[i - 1];
|
const prev = arr[i - 1];
|
||||||
|
@ -23,14 +23,6 @@
|
|||||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Component } from '@angular/core';
|
export interface NodePermissions {
|
||||||
|
check(source: any, permissions: string[], options?: any): boolean;
|
||||||
@Component({
|
}
|
||||||
selector: 'app-demo-button',
|
|
||||||
template: `
|
|
||||||
<button color="primary" mat-icon-button>
|
|
||||||
<mat-icon>extension</mat-icon>
|
|
||||||
</button>
|
|
||||||
`
|
|
||||||
})
|
|
||||||
export class DemoButtonComponent {}
|
|
@ -25,6 +25,7 @@
|
|||||||
|
|
||||||
import { SelectionState } from '../store/states';
|
import { SelectionState } from '../store/states';
|
||||||
import { NavigationState } from '../store/states/navigation.state';
|
import { NavigationState } from '../store/states/navigation.state';
|
||||||
|
import { NodePermissions } from './permission.extensions';
|
||||||
|
|
||||||
export type RuleEvaluator = (context: RuleContext, ...args: any[]) => boolean;
|
export type RuleEvaluator = (context: RuleContext, ...args: any[]) => boolean;
|
||||||
|
|
||||||
@ -32,6 +33,7 @@ export interface RuleContext {
|
|||||||
selection: SelectionState;
|
selection: SelectionState;
|
||||||
navigation: NavigationState;
|
navigation: NavigationState;
|
||||||
evaluators: { [key: string]: RuleEvaluator };
|
evaluators: { [key: string]: RuleEvaluator };
|
||||||
|
permissions: NodePermissions;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class RuleRef {
|
export class RuleRef {
|
||||||
|
@ -39,7 +39,8 @@ import {
|
|||||||
SearchRequest,
|
SearchRequest,
|
||||||
ResultSetPaging,
|
ResultSetPaging,
|
||||||
SiteBody,
|
SiteBody,
|
||||||
SiteEntry
|
SiteEntry,
|
||||||
|
FavoriteBody
|
||||||
} from 'alfresco-js-api';
|
} from 'alfresco-js-api';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
@ -242,4 +243,30 @@ export class ContentApiService {
|
|||||||
this.api.sitesApi.getSite(siteId, opts)
|
this.api.sitesApi.getSite(siteId, opts)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
addFavorite(nodes: Array<MinimalNodeEntity>): Observable<any> {
|
||||||
|
const payload: FavoriteBody[] = nodes.map(node => {
|
||||||
|
const { isFolder, nodeId, id } = node.entry;
|
||||||
|
const siteId = node.entry['guid'];
|
||||||
|
const type = siteId ? 'site' : isFolder ? 'folder' : 'file';
|
||||||
|
const guid = siteId || nodeId || id;
|
||||||
|
|
||||||
|
return {
|
||||||
|
target: {
|
||||||
|
[type]: {
|
||||||
|
guid
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
return Observable.from(this.api.favoritesApi.addFavorite('-me-', <any>payload));
|
||||||
|
}
|
||||||
|
|
||||||
|
removeFavorite(nodes: Array<MinimalNodeEntity>): Observable<any> {
|
||||||
|
return Observable.from(Promise.all(nodes.map(node => {
|
||||||
|
const id = node.entry.nodeId || node.entry.id;
|
||||||
|
return this.api.favoritesApi.removeFavoriteSite('-me-', id);
|
||||||
|
})));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
1275
src/app/services/content-management.service.spec.ts
Normal file
1275
src/app/services/content-management.service.spec.ts
Normal file
File diff suppressed because it is too large
Load Diff
@ -25,11 +25,11 @@
|
|||||||
|
|
||||||
import { Subject, Observable } from 'rxjs/Rx';
|
import { Subject, Observable } from 'rxjs/Rx';
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { MatDialog } from '@angular/material';
|
import { MatDialog, MatSnackBar } from '@angular/material';
|
||||||
import { FolderDialogComponent, ConfirmDialogComponent } 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 { SnackbarErrorAction, SnackbarInfoAction, SnackbarAction, SnackbarWarningAction,
|
||||||
NavigateRouteAction, SnackbarUserAction } from '../store/actions';
|
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 {
|
||||||
@ -43,6 +43,11 @@ import {
|
|||||||
import { NodePermissionService } from './node-permission.service';
|
import { NodePermissionService } from './node-permission.service';
|
||||||
import { NodeInfo, DeletedNodeInfo, DeleteStatus } from '../store/models';
|
import { NodeInfo, DeletedNodeInfo, DeleteStatus } from '../store/models';
|
||||||
import { ContentApiService } from './content-api.service';
|
import { ContentApiService } from './content-api.service';
|
||||||
|
import { sharedUrl } from '../store/selectors/app.selectors';
|
||||||
|
import { NodeActionsService } from './node-actions.service';
|
||||||
|
import { TranslationService } from '@alfresco/adf-core';
|
||||||
|
import { NodePermissionsDialogComponent } from '../dialogs/node-permissions/node-permissions.dialog';
|
||||||
|
import { NodeVersionsDialogComponent } from '../dialogs/node-versions/node-versions.dialog';
|
||||||
|
|
||||||
interface RestoredNode {
|
interface RestoredNode {
|
||||||
status: number;
|
status: number;
|
||||||
@ -61,14 +66,112 @@ export class ContentManagementService {
|
|||||||
libraryDeleted = new Subject<string>();
|
libraryDeleted = new Subject<string>();
|
||||||
libraryCreated = new Subject<SiteEntry>();
|
libraryCreated = new Subject<SiteEntry>();
|
||||||
linksUnshared = new Subject<any>();
|
linksUnshared = new Subject<any>();
|
||||||
|
favoriteAdded = new Subject<Array<MinimalNodeEntity>>();
|
||||||
|
favoriteRemoved = new Subject<Array<MinimalNodeEntity>>();
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private store: Store<AppStore>,
|
private store: Store<AppStore>,
|
||||||
private contentApi: ContentApiService,
|
private contentApi: ContentApiService,
|
||||||
private permission: NodePermissionService,
|
private permission: NodePermissionService,
|
||||||
private dialogRef: MatDialog
|
private dialogRef: MatDialog,
|
||||||
|
private nodeActionsService: NodeActionsService,
|
||||||
|
private translation: TranslationService,
|
||||||
|
private snackBar: MatSnackBar
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
|
addFavorite(nodes: Array<MinimalNodeEntity>) {
|
||||||
|
if (nodes && nodes.length > 0) {
|
||||||
|
this.contentApi.addFavorite(nodes).subscribe(() => {
|
||||||
|
nodes.forEach(node => {
|
||||||
|
node.entry.isFavorite = true;
|
||||||
|
});
|
||||||
|
this.store.dispatch(new SetSelectedNodesAction(nodes));
|
||||||
|
this.favoriteAdded.next(nodes);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
removeFavorite(nodes: Array<MinimalNodeEntity>) {
|
||||||
|
if (nodes && nodes.length > 0) {
|
||||||
|
this.contentApi.removeFavorite(nodes).subscribe(() => {
|
||||||
|
nodes.forEach(node => {
|
||||||
|
node.entry.isFavorite = false;
|
||||||
|
});
|
||||||
|
this.store.dispatch(new SetSelectedNodesAction(nodes));
|
||||||
|
this.favoriteRemoved.next(nodes);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
managePermissions(node: MinimalNodeEntity): void {
|
||||||
|
if (node && node.entry) {
|
||||||
|
const { nodeId, id } = node.entry;
|
||||||
|
const siteId = node.entry['guid'];
|
||||||
|
const targetId = siteId || nodeId || id;
|
||||||
|
|
||||||
|
if (targetId) {
|
||||||
|
this.dialogRef.open(NodePermissionsDialogComponent, {
|
||||||
|
data: { nodeId: targetId },
|
||||||
|
panelClass: 'aca-permissions-dialog-panel',
|
||||||
|
width: '730px'
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.store.dispatch(
|
||||||
|
new SnackbarErrorAction('APP.MESSAGES.ERRORS.PERMISSION')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
manageVersions(node: MinimalNodeEntity) {
|
||||||
|
if (node && node.entry) {
|
||||||
|
if (node.entry.nodeId) {
|
||||||
|
this.contentApi
|
||||||
|
.getNodeInfo(node.entry.nodeId)
|
||||||
|
.subscribe(entry => {
|
||||||
|
this.openVersionManagerDialog(entry);
|
||||||
|
});
|
||||||
|
|
||||||
|
} else {
|
||||||
|
this.openVersionManagerDialog(node.entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private openVersionManagerDialog(node: MinimalNodeEntryEntity) {
|
||||||
|
// workaround Shared
|
||||||
|
if (node.isFile || node.nodeId) {
|
||||||
|
this.dialogRef.open(NodeVersionsDialogComponent, {
|
||||||
|
data: { node },
|
||||||
|
panelClass: 'adf-version-manager-dialog-panel',
|
||||||
|
width: '630px'
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.store.dispatch(
|
||||||
|
new SnackbarErrorAction('APP.MESSAGES.ERRORS.PERMISSION')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
shareNode(node: MinimalNodeEntity): void {
|
||||||
|
if (node && node.entry && node.entry.isFile) {
|
||||||
|
|
||||||
|
this.store
|
||||||
|
.select(sharedUrl)
|
||||||
|
.take(1)
|
||||||
|
.subscribe(baseShareUrl => {
|
||||||
|
this.dialogRef.open(ShareDialogComponent, {
|
||||||
|
width: '600px',
|
||||||
|
disableClose: true,
|
||||||
|
data: {
|
||||||
|
node,
|
||||||
|
baseShareUrl
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
createFolder(parentNodeId: string) {
|
createFolder(parentNodeId: string) {
|
||||||
const dialogInstance = this.dialogRef.open(FolderDialogComponent, {
|
const dialogInstance = this.dialogRef.open(FolderDialogComponent, {
|
||||||
data: {
|
data: {
|
||||||
@ -129,12 +232,30 @@ export class ContentManagementService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
canDeleteNode(node: MinimalNodeEntity | Node): boolean {
|
deleteLibrary(id: string): void {
|
||||||
return this.permission.check(node, ['delete']);
|
this.contentApi.deleteSite(id).subscribe(
|
||||||
|
() => {
|
||||||
|
this.libraryDeleted.next(id);
|
||||||
|
this.store.dispatch(
|
||||||
|
new SnackbarInfoAction(
|
||||||
|
'APP.MESSAGES.INFO.LIBRARY_DELETED'
|
||||||
|
)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
this.store.dispatch(
|
||||||
|
new SnackbarErrorAction(
|
||||||
|
'APP.MESSAGES.ERRORS.DELETE_LIBRARY_FAILED'
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
canDeleteNodes(nodes: MinimalNodeEntity[]): boolean {
|
async unshareNodes(links: Array<MinimalNodeEntity>) {
|
||||||
return this.permission.check(nodes, ['delete']);
|
const promises = links.map(link => this.contentApi.deleteSharedLink(link.entry.id).toPromise());
|
||||||
|
await Promise.all(promises);
|
||||||
|
this.linksUnshared.next();
|
||||||
}
|
}
|
||||||
|
|
||||||
canUpdateNode(node: MinimalNodeEntity | Node): boolean {
|
canUpdateNode(node: MinimalNodeEntity | Node): boolean {
|
||||||
@ -145,18 +266,6 @@ export class ContentManagementService {
|
|||||||
return this.permission.check(folderNode, ['create']);
|
return this.permission.check(folderNode, ['create']);
|
||||||
}
|
}
|
||||||
|
|
||||||
canDeleteSharedNodes(sharedLinks: MinimalNodeEntity[]): boolean {
|
|
||||||
return this.permission.check(sharedLinks, ['delete'], {
|
|
||||||
target: 'allowableOperationsOnTarget'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
canUpdateSharedNode(sharedLink: MinimalNodeEntity): boolean {
|
|
||||||
return this.permission.check(sharedLink, ['update'], {
|
|
||||||
target: 'allowableOperationsOnTarget'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
purgeDeletedNodes(nodes: MinimalNodeEntity[]) {
|
purgeDeletedNodes(nodes: MinimalNodeEntity[]) {
|
||||||
if (!nodes || nodes.length === 0) {
|
if (!nodes || nodes.length === 0) {
|
||||||
return;
|
return;
|
||||||
@ -226,6 +335,271 @@ export class ContentManagementService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
copyNodes(nodes: Array<MinimalNodeEntity>) {
|
||||||
|
Observable.zip(
|
||||||
|
this.nodeActionsService.copyNodes(nodes),
|
||||||
|
this.nodeActionsService.contentCopied
|
||||||
|
).subscribe(
|
||||||
|
result => {
|
||||||
|
const [operationResult, newItems] = result;
|
||||||
|
this.showCopyMessage(operationResult, nodes, newItems);
|
||||||
|
},
|
||||||
|
error => {
|
||||||
|
this.showCopyMessage(error, nodes);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private showCopyMessage(
|
||||||
|
info: any,
|
||||||
|
nodes: Array<MinimalNodeEntity>,
|
||||||
|
newItems?: Array<MinimalNodeEntity>
|
||||||
|
) {
|
||||||
|
const numberOfCopiedItems = newItems ? newItems.length : 0;
|
||||||
|
const failedItems = nodes.length - numberOfCopiedItems;
|
||||||
|
|
||||||
|
let i18nMessageString = 'APP.MESSAGES.ERRORS.GENERIC';
|
||||||
|
|
||||||
|
if (typeof info === 'string') {
|
||||||
|
if (info.toLowerCase().indexOf('succes') !== -1) {
|
||||||
|
let i18MessageSuffix;
|
||||||
|
|
||||||
|
if (failedItems) {
|
||||||
|
if (numberOfCopiedItems) {
|
||||||
|
i18MessageSuffix =
|
||||||
|
numberOfCopiedItems === 1
|
||||||
|
? 'PARTIAL_SINGULAR'
|
||||||
|
: 'PARTIAL_PLURAL';
|
||||||
|
} else {
|
||||||
|
i18MessageSuffix =
|
||||||
|
failedItems === 1 ? 'FAIL_SINGULAR' : 'FAIL_PLURAL';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
i18MessageSuffix =
|
||||||
|
numberOfCopiedItems === 1 ? 'SINGULAR' : 'PLURAL';
|
||||||
|
}
|
||||||
|
|
||||||
|
i18nMessageString = `APP.MESSAGES.INFO.NODE_COPY.${i18MessageSuffix}`;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
const {
|
||||||
|
error: { statusCode }
|
||||||
|
} = JSON.parse(info.message);
|
||||||
|
|
||||||
|
if (statusCode === 403) {
|
||||||
|
i18nMessageString = 'APP.MESSAGES.ERRORS.PERMISSION';
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
|
||||||
|
const undo =
|
||||||
|
numberOfCopiedItems > 0
|
||||||
|
? this.translation.instant('APP.ACTIONS.UNDO')
|
||||||
|
: '';
|
||||||
|
|
||||||
|
const message = this.translation.instant(i18nMessageString, {
|
||||||
|
success: numberOfCopiedItems,
|
||||||
|
failed: failedItems
|
||||||
|
});
|
||||||
|
|
||||||
|
this.snackBar
|
||||||
|
.open(message, undo, {
|
||||||
|
panelClass: 'info-snackbar',
|
||||||
|
duration: 3000
|
||||||
|
})
|
||||||
|
.onAction()
|
||||||
|
.subscribe(() => this.undoCopyNodes(newItems));
|
||||||
|
}
|
||||||
|
|
||||||
|
private undoCopyNodes(nodes: MinimalNodeEntity[]) {
|
||||||
|
const batch = this.nodeActionsService
|
||||||
|
.flatten(nodes)
|
||||||
|
.filter(item => item.entry)
|
||||||
|
.map(item =>
|
||||||
|
this.contentApi.deleteNode(item.entry.id, { permanent: true })
|
||||||
|
);
|
||||||
|
|
||||||
|
Observable.forkJoin(...batch).subscribe(
|
||||||
|
() => {
|
||||||
|
this.nodesDeleted.next(null);
|
||||||
|
},
|
||||||
|
error => {
|
||||||
|
let i18nMessageString = 'APP.MESSAGES.ERRORS.GENERIC';
|
||||||
|
|
||||||
|
let errorJson = null;
|
||||||
|
try {
|
||||||
|
errorJson = JSON.parse(error.message);
|
||||||
|
} catch {}
|
||||||
|
|
||||||
|
if (
|
||||||
|
errorJson &&
|
||||||
|
errorJson.error &&
|
||||||
|
errorJson.error.statusCode === 403
|
||||||
|
) {
|
||||||
|
i18nMessageString = 'APP.MESSAGES.ERRORS.PERMISSION';
|
||||||
|
}
|
||||||
|
|
||||||
|
this.store.dispatch(new SnackbarErrorAction(i18nMessageString));
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
moveNodes(nodes: Array<MinimalNodeEntity>) {
|
||||||
|
const permissionForMove = '!';
|
||||||
|
|
||||||
|
Observable.zip(
|
||||||
|
this.nodeActionsService.moveNodes(nodes, permissionForMove),
|
||||||
|
this.nodeActionsService.contentMoved
|
||||||
|
).subscribe(
|
||||||
|
(result) => {
|
||||||
|
const [ operationResult, moveResponse ] = result;
|
||||||
|
this.showMoveMessage(nodes, operationResult, moveResponse);
|
||||||
|
|
||||||
|
this.nodesMoved.next(null);
|
||||||
|
},
|
||||||
|
(error) => {
|
||||||
|
this.showMoveMessage(nodes, error);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private undoMoveNodes(moveResponse, selectionParentId) {
|
||||||
|
const movedNodes = (moveResponse && moveResponse['succeeded']) ? moveResponse['succeeded'] : [];
|
||||||
|
const partiallyMovedNodes = (moveResponse && moveResponse['partiallySucceeded']) ? moveResponse['partiallySucceeded'] : [];
|
||||||
|
|
||||||
|
const restoreDeletedNodesBatch = this.nodeActionsService.moveDeletedEntries
|
||||||
|
.map((folderEntry) => {
|
||||||
|
return this.contentApi
|
||||||
|
.restoreNode(folderEntry.nodeId || folderEntry.id)
|
||||||
|
.map(node => node.entry);
|
||||||
|
});
|
||||||
|
|
||||||
|
Observable.zip(...restoreDeletedNodesBatch, Observable.of(null))
|
||||||
|
.flatMap(() => {
|
||||||
|
|
||||||
|
const nodesToBeMovedBack = [...partiallyMovedNodes, ...movedNodes];
|
||||||
|
|
||||||
|
const revertMoveBatch = this.nodeActionsService
|
||||||
|
.flatten(nodesToBeMovedBack)
|
||||||
|
.filter(node => node.entry || (node.itemMoved && node.itemMoved.entry))
|
||||||
|
.map((node) => {
|
||||||
|
if (node.itemMoved) {
|
||||||
|
return this.nodeActionsService.moveNodeAction(node.itemMoved.entry, node.initialParentId);
|
||||||
|
} else {
|
||||||
|
return this.nodeActionsService.moveNodeAction(node.entry, selectionParentId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return Observable.zip(...revertMoveBatch, Observable.of(null));
|
||||||
|
})
|
||||||
|
.subscribe(
|
||||||
|
() => {
|
||||||
|
this.nodesMoved.next(null);
|
||||||
|
},
|
||||||
|
error => {
|
||||||
|
let message = 'APP.MESSAGES.ERRORS.GENERIC';
|
||||||
|
|
||||||
|
let errorJson = null;
|
||||||
|
try {
|
||||||
|
errorJson = JSON.parse(error.message);
|
||||||
|
} catch {}
|
||||||
|
|
||||||
|
if (errorJson && errorJson.error && errorJson.error.statusCode === 403) {
|
||||||
|
message = 'APP.MESSAGES.ERRORS.PERMISSION';
|
||||||
|
}
|
||||||
|
|
||||||
|
this.store.dispatch(new SnackbarErrorAction(message));
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteNodes(items: MinimalNodeEntity[]): void {
|
||||||
|
const batch: Observable<DeletedNodeInfo>[] = [];
|
||||||
|
|
||||||
|
items.forEach(node => {
|
||||||
|
batch.push(this.deleteNode(node));
|
||||||
|
});
|
||||||
|
|
||||||
|
Observable.forkJoin(...batch).subscribe((data: DeletedNodeInfo[]) => {
|
||||||
|
const status = this.processStatus(data);
|
||||||
|
const message = this.getDeleteMessage(status);
|
||||||
|
|
||||||
|
if (message && status.someSucceeded) {
|
||||||
|
message.duration = 10000;
|
||||||
|
message.userAction = new SnackbarUserAction(
|
||||||
|
'APP.ACTIONS.UNDO',
|
||||||
|
new UndoDeleteNodesAction([...status.success])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.store.dispatch(message);
|
||||||
|
|
||||||
|
if (status.someSucceeded) {
|
||||||
|
this.nodesDeleted.next();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
undoDeleteNodes(items: DeletedNodeInfo[]): void {
|
||||||
|
const batch: Observable<DeletedNodeInfo>[] = [];
|
||||||
|
|
||||||
|
items.forEach(item => {
|
||||||
|
batch.push(this.undoDeleteNode(item));
|
||||||
|
});
|
||||||
|
|
||||||
|
Observable.forkJoin(...batch).subscribe(data => {
|
||||||
|
const processedData = this.processStatus(data);
|
||||||
|
|
||||||
|
if (processedData.fail.length) {
|
||||||
|
const message = this.getUndoDeleteMessage(processedData);
|
||||||
|
this.store.dispatch(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (processedData.someSucceeded) {
|
||||||
|
this.nodesRestored.next();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private undoDeleteNode(item: DeletedNodeInfo): Observable<DeletedNodeInfo> {
|
||||||
|
const { id, name } = item;
|
||||||
|
|
||||||
|
return this.contentApi
|
||||||
|
.restoreNode(id)
|
||||||
|
.map(() => {
|
||||||
|
return {
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
status: 1
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.catch((error: any) => {
|
||||||
|
return Observable.of({
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
status: 0
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private getUndoDeleteMessage(status: DeleteStatus): SnackbarAction {
|
||||||
|
if (status.someFailed && !status.oneFailed) {
|
||||||
|
return new SnackbarErrorAction(
|
||||||
|
'APP.MESSAGES.ERRORS.NODE_RESTORE_PLURAL',
|
||||||
|
{ number: status.fail.length }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status.oneFailed) {
|
||||||
|
return new SnackbarErrorAction('APP.MESSAGES.ERRORS.NODE_RESTORE', {
|
||||||
|
name: status.fail[0].name
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
private restoreNode(node: MinimalNodeEntity): Observable<RestoredNode> {
|
private restoreNode(node: MinimalNodeEntity): Observable<RestoredNode> {
|
||||||
const { entry } = node;
|
const { entry } = node;
|
||||||
|
|
||||||
@ -457,4 +831,170 @@ export class ContentManagementService {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private deleteNode(node: MinimalNodeEntity): Observable<DeletedNodeInfo> {
|
||||||
|
const { name } = node.entry;
|
||||||
|
const id = node.entry.nodeId || node.entry.id;
|
||||||
|
|
||||||
|
|
||||||
|
return this.contentApi
|
||||||
|
.deleteNode(id)
|
||||||
|
.map(() => {
|
||||||
|
return {
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
status: 1
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
return Observable.of({
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
status: 0
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private getDeleteMessage(status: DeleteStatus): SnackbarAction {
|
||||||
|
if (status.allFailed && !status.oneFailed) {
|
||||||
|
return new SnackbarErrorAction(
|
||||||
|
'APP.MESSAGES.ERRORS.NODE_DELETION_PLURAL',
|
||||||
|
{ number: status.fail.length }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status.allSucceeded && !status.oneSucceeded) {
|
||||||
|
return new SnackbarInfoAction(
|
||||||
|
'APP.MESSAGES.INFO.NODE_DELETION.PLURAL',
|
||||||
|
{ number: status.success.length }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status.someFailed && status.someSucceeded && !status.oneSucceeded) {
|
||||||
|
return new SnackbarWarningAction(
|
||||||
|
'APP.MESSAGES.INFO.NODE_DELETION.PARTIAL_PLURAL',
|
||||||
|
{
|
||||||
|
success: status.success.length,
|
||||||
|
failed: status.fail.length
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status.someFailed && status.oneSucceeded) {
|
||||||
|
return new SnackbarWarningAction(
|
||||||
|
'APP.MESSAGES.INFO.NODE_DELETION.PARTIAL_SINGULAR',
|
||||||
|
{
|
||||||
|
success: status.success.length,
|
||||||
|
failed: status.fail.length
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status.oneFailed && !status.someSucceeded) {
|
||||||
|
return new SnackbarErrorAction(
|
||||||
|
'APP.MESSAGES.ERRORS.NODE_DELETION',
|
||||||
|
{ name: status.fail[0].name }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status.oneSucceeded && !status.someFailed) {
|
||||||
|
return new SnackbarInfoAction(
|
||||||
|
'APP.MESSAGES.INFO.NODE_DELETION.SINGULAR',
|
||||||
|
{ name: status.success[0].name }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private showMoveMessage(nodes: Array<MinimalNodeEntity>, info: any, 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 partialSuccessMessage = '';
|
||||||
|
let failedMessage = '';
|
||||||
|
let errorMessage = '';
|
||||||
|
|
||||||
|
if (typeof info === 'string') {
|
||||||
|
|
||||||
|
// in case of success
|
||||||
|
if (info.toLowerCase().indexOf('succes') !== -1) {
|
||||||
|
const i18nMessageString = 'APP.MESSAGES.INFO.NODE_MOVE.';
|
||||||
|
let i18MessageSuffix = '';
|
||||||
|
|
||||||
|
if (succeeded) {
|
||||||
|
i18MessageSuffix = ( succeeded === 1 ) ? 'SINGULAR' : 'PLURAL';
|
||||||
|
successMessage = `${i18nMessageString}${i18MessageSuffix}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (partiallySucceeded) {
|
||||||
|
i18MessageSuffix = ( partiallySucceeded === 1 ) ? 'PARTIAL.SINGULAR' : 'PARTIAL.PLURAL';
|
||||||
|
partialSuccessMessage = `${i18nMessageString}${i18MessageSuffix}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (failures) {
|
||||||
|
// if moving failed for ALL nodes, emit error
|
||||||
|
if (failures === nodes.length) {
|
||||||
|
const errors = this.nodeActionsService.flatten(moveResponse['failed']);
|
||||||
|
errorMessage = this.getErrorMessage(errors[0]);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
i18MessageSuffix = 'PARTIAL.FAIL';
|
||||||
|
failedMessage = `${i18nMessageString}${i18MessageSuffix}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
errorMessage = 'APP.MESSAGES.ERRORS.GENERIC';
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
errorMessage = this.getErrorMessage(info);
|
||||||
|
}
|
||||||
|
|
||||||
|
const undo = (succeeded + partiallySucceeded > 0) ? this.translation.instant('APP.ACTIONS.UNDO') : '';
|
||||||
|
failedMessage = errorMessage ? errorMessage : failedMessage;
|
||||||
|
|
||||||
|
const beforePartialSuccessMessage = (successMessage && partialSuccessMessage) ? ' ' : '';
|
||||||
|
const beforeFailedMessage = ((successMessage || partialSuccessMessage) && failedMessage) ? ' ' : '';
|
||||||
|
|
||||||
|
const initialParentId = this.nodeActionsService.getEntryParentId(nodes[0].entry);
|
||||||
|
|
||||||
|
const messages = this.translation.instant(
|
||||||
|
[successMessage, partialSuccessMessage, failedMessage],
|
||||||
|
{ success: succeeded, failed: failures, partially: partiallySucceeded}
|
||||||
|
);
|
||||||
|
|
||||||
|
// TODO: review in terms of i18n
|
||||||
|
this.snackBar
|
||||||
|
.open(
|
||||||
|
messages[successMessage]
|
||||||
|
+ beforePartialSuccessMessage + messages[partialSuccessMessage]
|
||||||
|
+ beforeFailedMessage + messages[failedMessage]
|
||||||
|
, undo, {
|
||||||
|
panelClass: 'info-snackbar',
|
||||||
|
duration: 3000
|
||||||
|
})
|
||||||
|
.onAction()
|
||||||
|
.subscribe(() => this.undoMoveNodes(moveResponse, initialParentId));
|
||||||
|
}
|
||||||
|
|
||||||
|
getErrorMessage(errorObject): string {
|
||||||
|
let i18nMessageString = 'APP.MESSAGES.ERRORS.GENERIC';
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { error: { statusCode } } = JSON.parse(errorObject.message);
|
||||||
|
|
||||||
|
if (statusCode === 409) {
|
||||||
|
i18nMessageString = 'APP.MESSAGES.ERRORS.NODE_MOVE';
|
||||||
|
|
||||||
|
} else if (statusCode === 403) {
|
||||||
|
i18nMessageString = 'APP.MESSAGES.ERRORS.PERMISSION';
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (err) { /* Do nothing, keep the original message */ }
|
||||||
|
|
||||||
|
return i18nMessageString;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -24,9 +24,10 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
|
import { NodePermissions } from '../extensions/permission.extensions';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class NodePermissionService {
|
export class NodePermissionService implements NodePermissions {
|
||||||
static DEFAULT_OPERATION = 'OR';
|
static DEFAULT_OPERATION = 'OR';
|
||||||
|
|
||||||
private defaultOptions = {
|
private defaultOptions = {
|
||||||
@ -34,8 +35,8 @@ export class NodePermissionService {
|
|||||||
target: null
|
target: null
|
||||||
};
|
};
|
||||||
|
|
||||||
check(source: any, permissions: string[], options: any = {}): boolean {
|
check(source: any, permissions: string[], options?: any): boolean {
|
||||||
const opts = Object.assign({}, this.defaultOptions, options);
|
const opts = Object.assign({}, this.defaultOptions, options || {});
|
||||||
|
|
||||||
if (source) {
|
if (source) {
|
||||||
if (Array.isArray(source) && source.length) {
|
if (Array.isArray(source) && source.length) {
|
||||||
|
@ -24,6 +24,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
export * from './actions/app.actions';
|
export * from './actions/app.actions';
|
||||||
|
export * from './actions/favorite.actions';
|
||||||
export * from './actions/node.actions';
|
export * from './actions/node.actions';
|
||||||
export * from './actions/snackbar.actions';
|
export * from './actions/snackbar.actions';
|
||||||
export * from './actions/router.actions';
|
export * from './actions/router.actions';
|
||||||
@ -31,3 +32,4 @@ export * from './actions/viewer.actions';
|
|||||||
export * from './actions/search.actions';
|
export * from './actions/search.actions';
|
||||||
export * from './actions/user.actions';
|
export * from './actions/user.actions';
|
||||||
export * from './actions/library.actions';
|
export * from './actions/library.actions';
|
||||||
|
export * from './actions/upload.actions';
|
||||||
|
@ -33,6 +33,8 @@ export const SET_LANGUAGE_PICKER = 'SET_LANGUAGE_PICKER';
|
|||||||
export const SET_SHARED_URL = 'SET_SHARED_URL';
|
export const SET_SHARED_URL = 'SET_SHARED_URL';
|
||||||
export const SET_CURRENT_FOLDER = 'SET_CURRENT_FOLDER';
|
export const SET_CURRENT_FOLDER = 'SET_CURRENT_FOLDER';
|
||||||
export const SET_CURRENT_URL = 'SET_CURRENT_URL';
|
export const SET_CURRENT_URL = 'SET_CURRENT_URL';
|
||||||
|
export const TOGGLE_INFO_DRAWER = 'TOGGLE_INFO_DRAWER';
|
||||||
|
export const TOGGLE_DOCUMENT_DISPLAY_MODE = 'TOGGLE_DOCUMENT_DISPLAY_MODE';
|
||||||
|
|
||||||
export class SetAppNameAction implements Action {
|
export class SetAppNameAction implements Action {
|
||||||
readonly type = SET_APP_NAME;
|
readonly type = SET_APP_NAME;
|
||||||
@ -68,3 +70,13 @@ export class SetCurrentUrlAction implements Action {
|
|||||||
readonly type = SET_CURRENT_URL;
|
readonly type = SET_CURRENT_URL;
|
||||||
constructor(public payload: string) {}
|
constructor(public payload: string) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class ToggleInfoDrawerAction implements Action {
|
||||||
|
readonly type = TOGGLE_INFO_DRAWER;
|
||||||
|
constructor(public payload?: any) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ToggleDocumentDisplayMode implements Action {
|
||||||
|
readonly type = TOGGLE_DOCUMENT_DISPLAY_MODE;
|
||||||
|
constructor(public payload?: any) {}
|
||||||
|
}
|
||||||
|
@ -23,23 +23,18 @@
|
|||||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Directive, HostListener, Input } from '@angular/core';
|
import { Action } from '@ngrx/store';
|
||||||
import { MinimalNodeEntity } from 'alfresco-js-api';
|
import { MinimalNodeEntity } from 'alfresco-js-api';
|
||||||
import { Store } from '@ngrx/store';
|
|
||||||
import { AppStore } from '../store/states';
|
|
||||||
import { RestoreDeletedNodesAction } from '../store/actions';
|
|
||||||
|
|
||||||
@Directive({
|
export const ADD_FAVORITE = 'ADD_FAVORITE';
|
||||||
selector: '[acaRestoreNode]'
|
export const REMOVE_FAVORITE = 'REMOVE_FAVORITE';
|
||||||
})
|
|
||||||
export class NodeRestoreDirective {
|
|
||||||
// tslint:disable-next-line:no-input-rename
|
|
||||||
@Input('acaRestoreNode') selection: MinimalNodeEntity[];
|
|
||||||
|
|
||||||
constructor(private store: Store<AppStore>) {}
|
export class AddFavoriteAction implements Action {
|
||||||
|
readonly type = ADD_FAVORITE;
|
||||||
@HostListener('click')
|
constructor(public payload: Array<MinimalNodeEntity>) {}
|
||||||
onClick() {
|
|
||||||
this.store.dispatch(new RestoreDeletedNodesAction(this.selection));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class RemoveFavoriteAction implements Action {
|
||||||
|
readonly type = REMOVE_FAVORITE;
|
||||||
|
constructor(public payload: Array<MinimalNodeEntity>) {}
|
||||||
}
|
}
|
@ -30,7 +30,7 @@ export const CREATE_LIBRARY = 'CREATE_LIBRARY';
|
|||||||
|
|
||||||
export class DeleteLibraryAction implements Action {
|
export class DeleteLibraryAction implements Action {
|
||||||
readonly type = DELETE_LIBRARY;
|
readonly type = DELETE_LIBRARY;
|
||||||
constructor(public payload: string) {}
|
constructor(public payload?: string) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class CreateLibraryAction implements Action {
|
export class CreateLibraryAction implements Action {
|
||||||
|
@ -24,7 +24,6 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { Action } from '@ngrx/store';
|
import { Action } from '@ngrx/store';
|
||||||
import { NodeInfo } from '../models';
|
|
||||||
import { MinimalNodeEntity } from 'alfresco-js-api';
|
import { MinimalNodeEntity } from 'alfresco-js-api';
|
||||||
|
|
||||||
export const SET_SELECTED_NODES = 'SET_SELECTED_NODES';
|
export const SET_SELECTED_NODES = 'SET_SELECTED_NODES';
|
||||||
@ -35,6 +34,12 @@ export const PURGE_DELETED_NODES = 'PURGE_DELETED_NODES';
|
|||||||
export const DOWNLOAD_NODES = 'DOWNLOAD_NODES';
|
export const DOWNLOAD_NODES = 'DOWNLOAD_NODES';
|
||||||
export const CREATE_FOLDER = 'CREATE_FOLDER';
|
export const CREATE_FOLDER = 'CREATE_FOLDER';
|
||||||
export const EDIT_FOLDER = 'EDIT_FOLDER';
|
export const EDIT_FOLDER = 'EDIT_FOLDER';
|
||||||
|
export const SHARE_NODE = 'SHARE_NODE';
|
||||||
|
export const UNSHARE_NODES = 'UNSHARE_NODES';
|
||||||
|
export const COPY_NODES = 'COPY_NODES';
|
||||||
|
export const MOVE_NODES = 'MOVE_NODES';
|
||||||
|
export const MANAGE_PERMISSIONS = 'MANAGE_PERMISSIONS';
|
||||||
|
export const MANAGE_VERSIONS = 'MANAGE_VERSIONS';
|
||||||
|
|
||||||
export class SetSelectedNodesAction implements Action {
|
export class SetSelectedNodesAction implements Action {
|
||||||
readonly type = SET_SELECTED_NODES;
|
readonly type = SET_SELECTED_NODES;
|
||||||
@ -43,7 +48,7 @@ export class SetSelectedNodesAction implements Action {
|
|||||||
|
|
||||||
export class DeleteNodesAction implements Action {
|
export class DeleteNodesAction implements Action {
|
||||||
readonly type = DELETE_NODES;
|
readonly type = DELETE_NODES;
|
||||||
constructor(public payload: NodeInfo[] = []) {}
|
constructor(public payload: MinimalNodeEntity[] = []) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class UndoDeleteNodesAction implements Action {
|
export class UndoDeleteNodesAction implements Action {
|
||||||
@ -75,3 +80,33 @@ export class EditFolderAction implements Action {
|
|||||||
readonly type = EDIT_FOLDER;
|
readonly type = EDIT_FOLDER;
|
||||||
constructor(public payload: MinimalNodeEntity) {}
|
constructor(public payload: MinimalNodeEntity) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class ShareNodeAction implements Action {
|
||||||
|
readonly type = SHARE_NODE;
|
||||||
|
constructor(public payload: MinimalNodeEntity) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class UnshareNodesAction implements Action {
|
||||||
|
readonly type = UNSHARE_NODES;
|
||||||
|
constructor(public payload: Array<MinimalNodeEntity>) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CopyNodesAction implements Action {
|
||||||
|
readonly type = COPY_NODES;
|
||||||
|
constructor(public payload: Array<MinimalNodeEntity>) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class MoveNodesAction implements Action {
|
||||||
|
readonly type = MOVE_NODES;
|
||||||
|
constructor(public payload: Array<MinimalNodeEntity>) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ManagePermissionsAction implements Action {
|
||||||
|
readonly type = MANAGE_PERMISSIONS;
|
||||||
|
constructor(public payload: MinimalNodeEntity) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ManageVersionsAction implements Action {
|
||||||
|
readonly type = MANAGE_VERSIONS;
|
||||||
|
constructor(public payload: MinimalNodeEntity) {}
|
||||||
|
}
|
||||||
|
@ -23,27 +23,17 @@
|
|||||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Directive, HostListener, Input } from '@angular/core';
|
import { Action } from '@ngrx/store';
|
||||||
import { MinimalNodeEntity } from 'alfresco-js-api';
|
|
||||||
import { Store } from '@ngrx/store';
|
|
||||||
import { AppStore } from '../store/states';
|
|
||||||
import { PurgeDeletedNodesAction } from '../store/actions';
|
|
||||||
|
|
||||||
@Directive({
|
export const UPLOAD_FILES = 'UPLOAD_FILES';
|
||||||
selector: '[acaPermanentDelete]'
|
export const UPLOAD_FOLDER = 'UPLOAD_FOLDER';
|
||||||
})
|
|
||||||
export class NodePermanentDeleteDirective {
|
|
||||||
|
|
||||||
// tslint:disable-next-line:no-input-rename
|
export class UploadFilesAction implements Action {
|
||||||
@Input('acaPermanentDelete')
|
readonly type = UPLOAD_FILES;
|
||||||
selection: MinimalNodeEntity[];
|
constructor(public payload: any) {}
|
||||||
|
|
||||||
constructor(
|
|
||||||
private store: Store<AppStore>
|
|
||||||
) {}
|
|
||||||
|
|
||||||
@HostListener('click')
|
|
||||||
onClick() {
|
|
||||||
this.store.dispatch(new PurgeDeletedNodesAction(this.selection));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class UploadFolderAction implements Action {
|
||||||
|
readonly type = UPLOAD_FOLDER;
|
||||||
|
constructor(public payload: any) {}
|
||||||
}
|
}
|
@ -38,7 +38,9 @@ import {
|
|||||||
DownloadEffects,
|
DownloadEffects,
|
||||||
ViewerEffects,
|
ViewerEffects,
|
||||||
SearchEffects,
|
SearchEffects,
|
||||||
SiteEffects
|
SiteEffects,
|
||||||
|
UploadEffects,
|
||||||
|
FavoriteEffects
|
||||||
} from './effects';
|
} from './effects';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
@ -55,7 +57,9 @@ import {
|
|||||||
DownloadEffects,
|
DownloadEffects,
|
||||||
ViewerEffects,
|
ViewerEffects,
|
||||||
SearchEffects,
|
SearchEffects,
|
||||||
SiteEffects
|
SiteEffects,
|
||||||
|
UploadEffects,
|
||||||
|
FavoriteEffects
|
||||||
]),
|
]),
|
||||||
!environment.production
|
!environment.production
|
||||||
? StoreDevtoolsModule.instrument({ maxAge: 25 })
|
? StoreDevtoolsModule.instrument({ maxAge: 25 })
|
||||||
|
@ -24,9 +24,11 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
export * from './effects/download.effects';
|
export * from './effects/download.effects';
|
||||||
|
export * from './effects/favorite.effects';
|
||||||
export * from './effects/node.effects';
|
export * from './effects/node.effects';
|
||||||
export * from './effects/router.effects';
|
export * from './effects/router.effects';
|
||||||
export * from './effects/snackbar.effects';
|
export * from './effects/snackbar.effects';
|
||||||
export * from './effects/viewer.effects';
|
export * from './effects/viewer.effects';
|
||||||
export * from './effects/search.effects';
|
export * from './effects/search.effects';
|
||||||
export * from './effects/library.effects';
|
export * from './effects/library.effects';
|
||||||
|
export * from './effects/upload.effects';
|
||||||
|
82
src/app/store/effects/favorite.effects.ts
Normal file
82
src/app/store/effects/favorite.effects.ts
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
/*!
|
||||||
|
* @license
|
||||||
|
* Alfresco Example Content Application
|
||||||
|
*
|
||||||
|
* Copyright (C) 2005 - 2018 Alfresco Software Limited
|
||||||
|
*
|
||||||
|
* This file is part of the Alfresco Example Content Application.
|
||||||
|
* If the software was purchased under a paid Alfresco license, the terms of
|
||||||
|
* the paid license agreement will prevail. Otherwise, the software is
|
||||||
|
* provided under the following open source license terms:
|
||||||
|
*
|
||||||
|
* The Alfresco Example Content Application is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* The Alfresco Example Content Application is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public License
|
||||||
|
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Effect, Actions, ofType } from '@ngrx/effects';
|
||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { map } from 'rxjs/operators';
|
||||||
|
import { ADD_FAVORITE, AddFavoriteAction, RemoveFavoriteAction, REMOVE_FAVORITE } from '../actions/favorite.actions';
|
||||||
|
import { Store } from '@ngrx/store';
|
||||||
|
import { AppStore } from '../states';
|
||||||
|
import { appSelection } from '../selectors/app.selectors';
|
||||||
|
import { ContentManagementService } from '../../services/content-management.service';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class FavoriteEffects {
|
||||||
|
constructor(
|
||||||
|
private store: Store<AppStore>,
|
||||||
|
private actions$: Actions,
|
||||||
|
private content: ContentManagementService
|
||||||
|
) {}
|
||||||
|
|
||||||
|
@Effect({ dispatch: false })
|
||||||
|
addFavorite$ = this.actions$.pipe(
|
||||||
|
ofType<AddFavoriteAction>(ADD_FAVORITE),
|
||||||
|
map(action => {
|
||||||
|
if (action.payload && action.payload.length > 0) {
|
||||||
|
this.content.addFavorite(action.payload);
|
||||||
|
} else {
|
||||||
|
this.store
|
||||||
|
.select(appSelection)
|
||||||
|
.take(1)
|
||||||
|
.subscribe(selection => {
|
||||||
|
if (selection && !selection.isEmpty) {
|
||||||
|
this.content.addFavorite(selection.nodes);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
@Effect({ dispatch: false })
|
||||||
|
removeFavorite$ = this.actions$.pipe(
|
||||||
|
ofType<RemoveFavoriteAction>(REMOVE_FAVORITE),
|
||||||
|
map(action => {
|
||||||
|
if (action.payload && action.payload.length > 0) {
|
||||||
|
this.content.removeFavorite(action.payload);
|
||||||
|
} else {
|
||||||
|
this.store
|
||||||
|
.select(appSelection)
|
||||||
|
.take(1)
|
||||||
|
.subscribe(selection => {
|
||||||
|
if (selection && !selection.isEmpty) {
|
||||||
|
this.content.removeFavorite(selection.nodes);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -30,21 +30,16 @@ import {
|
|||||||
DeleteLibraryAction, DELETE_LIBRARY,
|
DeleteLibraryAction, DELETE_LIBRARY,
|
||||||
CreateLibraryAction, CREATE_LIBRARY
|
CreateLibraryAction, CREATE_LIBRARY
|
||||||
} from '../actions';
|
} from '../actions';
|
||||||
import {
|
|
||||||
SnackbarInfoAction,
|
|
||||||
SnackbarErrorAction
|
|
||||||
} from '../actions/snackbar.actions';
|
|
||||||
import { Store } from '@ngrx/store';
|
|
||||||
import { AppStore } from '../states/app.state';
|
|
||||||
import { ContentManagementService } from '../../services/content-management.service';
|
import { ContentManagementService } from '../../services/content-management.service';
|
||||||
import { ContentApiService } from '../../services/content-api.service';
|
import { Store } from '@ngrx/store';
|
||||||
|
import { AppStore } from '../states';
|
||||||
|
import { appSelection } from '../selectors/app.selectors';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class SiteEffects {
|
export class SiteEffects {
|
||||||
constructor(
|
constructor(
|
||||||
private actions$: Actions,
|
|
||||||
private store: Store<AppStore>,
|
private store: Store<AppStore>,
|
||||||
private contentApi: ContentApiService,
|
private actions$: Actions,
|
||||||
private content: ContentManagementService
|
private content: ContentManagementService
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
@ -53,7 +48,16 @@ export class SiteEffects {
|
|||||||
ofType<DeleteLibraryAction>(DELETE_LIBRARY),
|
ofType<DeleteLibraryAction>(DELETE_LIBRARY),
|
||||||
map(action => {
|
map(action => {
|
||||||
if (action.payload) {
|
if (action.payload) {
|
||||||
this.deleteLibrary(action.payload);
|
this.content.deleteLibrary(action.payload);
|
||||||
|
} else {
|
||||||
|
this.store
|
||||||
|
.select(appSelection)
|
||||||
|
.take(1)
|
||||||
|
.subscribe(selection => {
|
||||||
|
if (selection && selection.library) {
|
||||||
|
this.content.deleteLibrary(selection.library.entry.id);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
@ -62,31 +66,7 @@ export class SiteEffects {
|
|||||||
createLibrary$ = this.actions$.pipe(
|
createLibrary$ = this.actions$.pipe(
|
||||||
ofType<CreateLibraryAction>(CREATE_LIBRARY),
|
ofType<CreateLibraryAction>(CREATE_LIBRARY),
|
||||||
map(action => {
|
map(action => {
|
||||||
this.createLibrary();
|
this.content.createLibrary();
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
private deleteLibrary(id: string) {
|
|
||||||
this.contentApi.deleteSite(id).subscribe(
|
|
||||||
() => {
|
|
||||||
this.content.libraryDeleted.next(id);
|
|
||||||
this.store.dispatch(
|
|
||||||
new SnackbarInfoAction(
|
|
||||||
'APP.MESSAGES.INFO.LIBRARY_DELETED'
|
|
||||||
)
|
|
||||||
);
|
|
||||||
},
|
|
||||||
() => {
|
|
||||||
this.store.dispatch(
|
|
||||||
new SnackbarErrorAction(
|
|
||||||
'APP.MESSAGES.ERRORS.DELETE_LIBRARY_FAILED'
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private createLibrary() {
|
|
||||||
this.content.createLibrary();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -29,49 +29,97 @@ import { map } from 'rxjs/operators';
|
|||||||
import { Store } from '@ngrx/store';
|
import { Store } from '@ngrx/store';
|
||||||
import { AppStore } from '../states/app.state';
|
import { AppStore } from '../states/app.state';
|
||||||
import {
|
import {
|
||||||
SnackbarWarningAction,
|
|
||||||
SnackbarInfoAction,
|
|
||||||
SnackbarErrorAction,
|
|
||||||
PurgeDeletedNodesAction,
|
PurgeDeletedNodesAction,
|
||||||
PURGE_DELETED_NODES,
|
PURGE_DELETED_NODES,
|
||||||
DeleteNodesAction,
|
DeleteNodesAction,
|
||||||
DELETE_NODES,
|
DELETE_NODES,
|
||||||
SnackbarUserAction,
|
|
||||||
SnackbarAction,
|
|
||||||
UndoDeleteNodesAction,
|
UndoDeleteNodesAction,
|
||||||
UNDO_DELETE_NODES,
|
UNDO_DELETE_NODES,
|
||||||
CreateFolderAction,
|
CreateFolderAction,
|
||||||
CREATE_FOLDER
|
CREATE_FOLDER,
|
||||||
|
EditFolderAction,
|
||||||
|
EDIT_FOLDER,
|
||||||
|
RestoreDeletedNodesAction,
|
||||||
|
RESTORE_DELETED_NODES,
|
||||||
|
ShareNodeAction,
|
||||||
|
SHARE_NODE
|
||||||
} from '../actions';
|
} from '../actions';
|
||||||
import { ContentManagementService } from '../../services/content-management.service';
|
import { ContentManagementService } from '../../services/content-management.service';
|
||||||
import { Observable } from 'rxjs/Rx';
|
|
||||||
import { NodeInfo, DeleteStatus, DeletedNodeInfo } from '../models';
|
|
||||||
import { ContentApiService } from '../../services/content-api.service';
|
|
||||||
import { currentFolder, appSelection } from '../selectors/app.selectors';
|
import { currentFolder, appSelection } from '../selectors/app.selectors';
|
||||||
import { EditFolderAction, EDIT_FOLDER, RestoreDeletedNodesAction, RESTORE_DELETED_NODES } from '../actions/node.actions';
|
import {
|
||||||
|
UnshareNodesAction,
|
||||||
|
UNSHARE_NODES,
|
||||||
|
CopyNodesAction,
|
||||||
|
COPY_NODES,
|
||||||
|
MoveNodesAction,
|
||||||
|
MOVE_NODES,
|
||||||
|
ManagePermissionsAction,
|
||||||
|
MANAGE_PERMISSIONS,
|
||||||
|
ManageVersionsAction,
|
||||||
|
MANAGE_VERSIONS
|
||||||
|
} from '../actions/node.actions';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class NodeEffects {
|
export class NodeEffects {
|
||||||
constructor(
|
constructor(
|
||||||
private store: Store<AppStore>,
|
private store: Store<AppStore>,
|
||||||
private actions$: Actions,
|
private actions$: Actions,
|
||||||
private contentManagementService: ContentManagementService,
|
private contentService: ContentManagementService
|
||||||
private contentApi: ContentApiService
|
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
|
@Effect({ dispatch: false })
|
||||||
|
shareNode$ = this.actions$.pipe(
|
||||||
|
ofType<ShareNodeAction>(SHARE_NODE),
|
||||||
|
map(action => {
|
||||||
|
if (action.payload) {
|
||||||
|
this.contentService.shareNode(action.payload);
|
||||||
|
} else {
|
||||||
|
this.store
|
||||||
|
.select(appSelection)
|
||||||
|
.take(1)
|
||||||
|
.subscribe(selection => {
|
||||||
|
if (selection && selection.file) {
|
||||||
|
this.contentService.shareNode(selection.file);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
@Effect({ dispatch: false })
|
||||||
|
unshareNodes$ = this.actions$.pipe(
|
||||||
|
ofType<UnshareNodesAction>(UNSHARE_NODES),
|
||||||
|
map(action => {
|
||||||
|
if (action && action.payload && action.payload.length > 0) {
|
||||||
|
this.contentService.unshareNodes(action.payload);
|
||||||
|
} else {
|
||||||
|
this.store
|
||||||
|
.select(appSelection)
|
||||||
|
.take(1)
|
||||||
|
.subscribe(selection => {
|
||||||
|
if (selection && !selection.isEmpty) {
|
||||||
|
this.contentService.unshareNodes(selection.nodes);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
@Effect({ dispatch: false })
|
@Effect({ dispatch: false })
|
||||||
purgeDeletedNodes$ = this.actions$.pipe(
|
purgeDeletedNodes$ = this.actions$.pipe(
|
||||||
ofType<PurgeDeletedNodesAction>(PURGE_DELETED_NODES),
|
ofType<PurgeDeletedNodesAction>(PURGE_DELETED_NODES),
|
||||||
map(action => {
|
map(action => {
|
||||||
if (action && action.payload && action.payload.length > 0) {
|
if (action && action.payload && action.payload.length > 0) {
|
||||||
this.contentManagementService.purgeDeletedNodes(action.payload);
|
this.contentService.purgeDeletedNodes(action.payload);
|
||||||
} else {
|
} else {
|
||||||
this.store
|
this.store
|
||||||
.select(appSelection)
|
.select(appSelection)
|
||||||
.take(1)
|
.take(1)
|
||||||
.subscribe(selection => {
|
.subscribe(selection => {
|
||||||
if (selection && selection.count > 0) {
|
if (selection && selection.count > 0) {
|
||||||
this.contentManagementService.purgeDeletedNodes(selection.nodes);
|
this.contentService.purgeDeletedNodes(
|
||||||
|
selection.nodes
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -83,14 +131,16 @@ export class NodeEffects {
|
|||||||
ofType<RestoreDeletedNodesAction>(RESTORE_DELETED_NODES),
|
ofType<RestoreDeletedNodesAction>(RESTORE_DELETED_NODES),
|
||||||
map(action => {
|
map(action => {
|
||||||
if (action && action.payload && action.payload.length > 0) {
|
if (action && action.payload && action.payload.length > 0) {
|
||||||
this.contentManagementService.restoreDeletedNodes(action.payload);
|
this.contentService.restoreDeletedNodes(action.payload);
|
||||||
} else {
|
} else {
|
||||||
this.store
|
this.store
|
||||||
.select(appSelection)
|
.select(appSelection)
|
||||||
.take(1)
|
.take(1)
|
||||||
.subscribe(selection => {
|
.subscribe(selection => {
|
||||||
if (selection && selection.count > 0) {
|
if (selection && selection.count > 0) {
|
||||||
this.contentManagementService.restoreDeletedNodes(selection.nodes);
|
this.contentService.restoreDeletedNodes(
|
||||||
|
selection.nodes
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -101,8 +151,17 @@ export class NodeEffects {
|
|||||||
deleteNodes$ = this.actions$.pipe(
|
deleteNodes$ = this.actions$.pipe(
|
||||||
ofType<DeleteNodesAction>(DELETE_NODES),
|
ofType<DeleteNodesAction>(DELETE_NODES),
|
||||||
map(action => {
|
map(action => {
|
||||||
if (action.payload.length > 0) {
|
if (action && action.payload && action.payload.length > 0) {
|
||||||
this.deleteNodes(action.payload);
|
this.contentService.deleteNodes(action.payload);
|
||||||
|
} else {
|
||||||
|
this.store
|
||||||
|
.select(appSelection)
|
||||||
|
.take(1)
|
||||||
|
.subscribe(selection => {
|
||||||
|
if (selection && selection.count > 0) {
|
||||||
|
this.contentService.deleteNodes(selection.nodes);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
@ -112,7 +171,7 @@ export class NodeEffects {
|
|||||||
ofType<UndoDeleteNodesAction>(UNDO_DELETE_NODES),
|
ofType<UndoDeleteNodesAction>(UNDO_DELETE_NODES),
|
||||||
map(action => {
|
map(action => {
|
||||||
if (action.payload.length > 0) {
|
if (action.payload.length > 0) {
|
||||||
this.undoDeleteNodes(action.payload);
|
this.contentService.undoDeleteNodes(action.payload);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
@ -122,14 +181,14 @@ export class NodeEffects {
|
|||||||
ofType<CreateFolderAction>(CREATE_FOLDER),
|
ofType<CreateFolderAction>(CREATE_FOLDER),
|
||||||
map(action => {
|
map(action => {
|
||||||
if (action.payload) {
|
if (action.payload) {
|
||||||
this.contentManagementService.createFolder(action.payload);
|
this.contentService.createFolder(action.payload);
|
||||||
} else {
|
} else {
|
||||||
this.store
|
this.store
|
||||||
.select(currentFolder)
|
.select(currentFolder)
|
||||||
.take(1)
|
.take(1)
|
||||||
.subscribe(node => {
|
.subscribe(node => {
|
||||||
if (node && node.id) {
|
if (node && node.id) {
|
||||||
this.contentManagementService.createFolder(node.id);
|
this.contentService.createFolder(node.id);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -141,215 +200,97 @@ export class NodeEffects {
|
|||||||
ofType<EditFolderAction>(EDIT_FOLDER),
|
ofType<EditFolderAction>(EDIT_FOLDER),
|
||||||
map(action => {
|
map(action => {
|
||||||
if (action.payload) {
|
if (action.payload) {
|
||||||
this.contentManagementService.editFolder(action.payload);
|
this.contentService.editFolder(action.payload);
|
||||||
} else {
|
} else {
|
||||||
this.store
|
this.store
|
||||||
.select(appSelection)
|
.select(appSelection)
|
||||||
.take(1)
|
.take(1)
|
||||||
.subscribe(selection => {
|
.subscribe(selection => {
|
||||||
if (selection && selection.folder) {
|
if (selection && selection.folder) {
|
||||||
this.contentManagementService.editFolder(selection.folder);
|
this.contentService.editFolder(selection.folder);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
private deleteNodes(items: NodeInfo[]): void {
|
@Effect({ dispatch: false })
|
||||||
const batch: Observable<DeletedNodeInfo>[] = [];
|
copyNodes$ = this.actions$.pipe(
|
||||||
|
ofType<CopyNodesAction>(COPY_NODES),
|
||||||
items.forEach(node => {
|
map(action => {
|
||||||
batch.push(this.deleteNode(node));
|
if (action.payload && action.payload.length > 0) {
|
||||||
});
|
this.contentService.copyNodes(action.payload);
|
||||||
|
|
||||||
Observable.forkJoin(...batch).subscribe((data: DeletedNodeInfo[]) => {
|
|
||||||
const status = this.processStatus(data);
|
|
||||||
const message = this.getDeleteMessage(status);
|
|
||||||
|
|
||||||
if (message && status.someSucceeded) {
|
|
||||||
message.duration = 10000;
|
|
||||||
message.userAction = new SnackbarUserAction(
|
|
||||||
'APP.ACTIONS.UNDO',
|
|
||||||
new UndoDeleteNodesAction([...status.success])
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.store.dispatch(message);
|
|
||||||
|
|
||||||
if (status.someSucceeded) {
|
|
||||||
this.contentManagementService.nodesDeleted.next();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private deleteNode(node: NodeInfo): Observable<DeletedNodeInfo> {
|
|
||||||
const { id, name } = node;
|
|
||||||
|
|
||||||
return this.contentApi
|
|
||||||
.deleteNode(id)
|
|
||||||
.map(() => {
|
|
||||||
return {
|
|
||||||
id,
|
|
||||||
name,
|
|
||||||
status: 1
|
|
||||||
};
|
|
||||||
})
|
|
||||||
.catch((error: any) => {
|
|
||||||
return Observable.of({
|
|
||||||
id,
|
|
||||||
name,
|
|
||||||
status: 0
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private getDeleteMessage(status: DeleteStatus): SnackbarAction {
|
|
||||||
if (status.allFailed && !status.oneFailed) {
|
|
||||||
return new SnackbarErrorAction(
|
|
||||||
'APP.MESSAGES.ERRORS.NODE_DELETION_PLURAL',
|
|
||||||
{ number: status.fail.length }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (status.allSucceeded && !status.oneSucceeded) {
|
|
||||||
return new SnackbarInfoAction(
|
|
||||||
'APP.MESSAGES.INFO.NODE_DELETION.PLURAL',
|
|
||||||
{ number: status.success.length }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (status.someFailed && status.someSucceeded && !status.oneSucceeded) {
|
|
||||||
return new SnackbarWarningAction(
|
|
||||||
'APP.MESSAGES.INFO.NODE_DELETION.PARTIAL_PLURAL',
|
|
||||||
{
|
|
||||||
success: status.success.length,
|
|
||||||
failed: status.fail.length
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (status.someFailed && status.oneSucceeded) {
|
|
||||||
return new SnackbarWarningAction(
|
|
||||||
'APP.MESSAGES.INFO.NODE_DELETION.PARTIAL_SINGULAR',
|
|
||||||
{
|
|
||||||
success: status.success.length,
|
|
||||||
failed: status.fail.length
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (status.oneFailed && !status.someSucceeded) {
|
|
||||||
return new SnackbarErrorAction(
|
|
||||||
'APP.MESSAGES.ERRORS.NODE_DELETION',
|
|
||||||
{ name: status.fail[0].name }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (status.oneSucceeded && !status.someFailed) {
|
|
||||||
return new SnackbarInfoAction(
|
|
||||||
'APP.MESSAGES.INFO.NODE_DELETION.SINGULAR',
|
|
||||||
{ name: status.success[0].name }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private undoDeleteNodes(items: DeletedNodeInfo[]): void {
|
|
||||||
const batch: Observable<DeletedNodeInfo>[] = [];
|
|
||||||
|
|
||||||
items.forEach(item => {
|
|
||||||
batch.push(this.undoDeleteNode(item));
|
|
||||||
});
|
|
||||||
|
|
||||||
Observable.forkJoin(...batch).subscribe(data => {
|
|
||||||
const processedData = this.processStatus(data);
|
|
||||||
|
|
||||||
if (processedData.fail.length) {
|
|
||||||
const message = this.getUndoDeleteMessage(processedData);
|
|
||||||
this.store.dispatch(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (processedData.someSucceeded) {
|
|
||||||
this.contentManagementService.nodesRestored.next();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private undoDeleteNode(item: DeletedNodeInfo): Observable<DeletedNodeInfo> {
|
|
||||||
const { id, name } = item;
|
|
||||||
|
|
||||||
return this.contentApi
|
|
||||||
.restoreNode(id)
|
|
||||||
.map(() => {
|
|
||||||
return {
|
|
||||||
id,
|
|
||||||
name,
|
|
||||||
status: 1
|
|
||||||
};
|
|
||||||
})
|
|
||||||
.catch((error: any) => {
|
|
||||||
return Observable.of({
|
|
||||||
id,
|
|
||||||
name,
|
|
||||||
status: 0
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private getUndoDeleteMessage(status: DeleteStatus): SnackbarAction {
|
|
||||||
if (status.someFailed && !status.oneFailed) {
|
|
||||||
return new SnackbarErrorAction(
|
|
||||||
'APP.MESSAGES.ERRORS.NODE_RESTORE_PLURAL',
|
|
||||||
{ number: status.fail.length }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (status.oneFailed) {
|
|
||||||
return new SnackbarErrorAction('APP.MESSAGES.ERRORS.NODE_RESTORE', {
|
|
||||||
name: status.fail[0].name
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private processStatus(data: DeletedNodeInfo[] = []): DeleteStatus {
|
|
||||||
const status = {
|
|
||||||
fail: [],
|
|
||||||
success: [],
|
|
||||||
get someFailed() {
|
|
||||||
return !!this.fail.length;
|
|
||||||
},
|
|
||||||
get someSucceeded() {
|
|
||||||
return !!this.success.length;
|
|
||||||
},
|
|
||||||
get oneFailed() {
|
|
||||||
return this.fail.length === 1;
|
|
||||||
},
|
|
||||||
get oneSucceeded() {
|
|
||||||
return this.success.length === 1;
|
|
||||||
},
|
|
||||||
get allSucceeded() {
|
|
||||||
return this.someSucceeded && !this.someFailed;
|
|
||||||
},
|
|
||||||
get allFailed() {
|
|
||||||
return this.someFailed && !this.someSucceeded;
|
|
||||||
},
|
|
||||||
reset() {
|
|
||||||
this.fail = [];
|
|
||||||
this.success = [];
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return data.reduce((acc, node) => {
|
|
||||||
if (node.status) {
|
|
||||||
acc.success.push(node);
|
|
||||||
} else {
|
} else {
|
||||||
acc.fail.push(node);
|
this.store
|
||||||
|
.select(appSelection)
|
||||||
|
.take(1)
|
||||||
|
.subscribe(selection => {
|
||||||
|
if (selection && !selection.isEmpty) {
|
||||||
|
this.contentService.copyNodes(selection.nodes);
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
return acc;
|
@Effect({ dispatch: false })
|
||||||
}, status);
|
moveNodes$ = this.actions$.pipe(
|
||||||
|
ofType<MoveNodesAction>(MOVE_NODES),
|
||||||
|
map(action => {
|
||||||
|
if (action.payload && action.payload.length > 0) {
|
||||||
|
this.contentService.moveNodes(action.payload);
|
||||||
|
} else {
|
||||||
|
this.store
|
||||||
|
.select(appSelection)
|
||||||
|
.take(1)
|
||||||
|
.subscribe(selection => {
|
||||||
|
if (selection && !selection.isEmpty) {
|
||||||
|
this.contentService.moveNodes(selection.nodes);
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
@Effect({ dispatch: false })
|
||||||
|
managePermissions = this.actions$.pipe(
|
||||||
|
ofType<ManagePermissionsAction>(MANAGE_PERMISSIONS),
|
||||||
|
map(action => {
|
||||||
|
if (action && action.payload) {
|
||||||
|
this.contentService.managePermissions(action.payload);
|
||||||
|
} else {
|
||||||
|
this.store
|
||||||
|
.select(appSelection)
|
||||||
|
.take(1)
|
||||||
|
.subscribe(selection => {
|
||||||
|
if (selection && !selection.isEmpty) {
|
||||||
|
this.contentService.managePermissions(
|
||||||
|
selection.first
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
@Effect({ dispatch: false })
|
||||||
|
manageVersions$ = this.actions$.pipe(
|
||||||
|
ofType<ManageVersionsAction>(MANAGE_VERSIONS),
|
||||||
|
map(action => {
|
||||||
|
if (action && action.payload) {
|
||||||
|
this.contentService.manageVersions(action.payload);
|
||||||
|
} else {
|
||||||
|
this.store
|
||||||
|
.select(appSelection)
|
||||||
|
.take(1)
|
||||||
|
.subscribe(selection => {
|
||||||
|
if (selection && selection.file) {
|
||||||
|
this.contentService.manageVersions(
|
||||||
|
selection.file
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
116
src/app/store/effects/upload.effects.ts
Normal file
116
src/app/store/effects/upload.effects.ts
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
/*!
|
||||||
|
* @license
|
||||||
|
* Alfresco Example Content Application
|
||||||
|
*
|
||||||
|
* Copyright (C) 2005 - 2018 Alfresco Software Limited
|
||||||
|
*
|
||||||
|
* This file is part of the Alfresco Example Content Application.
|
||||||
|
* If the software was purchased under a paid Alfresco license, the terms of
|
||||||
|
* the paid license agreement will prevail. Otherwise, the software is
|
||||||
|
* provided under the following open source license terms:
|
||||||
|
*
|
||||||
|
* The Alfresco Example Content Application is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* The Alfresco Example Content Application is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public License
|
||||||
|
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Injectable, RendererFactory2, NgZone } from '@angular/core';
|
||||||
|
import { Actions, Effect, ofType } from '@ngrx/effects';
|
||||||
|
import { Store } from '@ngrx/store';
|
||||||
|
import { AppStore } from '../states';
|
||||||
|
import { UploadFilesAction, UPLOAD_FILES } from '../actions';
|
||||||
|
import { map } from 'rxjs/operators';
|
||||||
|
import { FileUtils, FileModel, UploadService } from '@alfresco/adf-core';
|
||||||
|
import { currentFolder } from '../selectors/app.selectors';
|
||||||
|
import { UploadFolderAction, UPLOAD_FOLDER } from '../actions/upload.actions';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class UploadEffects {
|
||||||
|
private fileInput: HTMLInputElement;
|
||||||
|
private folderInput: HTMLInputElement;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private store: Store<AppStore>,
|
||||||
|
private actions$: Actions,
|
||||||
|
private ngZone: NgZone,
|
||||||
|
private uploadService: UploadService,
|
||||||
|
rendererFactory: RendererFactory2
|
||||||
|
) {
|
||||||
|
const renderer = rendererFactory.createRenderer(null, null);
|
||||||
|
|
||||||
|
this.fileInput = renderer.createElement('input') as HTMLInputElement;
|
||||||
|
this.fileInput.id = 'app-upload-files';
|
||||||
|
this.fileInput.type = 'file';
|
||||||
|
this.fileInput.style.display = 'none';
|
||||||
|
this.fileInput.setAttribute('multiple', '');
|
||||||
|
this.fileInput.addEventListener('change', event => this.upload(event));
|
||||||
|
renderer.appendChild(document.body, this.fileInput);
|
||||||
|
|
||||||
|
|
||||||
|
this.folderInput = renderer.createElement('input') as HTMLInputElement;
|
||||||
|
this.folderInput.id = 'app-upload-folder';
|
||||||
|
this.folderInput.type = 'file';
|
||||||
|
this.folderInput.style.display = 'none';
|
||||||
|
this.folderInput.setAttribute('directory', '');
|
||||||
|
this.folderInput.setAttribute('webkitdirectory', '');
|
||||||
|
this.folderInput.addEventListener('change', event => this.upload(event));
|
||||||
|
renderer.appendChild(document.body, this.folderInput);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Effect({ dispatch: false })
|
||||||
|
uploadFiles$ = this.actions$.pipe(
|
||||||
|
ofType<UploadFilesAction>(UPLOAD_FILES),
|
||||||
|
map(() => {
|
||||||
|
this.fileInput.click();
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
@Effect({ dispatch: false })
|
||||||
|
uploadFolder$ = this.actions$.pipe(
|
||||||
|
ofType<UploadFolderAction>(UPLOAD_FOLDER),
|
||||||
|
map(() => {
|
||||||
|
this.folderInput.click();
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
private upload(event: any): void {
|
||||||
|
this.store
|
||||||
|
.select(currentFolder)
|
||||||
|
.take(1)
|
||||||
|
.subscribe(node => {
|
||||||
|
if (node && node.id) {
|
||||||
|
const input = <HTMLInputElement>event.currentTarget;
|
||||||
|
const files = FileUtils.toFileArray(input.files).map(
|
||||||
|
file => {
|
||||||
|
return new FileModel(file, {
|
||||||
|
parentId: node.id,
|
||||||
|
path: (file.webkitRelativePath || '').replace(/\/[^\/]*$/, ''),
|
||||||
|
nodeType: 'cm:content'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
this.uploadQueue(files);
|
||||||
|
event.target.value = '';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private uploadQueue(files: FileModel[]) {
|
||||||
|
if (files.length > 0) {
|
||||||
|
this.ngZone.run(() => {
|
||||||
|
this.uploadService.addToQueue(...files);
|
||||||
|
this.uploadService.uploadFilesInTheQueue();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -42,8 +42,15 @@ import {
|
|||||||
SetSharedUrlAction,
|
SetSharedUrlAction,
|
||||||
SET_CURRENT_FOLDER,
|
SET_CURRENT_FOLDER,
|
||||||
SetCurrentFolderAction,
|
SetCurrentFolderAction,
|
||||||
SET_CURRENT_URL, SetCurrentUrlAction
|
SET_CURRENT_URL,
|
||||||
|
SetCurrentUrlAction
|
||||||
} from '../actions';
|
} from '../actions';
|
||||||
|
import {
|
||||||
|
TOGGLE_INFO_DRAWER,
|
||||||
|
ToggleInfoDrawerAction,
|
||||||
|
TOGGLE_DOCUMENT_DISPLAY_MODE,
|
||||||
|
ToggleDocumentDisplayMode
|
||||||
|
} from '../actions/app.actions';
|
||||||
|
|
||||||
export function appReducer(
|
export function appReducer(
|
||||||
state: AppState = INITIAL_APP_STATE,
|
state: AppState = INITIAL_APP_STATE,
|
||||||
@ -85,6 +92,14 @@ export function appReducer(
|
|||||||
case SET_CURRENT_URL:
|
case SET_CURRENT_URL:
|
||||||
newState = updateCurrentUrl(state, <SetCurrentUrlAction>action);
|
newState = updateCurrentUrl(state, <SetCurrentUrlAction>action);
|
||||||
break;
|
break;
|
||||||
|
case TOGGLE_INFO_DRAWER:
|
||||||
|
newState = updateInfoDrawer(state, <ToggleInfoDrawerAction>action);
|
||||||
|
break;
|
||||||
|
case TOGGLE_DOCUMENT_DISPLAY_MODE:
|
||||||
|
newState = updateDocumentDisplayMode(state, <
|
||||||
|
ToggleDocumentDisplayMode
|
||||||
|
>action);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
newState = Object.assign({}, state);
|
newState = Object.assign({}, state);
|
||||||
}
|
}
|
||||||
@ -168,6 +183,31 @@ function updateCurrentUrl(state: AppState, action: SetCurrentUrlAction) {
|
|||||||
return newState;
|
return newState;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function updateInfoDrawer(state: AppState, action: ToggleInfoDrawerAction) {
|
||||||
|
const newState = Object.assign({}, state);
|
||||||
|
|
||||||
|
let value = state.infoDrawerOpened;
|
||||||
|
if (state.selection.isEmpty) {
|
||||||
|
value = false;
|
||||||
|
} else {
|
||||||
|
value = !value;
|
||||||
|
}
|
||||||
|
|
||||||
|
newState.infoDrawerOpened = value;
|
||||||
|
|
||||||
|
return newState;
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateDocumentDisplayMode(
|
||||||
|
state: AppState,
|
||||||
|
action: ToggleDocumentDisplayMode
|
||||||
|
) {
|
||||||
|
const newState = Object.assign({}, state);
|
||||||
|
newState.documentDisplayMode =
|
||||||
|
newState.documentDisplayMode === 'list' ? 'gallery' : 'list';
|
||||||
|
return newState;
|
||||||
|
}
|
||||||
|
|
||||||
function updateSelectedNodes(
|
function updateSelectedNodes(
|
||||||
state: AppState,
|
state: AppState,
|
||||||
action: SetSelectedNodesAction
|
action: SetSelectedNodesAction
|
||||||
@ -181,6 +221,7 @@ function updateSelectedNodes(
|
|||||||
let last = null;
|
let last = null;
|
||||||
let file = null;
|
let file = null;
|
||||||
let folder = null;
|
let folder = null;
|
||||||
|
let library = null;
|
||||||
|
|
||||||
if (nodes.length > 0) {
|
if (nodes.length > 0) {
|
||||||
first = nodes[0];
|
first = nodes[0];
|
||||||
@ -197,6 +238,15 @@ function updateSelectedNodes(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const libraries = [...action.payload].filter((node: any) => node.isLibrary);
|
||||||
|
if (libraries.length === 1) {
|
||||||
|
library = libraries[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isEmpty) {
|
||||||
|
newState.infoDrawerOpened = false;
|
||||||
|
}
|
||||||
|
|
||||||
newState.selection = {
|
newState.selection = {
|
||||||
count,
|
count,
|
||||||
nodes,
|
nodes,
|
||||||
@ -204,7 +254,9 @@ function updateSelectedNodes(
|
|||||||
first,
|
first,
|
||||||
last,
|
last,
|
||||||
file,
|
file,
|
||||||
folder
|
folder,
|
||||||
|
libraries,
|
||||||
|
library
|
||||||
};
|
};
|
||||||
return newState;
|
return newState;
|
||||||
}
|
}
|
||||||
|
@ -36,6 +36,8 @@ 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(selectApp, state => state.navigation);
|
||||||
export const currentFolder = createSelector(selectApp, state => state.navigation.currentFolder);
|
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 selectionWithFolder = createSelector(
|
export const selectionWithFolder = createSelector(
|
||||||
appSelection,
|
appSelection,
|
||||||
|
@ -36,6 +36,8 @@ export interface AppState {
|
|||||||
selection: SelectionState;
|
selection: SelectionState;
|
||||||
user: ProfileState;
|
user: ProfileState;
|
||||||
navigation: NavigationState;
|
navigation: NavigationState;
|
||||||
|
infoDrawerOpened: boolean;
|
||||||
|
documentDisplayMode: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const INITIAL_APP_STATE: AppState = {
|
export const INITIAL_APP_STATE: AppState = {
|
||||||
@ -52,12 +54,15 @@ export const INITIAL_APP_STATE: AppState = {
|
|||||||
},
|
},
|
||||||
selection: {
|
selection: {
|
||||||
nodes: [],
|
nodes: [],
|
||||||
|
libraries: [],
|
||||||
isEmpty: true,
|
isEmpty: true,
|
||||||
count: 0
|
count: 0
|
||||||
},
|
},
|
||||||
navigation: {
|
navigation: {
|
||||||
currentFolder: null
|
currentFolder: null
|
||||||
}
|
},
|
||||||
|
infoDrawerOpened: false,
|
||||||
|
documentDisplayMode: 'list'
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface AppStore {
|
export interface AppStore {
|
||||||
|
@ -23,14 +23,16 @@
|
|||||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { MinimalNodeEntity } from 'alfresco-js-api';
|
import { MinimalNodeEntity, SiteEntry } from 'alfresco-js-api';
|
||||||
|
|
||||||
export interface SelectionState {
|
export interface SelectionState {
|
||||||
count: number;
|
count: number;
|
||||||
nodes: MinimalNodeEntity[];
|
nodes: MinimalNodeEntity[];
|
||||||
|
libraries: SiteEntry[];
|
||||||
isEmpty: boolean;
|
isEmpty: boolean;
|
||||||
first?: MinimalNodeEntity;
|
first?: MinimalNodeEntity;
|
||||||
last?: MinimalNodeEntity;
|
last?: MinimalNodeEntity;
|
||||||
folder?: MinimalNodeEntity;
|
folder?: MinimalNodeEntity;
|
||||||
file?: MinimalNodeEntity;
|
file?: MinimalNodeEntity;
|
||||||
|
library?: SiteEntry;
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,85 @@
|
|||||||
],
|
],
|
||||||
|
|
||||||
"rules": [
|
"rules": [
|
||||||
|
{
|
||||||
|
"id": "app.toolbar.favorite.canToggle",
|
||||||
|
"comment": "workaround for recent files and search api issue",
|
||||||
|
"type": "core.every",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "rule",
|
||||||
|
"value": "core.some",
|
||||||
|
"parameters": [
|
||||||
|
{ "type": "rule", "value": "app.selection.canAddFavorite" },
|
||||||
|
{ "type": "rule", "value": "app.selection.canRemoveFavorite" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "rule",
|
||||||
|
"value": "core.some",
|
||||||
|
"parameters": [
|
||||||
|
{ "type": "rule", "value": "app.navigation.isRecentFiles" },
|
||||||
|
{ "type": "rule", "value": "app.navigation.isSharedFiles" },
|
||||||
|
{ "type": "rule", "value": "app.navigation.isSearchResults" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "app.toolbar.favorite.canAdd",
|
||||||
|
"type": "core.every",
|
||||||
|
"parameters": [
|
||||||
|
{ "type": "rule", "value": "app.selection.canAddFavorite" },
|
||||||
|
{ "type": "rule", "value": "app.navigation.isNotRecentFiles" },
|
||||||
|
{ "type": "rule", "value": "app.navigation.isNotSharedFiles" },
|
||||||
|
{ "type": "rule", "value": "app.navigation.isNotSearchResults" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "app.toolbar.favorite.canRemove",
|
||||||
|
"type": "core.every",
|
||||||
|
"parameters": [
|
||||||
|
{ "type": "rule", "value": "app.selection.canRemoveFavorite" },
|
||||||
|
{ "type": "rule", "value": "app.navigation.isNotRecentFiles" },
|
||||||
|
{ "type": "rule", "value": "app.navigation.isNotSharedFiles" },
|
||||||
|
{ "type": "rule", "value": "app.navigation.isNotSearchResults" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "app.toolbar.info",
|
||||||
|
"type": "core.every",
|
||||||
|
"parameters": [
|
||||||
|
{ "type": "rule", "value": "app.selection.notEmpty" },
|
||||||
|
{ "type": "rule", "value": "app.navigation.isNotLibraries" },
|
||||||
|
{ "type": "rule", "value": "app.navigation.isNotTrashcan" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "app.toolbar.canCopyNode",
|
||||||
|
"type": "core.every",
|
||||||
|
"parameters": [
|
||||||
|
{ "type": "rule", "value": "app.selection.notEmpty" },
|
||||||
|
{ "type": "rule", "value": "app.navigation.isNotTrashcan" },
|
||||||
|
{ "type": "rule", "value": "app.navigation.isNotLibraries" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "app.toolbar.permissions",
|
||||||
|
"type": "core.every",
|
||||||
|
"parameters": [
|
||||||
|
{ "type": "rule", "value": "app.selection.file" },
|
||||||
|
{ "type": "rule", "value": "app.selection.first.canUpdate" },
|
||||||
|
{ "type": "rule", "value": "app.navigation.isNotTrashcan" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "app.toolbar.versions",
|
||||||
|
"type": "core.every",
|
||||||
|
"parameters": [
|
||||||
|
{ "type": "rule", "value": "app.selection.file" },
|
||||||
|
{ "type": "rule", "value": "app.navigation.isNotTrashcan" }
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "app.trashcan.hasSelection",
|
"id": "app.trashcan.hasSelection",
|
||||||
"type": "core.every",
|
"type": "core.every",
|
||||||
@ -21,7 +100,8 @@
|
|||||||
"type": "core.every",
|
"type": "core.every",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{ "type": "rule", "value": "app.selection.folder" },
|
{ "type": "rule", "value": "app.selection.folder" },
|
||||||
{ "type": "rule", "value": "app.selection.folder.canUpdate" }
|
{ "type": "rule", "value": "app.selection.folder.canUpdate" },
|
||||||
|
{ "type": "rule", "value": "app.navigation.isNotTrashcan" }
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -57,13 +137,43 @@
|
|||||||
"id": "app.create.folder",
|
"id": "app.create.folder",
|
||||||
"type": "default",
|
"type": "default",
|
||||||
"icon": "create_new_folder",
|
"icon": "create_new_folder",
|
||||||
"title": "ext: Create Folder",
|
"title": "APP.NEW_MENU.MENU_ITEMS.CREATE_FOLDER",
|
||||||
|
"description": "APP.NEW_MENU.TOOLTIPS.CREATE_FOLDER",
|
||||||
|
"description-disabled": "APP.NEW_MENU.TOOLTIPS.CREATE_FOLDER_NOT_ALLOWED",
|
||||||
"actions": {
|
"actions": {
|
||||||
"click": "CREATE_FOLDER"
|
"click": "CREATE_FOLDER"
|
||||||
},
|
},
|
||||||
"rules": {
|
"rules": {
|
||||||
"enabled": "app.navigation.folder.canCreate"
|
"enabled": "app.navigation.folder.canCreate"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "app.create.uploadFile",
|
||||||
|
"type": "default",
|
||||||
|
"icon": "file_upload",
|
||||||
|
"title": "APP.NEW_MENU.MENU_ITEMS.UPLOAD_FILE",
|
||||||
|
"description": "APP.NEW_MENU.TOOLTIPS.UPLOAD_FILES",
|
||||||
|
"description-disabled": "APP.NEW_MENU.TOOLTIPS.UPLOAD_FILES_NOT_ALLOWED",
|
||||||
|
"actions": {
|
||||||
|
"click": "UPLOAD_FILES"
|
||||||
|
},
|
||||||
|
"rules": {
|
||||||
|
"enabled": "app.navigation.folder.canUpload"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "app.create.uploadFolder",
|
||||||
|
"type": "default",
|
||||||
|
"icon": "file_upload",
|
||||||
|
"title": "APP.NEW_MENU.MENU_ITEMS.UPLOAD_FOLDER",
|
||||||
|
"description": "APP.NEW_MENU.TOOLTIPS.UPLOAD_FOLDERS",
|
||||||
|
"description-disabled": "APP.NEW_MENU.TOOLTIPS.UPLOAD_FOLDERS_NOT_ALLOWED",
|
||||||
|
"actions": {
|
||||||
|
"click": "UPLOAD_FOLDER"
|
||||||
|
},
|
||||||
|
"rules": {
|
||||||
|
"enabled": "app.navigation.folder.canUpload"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"navbar": [
|
"navbar": [
|
||||||
@ -122,24 +232,6 @@
|
|||||||
],
|
],
|
||||||
"content": {
|
"content": {
|
||||||
"actions": [
|
"actions": [
|
||||||
{
|
|
||||||
"id": "app.toolbar.separator.1",
|
|
||||||
"order": 5,
|
|
||||||
"type": "separator"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "app.toolbar.createFolder",
|
|
||||||
"type": "button",
|
|
||||||
"order": 10,
|
|
||||||
"title": "APP.NEW_MENU.TOOLTIPS.CREATE_FOLDER",
|
|
||||||
"icon": "create_new_folder",
|
|
||||||
"actions": {
|
|
||||||
"click": "CREATE_FOLDER"
|
|
||||||
},
|
|
||||||
"rules": {
|
|
||||||
"visible": "app.navigation.folder.canCreate"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"id": "app.toolbar.preview",
|
"id": "app.toolbar.preview",
|
||||||
"type": "button",
|
"type": "button",
|
||||||
@ -204,16 +296,261 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "app.toolbar.separator.2",
|
"id": "app.toolbar.createLibrary",
|
||||||
"order": 200,
|
"type": "button",
|
||||||
"type": "separator"
|
"title": "Create Library",
|
||||||
|
"icon": "create_new_folder",
|
||||||
|
"actions": {
|
||||||
|
"click": "CREATE_LIBRARY"
|
||||||
|
},
|
||||||
|
"rules": {
|
||||||
|
"visible": "app.navigation.isLibraries"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"disabled": true,
|
"id": "app.toolbar.info",
|
||||||
"id": "app.toolbar.custom.1",
|
|
||||||
"order": 200,
|
|
||||||
"type": "custom",
|
"type": "custom",
|
||||||
"component": "app.demo.button"
|
"component": "app.toolbar.toggleInfoDrawer",
|
||||||
|
"rules": {
|
||||||
|
"visible": "app.toolbar.info"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "app.toolbar.more",
|
||||||
|
"type": "menu",
|
||||||
|
"icon": "more_vert",
|
||||||
|
"title": "APP.ACTIONS.MORE",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"id": "app.toolbar.favorite",
|
||||||
|
"comment": "workaround for Recent Files and Search API issue",
|
||||||
|
"type": "custom",
|
||||||
|
"component": "app.toolbar.toggleFavorite",
|
||||||
|
"rules": {
|
||||||
|
"visible": "app.toolbar.favorite.canToggle"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "app.toolbar.favorite.add",
|
||||||
|
"type": "button",
|
||||||
|
"title": "APP.ACTIONS.FAVORITE",
|
||||||
|
"icon": "star_border",
|
||||||
|
"actions": {
|
||||||
|
"click": "ADD_FAVORITE"
|
||||||
|
},
|
||||||
|
"rules": {
|
||||||
|
"visible": "app.toolbar.favorite.canAdd"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "app.toolbar.favorite.remove",
|
||||||
|
"type": "button",
|
||||||
|
"title": "APP.ACTIONS.FAVORITE",
|
||||||
|
"icon": "star",
|
||||||
|
"actions": {
|
||||||
|
"click": "REMOVE_FAVORITE"
|
||||||
|
},
|
||||||
|
"rules": {
|
||||||
|
"visible": "app.toolbar.favorite.canRemove"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "app.toolbar.copy",
|
||||||
|
"type": "button",
|
||||||
|
"title": "APP.ACTIONS.COPY",
|
||||||
|
"icon": "content_copy",
|
||||||
|
"actions": {
|
||||||
|
"click": "COPY_NODES"
|
||||||
|
},
|
||||||
|
"rules": {
|
||||||
|
"visible": "app.toolbar.canCopyNode"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "app.toolbar.move",
|
||||||
|
"type": "button",
|
||||||
|
"title": "APP.ACTIONS.MOVE",
|
||||||
|
"icon": "library_books",
|
||||||
|
"actions": {
|
||||||
|
"click": "MOVE_NODES"
|
||||||
|
},
|
||||||
|
"rules": {
|
||||||
|
"visible": "app.selection.canDelete"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "app.toolbar.share",
|
||||||
|
"type": "button",
|
||||||
|
"title": "APP.ACTIONS.SHARE",
|
||||||
|
"icon": "share",
|
||||||
|
"actions": {
|
||||||
|
"click": "SHARE_NODE"
|
||||||
|
},
|
||||||
|
"rules": {
|
||||||
|
"visible": "app.selection.file.canShare"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "app.toolbar.unshare",
|
||||||
|
"type": "button",
|
||||||
|
"title": "APP.ACTIONS.UNSHARE",
|
||||||
|
"icon": "stop_screen_share",
|
||||||
|
"actions": {
|
||||||
|
"click": "UNSHARE_NODES"
|
||||||
|
},
|
||||||
|
"rules": {
|
||||||
|
"visible": "app.selection.canUnshare"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "app.toolbar.delete",
|
||||||
|
"type": "button",
|
||||||
|
"title": "APP.ACTIONS.DELETE",
|
||||||
|
"icon": "delete",
|
||||||
|
"actions": {
|
||||||
|
"click": "DELETE_NODES"
|
||||||
|
},
|
||||||
|
"rules": {
|
||||||
|
"visible": "app.selection.canDelete"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "app.toolbar.deleteLibrary",
|
||||||
|
"type": "button",
|
||||||
|
"title": "APP.ACTIONS.DELETE",
|
||||||
|
"icon": "delete",
|
||||||
|
"actions": {
|
||||||
|
"click": "DELETE_LIBRARY"
|
||||||
|
},
|
||||||
|
"rules": {
|
||||||
|
"visible": "app.selection.library"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "app.toolbar.versions",
|
||||||
|
"type": "button",
|
||||||
|
"title": "APP.ACTIONS.VERSIONS",
|
||||||
|
"icon": "history",
|
||||||
|
"actions": {
|
||||||
|
"click": "MANAGE_VERSIONS"
|
||||||
|
},
|
||||||
|
"rules": {
|
||||||
|
"visible": "app.toolbar.versions"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "app.toolbar.permissions",
|
||||||
|
"type": "button",
|
||||||
|
"title": "APP.ACTIONS.PERMISSIONS",
|
||||||
|
"icon": "settings_input_component",
|
||||||
|
"actions": {
|
||||||
|
"click": "MANAGE_PERMISSIONS"
|
||||||
|
},
|
||||||
|
"rules": {
|
||||||
|
"visible": "app.toolbar.permissions"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"viewer": {
|
||||||
|
"actions": [
|
||||||
|
{
|
||||||
|
"id": "app.viewer.favorite.add",
|
||||||
|
"type": "button",
|
||||||
|
"title": "APP.ACTIONS.FAVORITE",
|
||||||
|
"icon": "star_border",
|
||||||
|
"actions": {
|
||||||
|
"click": "ADD_FAVORITE"
|
||||||
|
},
|
||||||
|
"rules": {
|
||||||
|
"visible": "app.toolbar.favorite.canAdd"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "app.viewer.favorite.remove",
|
||||||
|
"type": "button",
|
||||||
|
"title": "APP.ACTIONS.FAVORITE",
|
||||||
|
"icon": "star",
|
||||||
|
"actions": {
|
||||||
|
"click": "REMOVE_FAVORITE"
|
||||||
|
},
|
||||||
|
"rules": {
|
||||||
|
"visible": "app.toolbar.favorite.canRemove"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "app.viewer.share",
|
||||||
|
"type": "button",
|
||||||
|
"title": "APP.ACTIONS.SHARE",
|
||||||
|
"icon": "share",
|
||||||
|
"actions": {
|
||||||
|
"click": "SHARE_NODE"
|
||||||
|
},
|
||||||
|
"rules": {
|
||||||
|
"visible": "app.selection.file.canShare"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "app.viewer.copy",
|
||||||
|
"type": "button",
|
||||||
|
"title": "APP.ACTIONS.COPY",
|
||||||
|
"icon": "content_copy",
|
||||||
|
"actions": {
|
||||||
|
"click": "COPY_NODES"
|
||||||
|
},
|
||||||
|
"rules": {
|
||||||
|
"visible": "app.toolbar.canCopyNode"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "app.viewer.move",
|
||||||
|
"type": "button",
|
||||||
|
"title": "APP.ACTIONS.MOVE",
|
||||||
|
"icon": "library_books",
|
||||||
|
"actions": {
|
||||||
|
"click": "MOVE_NODES"
|
||||||
|
},
|
||||||
|
"rules": {
|
||||||
|
"visible": "app.selection.canDelete"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "app.viewer.delete",
|
||||||
|
"type": "button",
|
||||||
|
"title": "APP.ACTIONS.DELETE",
|
||||||
|
"icon": "delete",
|
||||||
|
"actions": {
|
||||||
|
"click": "DELETE_NODES"
|
||||||
|
},
|
||||||
|
"rules": {
|
||||||
|
"visible": "app.selection.canDelete"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "app.viewer.versions",
|
||||||
|
"type": "button",
|
||||||
|
"title": "APP.ACTIONS.VERSIONS",
|
||||||
|
"icon": "history",
|
||||||
|
"actions": {
|
||||||
|
"click": "MANAGE_VERSIONS"
|
||||||
|
},
|
||||||
|
"rules": {
|
||||||
|
"visible": "app.toolbar.versions"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "app.viewer.permissions",
|
||||||
|
"type": "button",
|
||||||
|
"title": "APP.ACTIONS.PERMISSIONS",
|
||||||
|
"icon": "settings_input_component",
|
||||||
|
"actions": {
|
||||||
|
"click": "MANAGE_PERMISSIONS"
|
||||||
|
},
|
||||||
|
"rules": {
|
||||||
|
"visible": "app.toolbar.permissions"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -26,11 +26,11 @@
|
|||||||
},
|
},
|
||||||
"TOOLTIPS": {
|
"TOOLTIPS": {
|
||||||
"CREATE_FOLDER": "Create new folder",
|
"CREATE_FOLDER": "Create new folder",
|
||||||
"CREATE_FOLDER_NOT_ALLOWED": "You can't create a folder here. You might not have the required permissions, check with your IT Team.",
|
"CREATE_FOLDER_NOT_ALLOWED": "Folders cannot be created whilst viewing the current items.",
|
||||||
"UPLOAD_FILES": "Select files to upload",
|
"UPLOAD_FILES": "Select files to upload",
|
||||||
"UPLOAD_FILES_NOT_ALLOWED": "You need permissions to upload here, check with your IT Team.",
|
"UPLOAD_FILES_NOT_ALLOWED": "Files cannot be uploaded whilst viewing the current items",
|
||||||
"UPLOAD_FOLDERS": "Select folders to upload",
|
"UPLOAD_FOLDERS": "Select folders to upload",
|
||||||
"UPLOAD_FOLDERS_NOT_ALLOWED": "You need permissions to upload here, check with your IT Team."
|
"UPLOAD_FOLDERS_NOT_ALLOWED": "Folders cannot be uploaded whilst viewing the current items"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"BROWSE": {
|
"BROWSE": {
|
||||||
|
@ -27,7 +27,7 @@
|
|||||||
"openWith": [
|
"openWith": [
|
||||||
{
|
{
|
||||||
"id": "plugin1.viewer.openWith.action1",
|
"id": "plugin1.viewer.openWith.action1",
|
||||||
"type": "default",
|
"type": "button",
|
||||||
"icon": "build",
|
"icon": "build",
|
||||||
"title": "Snackbar",
|
"title": "Snackbar",
|
||||||
"actions": {
|
"actions": {
|
||||||
@ -53,6 +53,52 @@
|
|||||||
"content": {
|
"content": {
|
||||||
"actions": [
|
"actions": [
|
||||||
{
|
{
|
||||||
|
"disabled": true,
|
||||||
|
"id": "app.toolbar.createFolder",
|
||||||
|
"type": "button",
|
||||||
|
"order": 10,
|
||||||
|
"title": "APP.NEW_MENU.MENU_ITEMS.CREATE_FOLDER",
|
||||||
|
"description": "APP.NEW_MENU.TOOLTIPS.CREATE_FOLDER",
|
||||||
|
"icon": "create_new_folder",
|
||||||
|
"actions": {
|
||||||
|
"click": "CREATE_FOLDER"
|
||||||
|
},
|
||||||
|
"rules": {
|
||||||
|
"visible": "app.navigation.folder.canCreate"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"disabled": true,
|
||||||
|
"id": "app.toolbar.uploadFile",
|
||||||
|
"order": 11,
|
||||||
|
"type": "button",
|
||||||
|
"icon": "file_upload",
|
||||||
|
"title": "APP.NEW_MENU.MENU_ITEMS.UPLOAD_FILE",
|
||||||
|
"description": "APP.NEW_MENU.TOOLTIPS.UPLOAD_FILES",
|
||||||
|
"actions": {
|
||||||
|
"click": "UPLOAD_FILES"
|
||||||
|
},
|
||||||
|
"rules": {
|
||||||
|
"visible": "app.navigation.folder.canUpload"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"disabled": true,
|
||||||
|
"id": "app.toolbar.uploadFolder",
|
||||||
|
"order": 12,
|
||||||
|
"type": "button",
|
||||||
|
"icon": "cloud_upload",
|
||||||
|
"title": "APP.NEW_MENU.MENU_ITEMS.UPLOAD_FOLDER",
|
||||||
|
"description": "APP.NEW_MENU.TOOLTIPS.UPLOAD_FOLDERS",
|
||||||
|
"actions": {
|
||||||
|
"click": "UPLOAD_FOLDER"
|
||||||
|
},
|
||||||
|
"rules": {
|
||||||
|
"visible": "app.navigation.folder.canUpload"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"disabled": true,
|
||||||
"id": "plugin1.toolbar.menu1",
|
"id": "plugin1.toolbar.menu1",
|
||||||
"type": "menu",
|
"type": "menu",
|
||||||
"icon": "storage",
|
"icon": "storage",
|
||||||
@ -70,6 +116,7 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"disabled": true,
|
||||||
"id": "plugin1.toolbar.separator3",
|
"id": "plugin1.toolbar.separator3",
|
||||||
"order": 301,
|
"order": 301,
|
||||||
"type": "separator"
|
"type": "separator"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user