mirror of
https://github.com/Alfresco/alfresco-content-app.git
synced 2025-05-12 17:04:46 +00:00
* [ACS-6813] Make search results list column configurable * [ACS-6813] Documentation update * [ACS-6813] Typo fix
593 lines
20 KiB
TypeScript
593 lines
20 KiB
TypeScript
/*!
|
|
* Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved.
|
|
*
|
|
* Alfresco Example Content Application
|
|
*
|
|
* 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
|
|
* from Hyland Software. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
import { Injectable } from '@angular/core';
|
|
import { Store } from '@ngrx/store';
|
|
import { MatIconRegistry } from '@angular/material/icon';
|
|
import { DomSanitizer } from '@angular/platform-browser';
|
|
import { AppStore, getRuleContext } from '@alfresco/aca-shared/store';
|
|
import {
|
|
ContentActionRef,
|
|
ContentActionType,
|
|
DocumentListPresetRef,
|
|
ExtensionConfig,
|
|
ExtensionLoaderService,
|
|
ExtensionRef,
|
|
ExtensionService,
|
|
IconRef,
|
|
mergeArrays,
|
|
mergeObjects,
|
|
NavBarGroupRef,
|
|
NavigationState,
|
|
ProfileState,
|
|
reduceEmptyMenus,
|
|
reduceSeparators,
|
|
RuleContext,
|
|
RuleEvaluator,
|
|
SelectionState,
|
|
SidebarTabRef,
|
|
sortByOrder
|
|
} from '@alfresco/adf-extensions';
|
|
import { AppConfigService, AuthenticationService, LogService } from '@alfresco/adf-core';
|
|
import { BehaviorSubject, Observable } from 'rxjs';
|
|
import { NodeEntry, RepositoryInfo } from '@alfresco/js-api';
|
|
import { ViewerRules } from '../models/viewer.rules';
|
|
import { Badge, SettingsGroupRef } from '../models/types';
|
|
import { NodePermissionService } from '../services/node-permission.service';
|
|
import { filter, map } from 'rxjs/operators';
|
|
import { SearchCategory } from '@alfresco/adf-content-services';
|
|
|
|
@Injectable({
|
|
providedIn: 'root'
|
|
})
|
|
export class AppExtensionService implements RuleContext {
|
|
private _references = new BehaviorSubject<ExtensionRef[]>([]);
|
|
|
|
navbar: Array<NavBarGroupRef> = [];
|
|
sidebarTabs: Array<SidebarTabRef> = [];
|
|
contentMetadata: any;
|
|
search: any;
|
|
viewerRules: ViewerRules = {};
|
|
settingGroups: Array<SettingsGroupRef> = [];
|
|
|
|
private _headerActions = new BehaviorSubject<Array<ContentActionRef>>([]);
|
|
private _toolbarActions = new BehaviorSubject<Array<ContentActionRef>>([]);
|
|
private _viewerToolbarActions = new BehaviorSubject<Array<ContentActionRef>>([]);
|
|
private _sharedLinkViewerToolbarActions = new BehaviorSubject<Array<ContentActionRef>>([]);
|
|
private _contextMenuActions = new BehaviorSubject<Array<ContentActionRef>>([]);
|
|
private _openWithActions = new BehaviorSubject<Array<ContentActionRef>>([]);
|
|
private _createActions = new BehaviorSubject<Array<ContentActionRef>>([]);
|
|
private _mainActions = new BehaviorSubject<ContentActionRef>(null);
|
|
private _sidebarActions = new BehaviorSubject<Array<ContentActionRef>>([]);
|
|
private _badges = new BehaviorSubject<Array<Badge>>([]);
|
|
private _filesDocumentListPreset = new BehaviorSubject<Array<DocumentListPresetRef>>([]);
|
|
private _customMetadataPanels = new BehaviorSubject<Array<ContentActionRef>>([]);
|
|
|
|
documentListPresets: {
|
|
libraries: Array<DocumentListPresetRef>;
|
|
favoriteLibraries: Array<DocumentListPresetRef>;
|
|
shared: Array<DocumentListPresetRef>;
|
|
recent: Array<DocumentListPresetRef>;
|
|
favorites: Array<DocumentListPresetRef>;
|
|
trashcan: Array<DocumentListPresetRef>;
|
|
searchLibraries: Array<DocumentListPresetRef>;
|
|
searchResults: Array<DocumentListPresetRef>;
|
|
} = {
|
|
libraries: [],
|
|
favoriteLibraries: [],
|
|
shared: [],
|
|
recent: [],
|
|
favorites: [],
|
|
trashcan: [],
|
|
searchLibraries: [],
|
|
searchResults: []
|
|
};
|
|
|
|
selection: SelectionState;
|
|
navigation: NavigationState;
|
|
profile: ProfileState;
|
|
repository: RepositoryInfo;
|
|
withCredentials: boolean;
|
|
|
|
references$: Observable<ExtensionRef[]>;
|
|
filesDocumentListPreset$: Observable<DocumentListPresetRef[]> = this._filesDocumentListPreset.asObservable();
|
|
|
|
config: ExtensionConfig;
|
|
|
|
constructor(
|
|
public auth: AuthenticationService,
|
|
protected store: Store<AppStore>,
|
|
protected loader: ExtensionLoaderService,
|
|
protected extensions: ExtensionService,
|
|
public permissions: NodePermissionService,
|
|
public appConfig: AppConfigService,
|
|
protected matIconRegistry: MatIconRegistry,
|
|
protected sanitizer: DomSanitizer,
|
|
protected logger: LogService
|
|
) {
|
|
this.references$ = this._references.asObservable();
|
|
|
|
this.store.select(getRuleContext).subscribe((result) => {
|
|
this.selection = result.selection;
|
|
this.navigation = result.navigation;
|
|
this.profile = result.profile;
|
|
this.repository = result.repository;
|
|
|
|
if (this.config) {
|
|
this.setup(this.config);
|
|
}
|
|
});
|
|
}
|
|
|
|
async load() {
|
|
this.config = await this.extensions.load();
|
|
this.setup(this.config);
|
|
}
|
|
|
|
setup(config: ExtensionConfig) {
|
|
if (!config) {
|
|
this.logger.error('Extension configuration not found');
|
|
return;
|
|
}
|
|
|
|
this.settingGroups = this.loader.getElements<SettingsGroupRef>(config, 'settings');
|
|
|
|
this._headerActions.next(this.loader.getContentActions(config, 'features.header'));
|
|
this._sidebarActions.next(this.loader.getContentActions(config, 'features.sidebar.toolbar'));
|
|
this._toolbarActions.next(this.loader.getContentActions(config, 'features.toolbar'));
|
|
this._viewerToolbarActions.next(this.loader.getContentActions(config, 'features.viewer.toolbarActions'));
|
|
this._sharedLinkViewerToolbarActions.next(this.loader.getContentActions(config, 'features.viewer.shared.toolbarActions'));
|
|
this._contextMenuActions.next(this.loader.getContentActions(config, 'features.contextMenu'));
|
|
this._openWithActions.next(this.loader.getContentActions(config, 'features.viewer.openWith'));
|
|
this._createActions.next(this.loader.getElements<ContentActionRef>(config, 'features.create'));
|
|
this._mainActions.next(this.loader.getFeatures(config).mainAction);
|
|
this._badges.next(this.loader.getElements<Badge>(config, 'features.badges'));
|
|
this._filesDocumentListPreset.next(this.getDocumentListPreset(config, 'files'));
|
|
this._customMetadataPanels.next(this.loader.getElements<ContentActionRef>(config, 'features.customMetadataPanels'));
|
|
|
|
this.navbar = this.loadNavBar(config);
|
|
this.sidebarTabs = this.loader.getElements<SidebarTabRef>(config, 'features.sidebar.tabs');
|
|
this.contentMetadata = this.loadContentMetadata(config);
|
|
this.search = this.loadSearchForms(config);
|
|
this.search?.forEach((searchSet) => {
|
|
searchSet.categories = searchSet.categories?.filter((category) => this.filterVisible(category));
|
|
});
|
|
|
|
this.documentListPresets = {
|
|
libraries: this.getDocumentListPreset(config, 'libraries'),
|
|
favoriteLibraries: this.getDocumentListPreset(config, 'favoriteLibraries'),
|
|
shared: this.getDocumentListPreset(config, 'shared'),
|
|
recent: this.getDocumentListPreset(config, 'recent'),
|
|
favorites: this.getDocumentListPreset(config, 'favorites'),
|
|
trashcan: this.getDocumentListPreset(config, 'trashcan'),
|
|
searchLibraries: this.getDocumentListPreset(config, 'search-libraries'),
|
|
searchResults: this.getDocumentListPreset(config, 'search-results')
|
|
};
|
|
|
|
this.withCredentials = this.appConfig.get<boolean>('auth.withCredentials', false);
|
|
|
|
if (config.features?.viewer) {
|
|
this.viewerRules = (config.features.viewer['rules'] as ViewerRules) || {};
|
|
}
|
|
|
|
this.registerIcons(config);
|
|
|
|
const references = (config.$references || []).filter((entry) => typeof entry === 'object').map((entry) => entry as ExtensionRef);
|
|
this._references.next(references);
|
|
}
|
|
|
|
protected registerIcons(config: ExtensionConfig) {
|
|
const icons: Array<IconRef> = this.loader.getElements<IconRef>(config, 'features.icons').filter((entry) => !entry.disabled);
|
|
|
|
for (const icon of icons) {
|
|
const [ns, id] = icon.id.split(':');
|
|
const value = icon.value;
|
|
|
|
if (!value) {
|
|
this.logger.warn(`Missing icon value for "${icon.id}".`);
|
|
} else if (!ns || !id) {
|
|
this.logger.warn(`Incorrect icon id format.`);
|
|
} else {
|
|
this.matIconRegistry.addSvgIconInNamespace(ns, id, this.sanitizer.bypassSecurityTrustResourceUrl(value));
|
|
}
|
|
}
|
|
}
|
|
|
|
protected loadNavBar(config: ExtensionConfig): Array<NavBarGroupRef> {
|
|
return this.loader.getElements<NavBarGroupRef>(config, 'features.navbar');
|
|
}
|
|
|
|
protected getDocumentListPreset(config: ExtensionConfig, key: string): DocumentListPresetRef[] {
|
|
return this.loader
|
|
.getElements<DocumentListPresetRef>(config, `features.documentList.${key}`)
|
|
.filter((group) => this.filterVisible(group))
|
|
.filter((entry) => !entry.disabled)
|
|
.map((entry) => {
|
|
entry.resizable = entry.resizable ?? true;
|
|
return entry;
|
|
})
|
|
.sort(sortByOrder);
|
|
}
|
|
|
|
getApplicationNavigation(elements): Array<NavBarGroupRef> {
|
|
return elements
|
|
.filter((group) => this.filterVisible(group))
|
|
.map((group) => ({
|
|
...group,
|
|
items: (group.items || [])
|
|
.filter((entry) => !entry.disabled)
|
|
.filter((item) => this.filterVisible(item))
|
|
.sort(sortByOrder)
|
|
.map((item) => {
|
|
if (item.children && item.children.length > 0) {
|
|
item.children = item.children
|
|
.filter((entry) => !entry.disabled)
|
|
.filter((child) => this.filterVisible(child))
|
|
.sort(sortByOrder)
|
|
.map((child) => {
|
|
if (child.component) {
|
|
return {
|
|
...child
|
|
};
|
|
}
|
|
|
|
if (!child.click) {
|
|
const childRouteRef = this.extensions.getRouteById(child.route);
|
|
const childUrl = `/${childRouteRef ? childRouteRef.path : child.route}`;
|
|
return {
|
|
...child,
|
|
url: childUrl
|
|
};
|
|
}
|
|
|
|
return {
|
|
...child,
|
|
action: child.click
|
|
};
|
|
});
|
|
|
|
return {
|
|
...item
|
|
};
|
|
}
|
|
|
|
if (item.component) {
|
|
return { ...item };
|
|
}
|
|
|
|
if (!item.click) {
|
|
const routeRef = this.extensions.getRouteById(item.route);
|
|
const url = `/${routeRef ? routeRef.path : item.route}`;
|
|
return {
|
|
...item,
|
|
url
|
|
};
|
|
}
|
|
|
|
return {
|
|
...item,
|
|
action: item.click
|
|
};
|
|
})
|
|
.reduce(reduceEmptyMenus, [])
|
|
}));
|
|
}
|
|
|
|
loadContentMetadata(config: ExtensionConfig): any {
|
|
const elements = this.loader.getElements<any>(config, 'features.content-metadata-presets');
|
|
if (!elements.length) {
|
|
return null;
|
|
}
|
|
|
|
let presets = {};
|
|
presets = this.filterDisabled(mergeObjects(presets, ...elements));
|
|
|
|
const metadata = this.appConfig.config['content-metadata'] || {};
|
|
metadata.presets = presets;
|
|
|
|
this.appConfig.config['content-metadata'] = metadata;
|
|
return { presets };
|
|
}
|
|
|
|
loadSearchForms(config: ExtensionConfig): any {
|
|
const elements = this.loader.getElements<any>(config, 'features.search');
|
|
if (!elements.length) {
|
|
return null;
|
|
}
|
|
|
|
const search = mergeArrays([], elements)
|
|
.filter((entry) => !entry.disabled)
|
|
.filter((entry) => this.filterVisible(entry))
|
|
.sort(sortByOrder);
|
|
|
|
this.appConfig.config['search'] = search;
|
|
return search;
|
|
}
|
|
|
|
filterDisabled(object: Array<{ disabled: boolean }> | { disabled: boolean }) {
|
|
if (Array.isArray(object)) {
|
|
return object.filter((item) => !item.disabled).map((item) => this.filterDisabled(item));
|
|
} else if (typeof object === 'object') {
|
|
if (!object.disabled) {
|
|
Object.keys(object).forEach((prop) => {
|
|
object[prop] = this.filterDisabled(object[prop]);
|
|
});
|
|
return object;
|
|
}
|
|
} else {
|
|
return object;
|
|
}
|
|
}
|
|
|
|
getNavigationGroups(): Array<NavBarGroupRef> {
|
|
return this.navbar;
|
|
}
|
|
|
|
getSidebarTabs(): Array<SidebarTabRef> {
|
|
return this.sidebarTabs.filter((action) => this.filterVisible(action));
|
|
}
|
|
|
|
private setActionDisabledFromRule(action: ContentActionRef) {
|
|
let disabled = false;
|
|
|
|
if (action?.rules?.enabled) {
|
|
disabled = !this.extensions.evaluateRule(action.rules.enabled, this);
|
|
}
|
|
|
|
return {
|
|
...action,
|
|
disabled
|
|
};
|
|
}
|
|
|
|
updateSidebarActions() {
|
|
this._sidebarActions.next(this.loader.getContentActions(this.config, 'features.sidebar.toolbar'));
|
|
}
|
|
|
|
getCreateActions(): Observable<Array<ContentActionRef>> {
|
|
return this._createActions.pipe(
|
|
map((createActions) =>
|
|
createActions
|
|
.filter((action) => this.filterVisible(action))
|
|
.map((action) => this.copyAction(action))
|
|
.map((action) => this.buildMenu(action))
|
|
.map((action) => this.setActionDisabledFromRule(action))
|
|
)
|
|
);
|
|
}
|
|
|
|
getMainAction(): Observable<ContentActionRef> {
|
|
return this._mainActions.pipe(
|
|
filter((mainAction) => mainAction && this.filterVisible(mainAction)),
|
|
map((mainAction) => {
|
|
let actionCopy = this.copyAction(mainAction);
|
|
actionCopy = this.setActionDisabledFromRule(actionCopy);
|
|
return actionCopy;
|
|
})
|
|
);
|
|
}
|
|
|
|
getBadges(node: NodeEntry): Observable<Array<Badge>> {
|
|
return this._badges.pipe(map((badges) => badges.filter((badge) => this.evaluateRule(badge.rules.visible, node))));
|
|
}
|
|
|
|
getCustomMetadataPanels(node: NodeEntry): Observable<Array<ContentActionRef>> {
|
|
return this._customMetadataPanels.pipe(map((panels) => panels.filter((panel) => this.evaluateRule(panel.rules.visible, node))));
|
|
}
|
|
|
|
private buildMenu(actionRef: ContentActionRef): ContentActionRef {
|
|
if (actionRef.type === ContentActionType.menu && actionRef.children && actionRef.children.length > 0) {
|
|
const children = actionRef.children.filter((action) => this.filterVisible(action)).map((action) => this.buildMenu(action));
|
|
|
|
actionRef.children = children
|
|
.map((action) => this.setActionDisabledFromRule(action))
|
|
.sort(sortByOrder)
|
|
.reduce(reduceEmptyMenus, [])
|
|
.reduce(reduceSeparators, []);
|
|
}
|
|
|
|
return actionRef;
|
|
}
|
|
|
|
private getAllowedActions(actions: ContentActionRef[]): ContentActionRef[] {
|
|
return (actions || [])
|
|
.filter((action) => this.filterVisible(action))
|
|
.map((action) => {
|
|
if (action.type === ContentActionType.menu) {
|
|
const copy = this.copyAction(action);
|
|
if (copy.children && copy.children.length > 0) {
|
|
copy.children = copy.children
|
|
.filter((entry) => !entry.disabled)
|
|
.filter((childAction) => this.filterVisible(childAction))
|
|
.sort(sortByOrder)
|
|
.reduce(reduceSeparators, []);
|
|
}
|
|
return copy;
|
|
}
|
|
return action;
|
|
})
|
|
.map((action) => this.setActionDisabledFromRule(action))
|
|
.reduce(reduceEmptyMenus, [])
|
|
.reduce(reduceSeparators, []);
|
|
}
|
|
|
|
getAllowedSidebarActions(): Observable<Array<ContentActionRef>> {
|
|
return this._sidebarActions.pipe(map((sidebarActions) => this.getAllowedActions(sidebarActions)));
|
|
}
|
|
|
|
getAllowedToolbarActions(): Observable<Array<ContentActionRef>> {
|
|
return this._toolbarActions.pipe(map((toolbarActions) => this.getAllowedActions(toolbarActions)));
|
|
}
|
|
|
|
getViewerToolbarActions(): Observable<Array<ContentActionRef>> {
|
|
return this._viewerToolbarActions.pipe(map((viewerToolbarActions) => this.getAllowedActions(viewerToolbarActions)));
|
|
}
|
|
|
|
getOpenWithActions(): Observable<Array<ContentActionRef>> {
|
|
return this._openWithActions.pipe(map((openWithActions) => this.getAllowedActions(openWithActions)));
|
|
}
|
|
|
|
getSharedLinkViewerToolbarActions(): Observable<Array<ContentActionRef>> {
|
|
return this._sharedLinkViewerToolbarActions.pipe(
|
|
map((sharedLinkViewerToolbarActions) => (!this.selection.isEmpty ? this.getAllowedActions(sharedLinkViewerToolbarActions) : []))
|
|
);
|
|
}
|
|
|
|
getHeaderActions(): Observable<Array<ContentActionRef>> {
|
|
return this._headerActions.pipe(
|
|
map((headerActions) =>
|
|
headerActions
|
|
.filter((action) => this.filterVisible(action))
|
|
.map((action) => {
|
|
if (action.type === ContentActionType.menu) {
|
|
const copy = this.copyAction(action);
|
|
if (copy.children && copy.children.length > 0) {
|
|
copy.children = copy.children
|
|
.filter((childAction) => this.filterVisible(childAction))
|
|
.sort(sortByOrder)
|
|
.reduce(reduceEmptyMenus, [])
|
|
.reduce(reduceSeparators, []);
|
|
}
|
|
return copy;
|
|
}
|
|
|
|
return action;
|
|
})
|
|
.map((action) => this.setActionDisabledFromRule(action))
|
|
.sort(sortByOrder)
|
|
.reduce(reduceEmptyMenus, [])
|
|
.reduce(reduceSeparators, [])
|
|
)
|
|
);
|
|
}
|
|
|
|
getAllowedContextMenuActions(): Observable<Array<ContentActionRef>> {
|
|
return this._contextMenuActions.pipe(map((contextMenuActions) => (!this.selection.isEmpty ? this.getAllowedActions(contextMenuActions) : [])));
|
|
}
|
|
|
|
getSettingsGroups(): Array<SettingsGroupRef> {
|
|
return this.settingGroups.filter((group) => this.filterVisible(group));
|
|
}
|
|
|
|
copyAction(action: ContentActionRef): ContentActionRef {
|
|
return {
|
|
...action,
|
|
children: (action.children || []).map((child) => this.copyAction(child))
|
|
};
|
|
}
|
|
|
|
filterVisible(action: ContentActionRef | SettingsGroupRef | SidebarTabRef | DocumentListPresetRef | SearchCategory): boolean {
|
|
if (action?.rules?.visible) {
|
|
if (Array.isArray(action.rules.visible)) {
|
|
return action.rules.visible.every((rule) => this.extensions.evaluateRule(rule, this));
|
|
}
|
|
return this.extensions.evaluateRule(action.rules.visible, this);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
isViewerExtensionDisabled(extension: any): boolean {
|
|
if (extension) {
|
|
if (extension.disabled) {
|
|
return true;
|
|
}
|
|
|
|
if (extension.rules?.disabled) {
|
|
return this.extensions.evaluateRule(extension.rules.disabled, this);
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
runActionById(id: string, additionalPayload?: any) {
|
|
const action = this.extensions.getActionById(id);
|
|
if (action) {
|
|
const { type, payload } = action;
|
|
const context = {
|
|
selection: this.selection
|
|
};
|
|
const expression = this.extensions.runExpression(payload, context);
|
|
|
|
this.store.dispatch({
|
|
type,
|
|
payload: expression,
|
|
configuration: additionalPayload
|
|
});
|
|
} else {
|
|
this.store.dispatch({
|
|
type: id,
|
|
configuration: additionalPayload
|
|
});
|
|
}
|
|
}
|
|
|
|
// todo: move to ADF/RuleService
|
|
isRuleDefined(ruleId: string): boolean {
|
|
return !!(ruleId && this.getEvaluator(ruleId));
|
|
}
|
|
|
|
// todo: move to ADF/RuleService
|
|
evaluateRule(ruleId: string, ...args: any[]): boolean {
|
|
const evaluator = this.getEvaluator(ruleId);
|
|
|
|
if (evaluator) {
|
|
return evaluator(this, ...args);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
getEvaluator(key: string): RuleEvaluator {
|
|
return this.extensions.getEvaluator(key);
|
|
}
|
|
|
|
canPreviewNode(node: NodeEntry) {
|
|
const rules = this.viewerRules;
|
|
|
|
if (this.isRuleDefined(rules.canPreview)) {
|
|
const canPreview = this.evaluateRule(rules.canPreview, node);
|
|
|
|
if (!canPreview) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
canShowViewerNavigation(node: NodeEntry) {
|
|
const rules = this.viewerRules;
|
|
|
|
if (this.isRuleDefined(rules.showNavigation)) {
|
|
const showNavigation = this.evaluateRule(rules.showNavigation, node);
|
|
if (!showNavigation) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|