mirror of
https://github.com/Alfresco/alfresco-content-app.git
synced 2025-05-19 17:14:45 +00:00
[ACA-1591] Load extensions from multiple files (#521)
* rework extension service, separate file with config * improve loading, optional entries * simplify config and unify content actions * load and merge multiple files * improve plugin loading, introduce second demo * move demo stuff to a plugin * rework navbar to make it pluggable * code and naming convention cleanup * extension schema * switch off custom navbar group by default * hotfix for facetQueries issue * consolidate files, final renames
This commit is contained in:
parent
43a71aa1c8
commit
8c9ffc1160
300
extension.schema.json
Normal file
300
extension.schema.json
Normal file
@ -0,0 +1,300 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
|
"$id": "https://github.com/Alfresco/alfresco-content-app/blob/development/extension.schema.json",
|
||||||
|
"title": "ACA Extension Schema",
|
||||||
|
"description": "Provides a validation schema for ACA extensions",
|
||||||
|
|
||||||
|
"definitions": {
|
||||||
|
"ruleRef": {
|
||||||
|
"type": "object",
|
||||||
|
"required": ["id", "type"],
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"description": "Unique rule definition id",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"description": "Rule evaluator type",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"parameters": {
|
||||||
|
"description": "Rule evaluator parameters",
|
||||||
|
"type": "array",
|
||||||
|
"items": { "$ref": "#/definitions/ruleParameter" },
|
||||||
|
"minItems": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ruleParameter": {
|
||||||
|
"type": "object",
|
||||||
|
"required": ["type", "value"],
|
||||||
|
"properties": {
|
||||||
|
"type": {
|
||||||
|
"description": "Rule parameter type",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"value": {
|
||||||
|
"description": "Rule parameter value",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"routeRef": {
|
||||||
|
"type": "object",
|
||||||
|
"required": ["id", "path", "component"],
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"description": "Unique route reference identifier.",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"path": {
|
||||||
|
"description": "Route path to register.",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"component": {
|
||||||
|
"description": "Unique identifier for the Component to use with the route.",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"layout": {
|
||||||
|
"description": "Unique identifier for the custom layout component to use.",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"auth": {
|
||||||
|
"description": "List of the authentication guards to use with the route.",
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"minLength": 1,
|
||||||
|
"uniqueItems": true
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"description": "Custom data to pass to the activated route so that your components can access it",
|
||||||
|
"type": "object"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"actionRef": {
|
||||||
|
"type": "object",
|
||||||
|
"required": ["id", "type"],
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"description": "Unique action identifier",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"description": "Action type",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"payload": {
|
||||||
|
"description": "Action payload value (string or expression)",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"contentActionRef": {
|
||||||
|
"type": "object",
|
||||||
|
"required": ["id", "type"],
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"description": "Unique action identifier.",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"description": "Element type",
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["default", "button", "separator", "menu"]
|
||||||
|
},
|
||||||
|
"title": {
|
||||||
|
"description": "Element title",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"order": {
|
||||||
|
"description": "Element order",
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"icon": {
|
||||||
|
"description": "Element icon",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"disabled": {
|
||||||
|
"description": "Toggles disabled state",
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"children": {
|
||||||
|
"description": "Child entries for the container types.",
|
||||||
|
"type": "array",
|
||||||
|
"items": { "$ref": "#/definitions/contentActionRef" },
|
||||||
|
"minItems": 1
|
||||||
|
},
|
||||||
|
"actions": {
|
||||||
|
"description": "Element actions",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"click": {
|
||||||
|
"description": "Action reference for the click handler",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"rules": {
|
||||||
|
"description": "Element rules",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"enabled": {
|
||||||
|
"description": "Rule to evaluate the enabled state",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"visible": {
|
||||||
|
"description": "Rule to evaluate the visibility state",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"navBarLinkRef": {
|
||||||
|
"type": "object",
|
||||||
|
"required": ["id", "icon", "title", "route"],
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"description": "Unique identifier",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"icon": {
|
||||||
|
"description": "Element icon",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"title": {
|
||||||
|
"description": "Element title",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"route": {
|
||||||
|
"description": "Route reference identifier",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"description": "Element description or tooltip",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"order": {
|
||||||
|
"description": "Element order",
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"disabled": {
|
||||||
|
"description": "Toggles the disabled state",
|
||||||
|
"type": "boolean"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"navBarGroupRef": {
|
||||||
|
"type": "object",
|
||||||
|
"required": ["id", "items"],
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"description": "Unique identifier for the navigation group",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"items": {
|
||||||
|
"description": "Navigation group items",
|
||||||
|
"type": "array",
|
||||||
|
"items": { "$ref": "#/definitions/navBarLinkRef" },
|
||||||
|
"minItems": 1
|
||||||
|
},
|
||||||
|
"order": {
|
||||||
|
"description": "Group order",
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"disabled": {
|
||||||
|
"description": "Toggles the disabled state",
|
||||||
|
"type": "boolean"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
"type": "object",
|
||||||
|
"required": ["name", "version"],
|
||||||
|
"properties": {
|
||||||
|
"name": {
|
||||||
|
"description": "Extension name",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"version": {
|
||||||
|
"description": "Extension version",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"description": "Brief description on what the extension does"
|
||||||
|
},
|
||||||
|
"references": {
|
||||||
|
"description": "References to external files",
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"minItems": 1,
|
||||||
|
"uniqueItems": true
|
||||||
|
},
|
||||||
|
"rules": {
|
||||||
|
"description": "List of rule definitions",
|
||||||
|
"type": "array",
|
||||||
|
"items": { "$ref": "#/definitions/ruleRef" },
|
||||||
|
"minItems": 1
|
||||||
|
},
|
||||||
|
"routes": {
|
||||||
|
"description": "List of custom application routes",
|
||||||
|
"type": "array",
|
||||||
|
"items": { "$ref": "#/definitions/routeRef" },
|
||||||
|
"minItems": 1
|
||||||
|
},
|
||||||
|
"actions": {
|
||||||
|
"description": "List of action definitions",
|
||||||
|
"type": "array",
|
||||||
|
"items": { "$ref": "#/definitions/actionRef" },
|
||||||
|
"minItems": 1
|
||||||
|
},
|
||||||
|
"features": {
|
||||||
|
"description": "Application-specific features and extensions",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"create": {
|
||||||
|
"description": "The [New] menu component extensions",
|
||||||
|
"type": "array",
|
||||||
|
"items": { "$ref": "#/definitions/contentActionRef" },
|
||||||
|
"minItems": 1
|
||||||
|
},
|
||||||
|
"viewer": {
|
||||||
|
"description": "Viewer component extensions",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"openWith": {
|
||||||
|
"description": "The [Open With] menu extensions",
|
||||||
|
"type": "array",
|
||||||
|
"items": { "$ref": "#/definitions/contentActionRef" },
|
||||||
|
"minItems": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"navbar": {
|
||||||
|
"description": "Navigation bar extensions",
|
||||||
|
"type": "array",
|
||||||
|
"items": { "$ref": "#/definitions/navBarGroupRef" },
|
||||||
|
"minItems": 1
|
||||||
|
},
|
||||||
|
"content": {
|
||||||
|
"description": "Main application content extensions",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"actions": {
|
||||||
|
"description": "Content actions (toolbar, context menus, etc.)",
|
||||||
|
"type": "array",
|
||||||
|
"items": { "$ref": "#/definitions/contentActionRef" },
|
||||||
|
"minItems": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -36,275 +36,6 @@
|
|||||||
"preserveState": true,
|
"preserveState": true,
|
||||||
"expandedSidenav": true
|
"expandedSidenav": true
|
||||||
},
|
},
|
||||||
"extensions": {
|
|
||||||
"external": [
|
|
||||||
"plugin1.json",
|
|
||||||
"plugin2.json"
|
|
||||||
],
|
|
||||||
"core": {
|
|
||||||
"rules": [
|
|
||||||
{
|
|
||||||
"id": "app.create.canCreateFolder",
|
|
||||||
"type": "app.navigation.folder.canCreate"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "app.toolbar.canEditFolder",
|
|
||||||
"type": "core.every",
|
|
||||||
"parameters": [
|
|
||||||
{ "type": "rule", "value": "app.selection.folder" },
|
|
||||||
{ "type": "rule", "value": "app.selection.folder.canUpdate" }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "app.toolbar.canViewFile",
|
|
||||||
"type": "app.selection.file"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "app.toolbar.canDownload",
|
|
||||||
"type": "app.selection.canDownload"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"routes": [
|
|
||||||
{
|
|
||||||
"id": "aca:routes/about",
|
|
||||||
"path": "ext/about",
|
|
||||||
"component": "aca:components/about",
|
|
||||||
"layout": "aca:layouts/main",
|
|
||||||
"auth":[ "aca:auth" ],
|
|
||||||
"data": {
|
|
||||||
"title": "Custom About"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"actions": [
|
|
||||||
{
|
|
||||||
"id": "aca:actions/create-folder",
|
|
||||||
"type": "CREATE_FOLDER",
|
|
||||||
"payload": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "aca:actions/edit-folder",
|
|
||||||
"type": "EDIT_FOLDER",
|
|
||||||
"payload": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "aca:actions/download",
|
|
||||||
"type": "DOWNLOAD_NODES",
|
|
||||||
"payload": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "aca:actions/preview",
|
|
||||||
"type": "VIEW_FILE",
|
|
||||||
"payload": null
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
"id": "aca:actions/info",
|
|
||||||
"type": "SNACKBAR_INFO",
|
|
||||||
"payload": "I'm a nice little popup raised by extension."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "aca:actions/node-name",
|
|
||||||
"type": "SNACKBAR_INFO",
|
|
||||||
"payload": "$('Action for ' + context.selection.first.entry.name)"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "aca:actions/settings",
|
|
||||||
"type": "NAVIGATE_URL",
|
|
||||||
"payload": "/settings"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"features": {
|
|
||||||
"create": [
|
|
||||||
{
|
|
||||||
"id": "app.create.folder",
|
|
||||||
"icon": "create_new_folder",
|
|
||||||
"title": "ext: Create Folder",
|
|
||||||
"actions": {
|
|
||||||
"click": "aca:actions/create-folder"
|
|
||||||
},
|
|
||||||
"rules": {
|
|
||||||
"enabled": "app.create.canCreateFolder"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"navigation": {
|
|
||||||
"aca:main": [
|
|
||||||
{
|
|
||||||
"id": "aca/personal-files",
|
|
||||||
"order": 100,
|
|
||||||
"icon": "folder",
|
|
||||||
"title": "APP.BROWSE.PERSONAL.SIDENAV_LINK.LABEL",
|
|
||||||
"description": "APP.BROWSE.PERSONAL.SIDENAV_LINK.TOOLTIP",
|
|
||||||
"route": "personal-files"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "aca/libraries",
|
|
||||||
"order": 101,
|
|
||||||
"icon": "group_work",
|
|
||||||
"title": "APP.BROWSE.LIBRARIES.SIDENAV_LINK.LABEL",
|
|
||||||
"description": "APP.BROWSE.LIBRARIES.SIDENAV_LINK.TOOLTIP",
|
|
||||||
"route": "libraries"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"aca:secondary": [
|
|
||||||
{
|
|
||||||
"id": "aca/shared",
|
|
||||||
"order": 100,
|
|
||||||
"icon": "people",
|
|
||||||
"title": "APP.BROWSE.SHARED.SIDENAV_LINK.LABEL",
|
|
||||||
"description": "APP.BROWSE.SHARED.SIDENAV_LINK.TOOLTIP",
|
|
||||||
"route": "shared"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "aca/recent-files",
|
|
||||||
"order": 101,
|
|
||||||
"icon": "schedule",
|
|
||||||
"title": "APP.BROWSE.RECENT.SIDENAV_LINK.LABEL",
|
|
||||||
"description": "APP.BROWSE.RECENT.SIDENAV_LINK.TOOLTIP",
|
|
||||||
"route": "recent-files"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "aca/favorites",
|
|
||||||
"order": 102,
|
|
||||||
"icon": "star",
|
|
||||||
"title": "APP.BROWSE.FAVORITES.SIDENAV_LINK.LABEL",
|
|
||||||
"description": "APP.BROWSE.FAVORITES.SIDENAV_LINK.TOOLTIP",
|
|
||||||
"route": "favorites"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "aca/trashcan",
|
|
||||||
"order": 103,
|
|
||||||
"icon": "delete",
|
|
||||||
"title": "APP.BROWSE.TRASHCAN.SIDENAV_LINK.LABEL",
|
|
||||||
"description": "APP.BROWSE.TRASHCAN.SIDENAV_LINK.TOOLTIP",
|
|
||||||
"route": "trashcan"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"aca:demo": [
|
|
||||||
{
|
|
||||||
"disabled": true,
|
|
||||||
"id": "aca:demo/link1",
|
|
||||||
"order": 100,
|
|
||||||
"icon": "build",
|
|
||||||
"title": "About (native)",
|
|
||||||
"description": "Uses native application route",
|
|
||||||
"route": "about"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"disabled": true,
|
|
||||||
"id": "aca:demo/link2",
|
|
||||||
"order": 100,
|
|
||||||
"icon": "build",
|
|
||||||
"title": "About (custom)",
|
|
||||||
"description": "Uses custom defined route",
|
|
||||||
"route": "aca:routes/about"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"viewer": {
|
|
||||||
"open-with": [
|
|
||||||
{
|
|
||||||
"disabled": false,
|
|
||||||
"id": "aca:viewer/action1",
|
|
||||||
"order": 100,
|
|
||||||
"icon": "build",
|
|
||||||
"title": "Snackbar",
|
|
||||||
"action": "aca:actions/info"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"content": {
|
|
||||||
"actions": [
|
|
||||||
{
|
|
||||||
"id": "aca:toolbar/separator-1",
|
|
||||||
"order": 5,
|
|
||||||
"type": "separator"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "aca:toolbar/create-folder",
|
|
||||||
"type": "button",
|
|
||||||
"order": 10,
|
|
||||||
"title": "APP.NEW_MENU.TOOLTIPS.CREATE_FOLDER",
|
|
||||||
"icon": "create_new_folder",
|
|
||||||
"actions": {
|
|
||||||
"click": "aca:actions/create-folder"
|
|
||||||
},
|
|
||||||
"rules": {
|
|
||||||
"visible": "app.create.canCreateFolder"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "aca:toolbar/preview",
|
|
||||||
"type": "button",
|
|
||||||
"order": 15,
|
|
||||||
"title": "APP.ACTIONS.VIEW",
|
|
||||||
"icon": "open_in_browser",
|
|
||||||
"actions": {
|
|
||||||
"click": "aca:actions/preview"
|
|
||||||
},
|
|
||||||
"rules": {
|
|
||||||
"visible": "app.toolbar.canViewFile"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "aca:toolbar/download",
|
|
||||||
"type": "button",
|
|
||||||
"order": 20,
|
|
||||||
"title": "APP.ACTIONS.DOWNLOAD",
|
|
||||||
"icon": "get_app",
|
|
||||||
"actions": {
|
|
||||||
"click": "aca:actions/download"
|
|
||||||
},
|
|
||||||
"rules": {
|
|
||||||
"visible": "app.toolbar.canDownload"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "aca:toolbar/edit-folder",
|
|
||||||
"type": "button",
|
|
||||||
"order": 30,
|
|
||||||
"title": "APP.ACTIONS.EDIT",
|
|
||||||
"icon": "create",
|
|
||||||
"actions": {
|
|
||||||
"click": "aca:actions/edit-folder"
|
|
||||||
},
|
|
||||||
"rules": {
|
|
||||||
"visible": "app.toolbar.canEditFolder"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
"id": "aca:toolbar/separator-2",
|
|
||||||
"order": 200,
|
|
||||||
"type": "separator"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "aca:toolbar/menu-1",
|
|
||||||
"type": "menu",
|
|
||||||
"icon": "storage",
|
|
||||||
"order": 300,
|
|
||||||
"children": [
|
|
||||||
{
|
|
||||||
"id": "aca:action3",
|
|
||||||
"type": "button",
|
|
||||||
"title": "Settings",
|
|
||||||
"icon": "settings_applications",
|
|
||||||
"actions": {
|
|
||||||
"click": "aca:actions/settings"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "aca:toolbar/separator-3",
|
|
||||||
"type": "separator"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"languages": [
|
"languages": [
|
||||||
{
|
{
|
||||||
"key": "de",
|
"key": "de",
|
||||||
@ -460,6 +191,7 @@
|
|||||||
{ "field": "modifier", "mincount": 1, "label": "SEARCH.FACET_FIELDS.MODIFIER" },
|
{ "field": "modifier", "mincount": 1, "label": "SEARCH.FACET_FIELDS.MODIFIER" },
|
||||||
{ "field": "SITE", "mincount": 1, "label": "SEARCH.FACET_FIELDS.FILE_LIBRARY" }
|
{ "field": "SITE", "mincount": 1, "label": "SEARCH.FACET_FIELDS.FILE_LIBRARY" }
|
||||||
],
|
],
|
||||||
|
"facetQueries": {},
|
||||||
"categories": [
|
"categories": [
|
||||||
{
|
{
|
||||||
"id": "modifiedDate",
|
"id": "modifiedDate",
|
||||||
|
@ -91,8 +91,6 @@ export class AppComponent implements OnInit {
|
|||||||
pageTitle.setTitle(data.title || '');
|
pageTitle.setTitle(data.title || '');
|
||||||
});
|
});
|
||||||
|
|
||||||
this.extensions.init();
|
|
||||||
|
|
||||||
this.router.config.unshift(...this.extensions.getApplicationRoutes());
|
this.router.config.unshift(...this.extensions.getApplicationRoutes());
|
||||||
|
|
||||||
this.uploadService.fileUploadError.subscribe(error =>
|
this.uploadService.fileUploadError.subscribe(error =>
|
||||||
|
@ -24,7 +24,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { BrowserModule } from '@angular/platform-browser';
|
import { BrowserModule } from '@angular/platform-browser';
|
||||||
import { NgModule } from '@angular/core';
|
import { NgModule, APP_INITIALIZER } from '@angular/core';
|
||||||
import { RouterModule, RouteReuseStrategy } from '@angular/router';
|
import { RouterModule, RouteReuseStrategy } from '@angular/router';
|
||||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||||
@ -84,7 +84,11 @@ import { NodePermissionsDialogComponent } from './dialogs/node-permissions/node-
|
|||||||
import { NodePermissionsDirective } from './directives/node-permissions.directive';
|
import { NodePermissionsDirective } from './directives/node-permissions.directive';
|
||||||
import { PermissionsManagerComponent } from './components/permission-manager/permissions-manager.component';
|
import { PermissionsManagerComponent } from './components/permission-manager/permissions-manager.component';
|
||||||
import { AppRouteReuseStrategy } from './app.routes.strategy';
|
import { AppRouteReuseStrategy } from './app.routes.strategy';
|
||||||
|
import { ExtensionService } from './extensions/extension.service';
|
||||||
|
|
||||||
|
export function setupExtensionServiceFactory(service: ExtensionService): Function {
|
||||||
|
return () => service.load();
|
||||||
|
}
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
BrowserModule,
|
BrowserModule,
|
||||||
@ -96,7 +100,7 @@ import { AppRouteReuseStrategy } from './app.routes.strategy';
|
|||||||
enableTracing: false // enable for debug only
|
enableTracing: false // enable for debug only
|
||||||
}),
|
}),
|
||||||
MaterialModule,
|
MaterialModule,
|
||||||
CoreModule,
|
CoreModule.forRoot(),
|
||||||
ContentModule,
|
ContentModule,
|
||||||
AppStoreModule,
|
AppStoreModule,
|
||||||
CoreExtensionsModule,
|
CoreExtensionsModule,
|
||||||
@ -159,7 +163,13 @@ import { AppRouteReuseStrategy } from './app.routes.strategy';
|
|||||||
NodePermissionService,
|
NodePermissionService,
|
||||||
ProfileResolver,
|
ProfileResolver,
|
||||||
ExperimentalGuard,
|
ExperimentalGuard,
|
||||||
ContentApiService
|
ContentApiService,
|
||||||
|
{
|
||||||
|
provide: APP_INITIALIZER,
|
||||||
|
useFactory: setupExtensionServiceFactory,
|
||||||
|
deps: [ExtensionService],
|
||||||
|
multi: true
|
||||||
|
}
|
||||||
],
|
],
|
||||||
entryComponents: [
|
entryComponents: [
|
||||||
LibraryDialogComponent,
|
LibraryDialogComponent,
|
||||||
|
@ -36,8 +36,8 @@ 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';
|
||||||
import { ExtensionService } from '../extensions/extension.service';
|
import { ExtensionService } from '../extensions/extension.service';
|
||||||
import { ContentActionExtension } from '../extensions/content-action.extension';
|
|
||||||
import { ContentManagementService } from '../services/content-management.service';
|
import { ContentManagementService } from '../services/content-management.service';
|
||||||
|
import { ContentActionRef } from '../extensions/action.extensions';
|
||||||
|
|
||||||
export abstract class PageComponent implements OnInit, OnDestroy {
|
export abstract class PageComponent implements OnInit, OnDestroy {
|
||||||
|
|
||||||
@ -52,7 +52,7 @@ export abstract class PageComponent implements OnInit, OnDestroy {
|
|||||||
selection: SelectionState;
|
selection: SelectionState;
|
||||||
displayMode = DisplayMode.List;
|
displayMode = DisplayMode.List;
|
||||||
sharedPreviewUrl$: Observable<string>;
|
sharedPreviewUrl$: Observable<string>;
|
||||||
actions: Array<ContentActionExtension> = [];
|
actions: Array<ContentActionRef> = [];
|
||||||
canUpdateFile = false;
|
canUpdateFile = false;
|
||||||
canUpdateNode = false;
|
canUpdateNode = false;
|
||||||
canDelete = false;
|
canDelete = false;
|
||||||
|
@ -18,7 +18,7 @@
|
|||||||
<adf-viewer-open-with *ifExperimental="'extensions'">
|
<adf-viewer-open-with *ifExperimental="'extensions'">
|
||||||
<button *ngFor="let entry of openWith"
|
<button *ngFor="let entry of openWith"
|
||||||
mat-menu-item
|
mat-menu-item
|
||||||
(click)="runAction(entry.action)">
|
(click)="runAction(entry.actions.click)">
|
||||||
<mat-icon>{{ entry.icon }}</mat-icon>
|
<mat-icon>{{ entry.icon }}</mat-icon>
|
||||||
<span>{{ entry.title }}</span>
|
<span>{{ entry.title }}</span>
|
||||||
</button>
|
</button>
|
||||||
|
@ -32,8 +32,8 @@ import { DeleteNodesAction, 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';
|
||||||
import { OpenWithExtension } from '../../extensions/open-with.extension';
|
|
||||||
import { ContentManagementService } from '../../services/content-management.service';
|
import { ContentManagementService } from '../../services/content-management.service';
|
||||||
|
import { ContentActionRef } from '../../extensions/action.extensions';
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-preview',
|
selector: 'app-preview',
|
||||||
templateUrl: 'preview.component.html',
|
templateUrl: 'preview.component.html',
|
||||||
@ -52,7 +52,7 @@ export class PreviewComponent extends PageComponent implements OnInit {
|
|||||||
previousNodeId: string;
|
previousNodeId: string;
|
||||||
nextNodeId: string;
|
nextNodeId: string;
|
||||||
navigateMultiple = false;
|
navigateMultiple = false;
|
||||||
openWith: Array<OpenWithExtension> = [];
|
openWith: Array<ContentActionRef> = [];
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private contentApi: ContentApiService,
|
private contentApi: ContentApiService,
|
||||||
|
@ -59,13 +59,13 @@
|
|||||||
|
|
||||||
<div class="sidenav__section sidenav__section--menu" *ngFor="let group of groups">
|
<div class="sidenav__section sidenav__section--menu" *ngFor="let group of groups">
|
||||||
<ul class="sidenav-menu">
|
<ul class="sidenav-menu">
|
||||||
<li *ngFor="let item of group" class="sidenav-menu__item"
|
<li *ngFor="let item of group.items" class="sidenav-menu__item"
|
||||||
routerLinkActive
|
routerLinkActive
|
||||||
#rla="routerLinkActive"
|
#rla="routerLinkActive"
|
||||||
title="{{ item.description | translate }}">
|
title="{{ item.description | translate }}">
|
||||||
|
|
||||||
<button
|
<button
|
||||||
[routerLink]="item.route"
|
[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-icon-button
|
||||||
@ -78,7 +78,7 @@
|
|||||||
</button>
|
</button>
|
||||||
|
|
||||||
<span #rippleTrigger
|
<span #rippleTrigger
|
||||||
[routerLink]="item.route"
|
[routerLink]="item.url"
|
||||||
class="menu__item--label"
|
class="menu__item--label"
|
||||||
[hidden]="!showLabel"
|
[hidden]="!showLabel"
|
||||||
[ngClass]="{
|
[ngClass]="{
|
||||||
|
@ -28,13 +28,13 @@ import { Component, Input, OnInit, OnDestroy } from '@angular/core';
|
|||||||
import { Node } from 'alfresco-js-api';
|
import { Node } from 'alfresco-js-api';
|
||||||
import { NodePermissionService } from '../../services/node-permission.service';
|
import { NodePermissionService } from '../../services/node-permission.service';
|
||||||
import { ExtensionService } from '../../extensions/extension.service';
|
import { ExtensionService } from '../../extensions/extension.service';
|
||||||
import { NavigationExtension } from '../../extensions/navigation.extension';
|
|
||||||
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 { 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 { ContentActionExtension } from '../../extensions/content-action.extension';
|
import { NavBarGroupRef } from '../../extensions/navbar.extensions';
|
||||||
|
import { ContentActionRef } from '../../extensions/action.extensions';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-sidenav',
|
selector: 'app-sidenav',
|
||||||
@ -45,8 +45,8 @@ export class SidenavComponent implements OnInit, OnDestroy {
|
|||||||
@Input() showLabel: boolean;
|
@Input() showLabel: boolean;
|
||||||
|
|
||||||
node: Node = null;
|
node: Node = null;
|
||||||
groups: Array<NavigationExtension[]> = [];
|
groups: Array<NavBarGroupRef> = [];
|
||||||
createActions: Array<ContentActionExtension> = [];
|
createActions: Array<ContentActionRef> = [];
|
||||||
canCreateContent = false;
|
canCreateContent = false;
|
||||||
onDestroy$: Subject<boolean> = new Subject<boolean>();
|
onDestroy$: Subject<boolean> = new Subject<boolean>();
|
||||||
|
|
||||||
@ -64,7 +64,7 @@ export class SidenavComponent implements OnInit, OnDestroy {
|
|||||||
.pipe(takeUntil(this.onDestroy$))
|
.pipe(takeUntil(this.onDestroy$))
|
||||||
.subscribe(node => {
|
.subscribe(node => {
|
||||||
this.node = node;
|
this.node = node;
|
||||||
this.createActions = this.extensions.getFolderCreateActions(node);
|
this.createActions = this.extensions.getCreateActions();
|
||||||
this.canCreateContent = node && this.permission.check(node, ['create']);
|
this.canCreateContent = node && this.permission.check(node, ['create']);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -30,21 +30,28 @@ export enum ContentActionType {
|
|||||||
menu = 'menu'
|
menu = 'menu'
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ContentActionExtension {
|
export interface ContentActionRef {
|
||||||
id: string;
|
id: string;
|
||||||
type: ContentActionType;
|
type: ContentActionType;
|
||||||
|
|
||||||
|
title?: string;
|
||||||
order?: number;
|
order?: number;
|
||||||
title: string;
|
|
||||||
icon?: string;
|
icon?: string;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
children?: Array<ContentActionExtension>;
|
children?: Array<ContentActionRef>;
|
||||||
actions?: {
|
actions?: {
|
||||||
click?: string;
|
click?: string;
|
||||||
[key: string]: string;
|
[key: string]: string;
|
||||||
};
|
};
|
||||||
rules: {
|
rules?: {
|
||||||
enabled?: string;
|
enabled?: string;
|
||||||
visible?: string;
|
visible?: string;
|
||||||
[key: string]: string;
|
[key: string]: string;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ActionRef {
|
||||||
|
id: string;
|
||||||
|
type: string;
|
||||||
|
payload?: string;
|
||||||
|
}
|
@ -1,30 +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/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
export interface ActionRef {
|
|
||||||
id: string;
|
|
||||||
type: string;
|
|
||||||
payload?: string;
|
|
||||||
}
|
|
@ -1,141 +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 { AppConfigService } from '@alfresco/adf-core';
|
|
||||||
import { ActionService } from './action.service';
|
|
||||||
import { Store } from '@ngrx/store';
|
|
||||||
import { AppStore } from '../../store/states';
|
|
||||||
import { TestBed } from '@angular/core/testing';
|
|
||||||
import { AppTestingModule } from '../../testing/app-testing.module';
|
|
||||||
|
|
||||||
describe('ActionService', () => {
|
|
||||||
let config: AppConfigService;
|
|
||||||
let actions: ActionService;
|
|
||||||
let store: Store<AppStore>;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
TestBed.configureTestingModule({
|
|
||||||
imports: [AppTestingModule]
|
|
||||||
});
|
|
||||||
|
|
||||||
actions = TestBed.get(ActionService);
|
|
||||||
store = TestBed.get(Store);
|
|
||||||
|
|
||||||
config = TestBed.get(AppConfigService);
|
|
||||||
config.config['extensions'] = {};
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('actions', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
config.config.extensions = {
|
|
||||||
core: {
|
|
||||||
actions: [
|
|
||||||
{
|
|
||||||
id: 'aca:actions/create-folder',
|
|
||||||
type: 'CREATE_FOLDER',
|
|
||||||
payload: 'folder-name'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should load actions from the config', () => {
|
|
||||||
actions.init();
|
|
||||||
expect(actions.actions.length).toBe(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should have an empty action list if config provides nothing', () => {
|
|
||||||
config.config.extensions = {};
|
|
||||||
actions.init();
|
|
||||||
|
|
||||||
expect(actions.actions).toEqual([]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should find action by id', () => {
|
|
||||||
actions.init();
|
|
||||||
|
|
||||||
const action = actions.getActionById(
|
|
||||||
'aca:actions/create-folder'
|
|
||||||
);
|
|
||||||
expect(action).toBeTruthy();
|
|
||||||
expect(action.type).toBe('CREATE_FOLDER');
|
|
||||||
expect(action.payload).toBe('folder-name');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not find action by id', () => {
|
|
||||||
actions.init();
|
|
||||||
|
|
||||||
const action = actions.getActionById('missing');
|
|
||||||
expect(action).toBeFalsy();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should run the action via store', () => {
|
|
||||||
actions.init();
|
|
||||||
spyOn(store, 'dispatch').and.stub();
|
|
||||||
|
|
||||||
actions.runActionById('aca:actions/create-folder');
|
|
||||||
expect(store.dispatch).toHaveBeenCalledWith({
|
|
||||||
type: 'CREATE_FOLDER',
|
|
||||||
payload: 'folder-name'
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not use store if action is missing', () => {
|
|
||||||
actions.init();
|
|
||||||
spyOn(store, 'dispatch').and.stub();
|
|
||||||
|
|
||||||
actions.runActionById('missing');
|
|
||||||
expect(store.dispatch).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('expressions', () => {
|
|
||||||
it('should eval static value', () => {
|
|
||||||
const value = actions.runExpression('hello world');
|
|
||||||
expect(value).toBe('hello world');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should eval string as an expression', () => {
|
|
||||||
const value = actions.runExpression('$( "hello world" )');
|
|
||||||
expect(value).toBe('hello world');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should eval expression with no context', () => {
|
|
||||||
const value = actions.runExpression('$( 1 + 1 )');
|
|
||||||
expect(value).toBe(2);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should eval expression with context', () => {
|
|
||||||
const context = {
|
|
||||||
a: 'hey',
|
|
||||||
b: 'there'
|
|
||||||
};
|
|
||||||
const expression = '$( context.a + " " + context.b + "!" )';
|
|
||||||
const value = actions.runExpression(expression, context);
|
|
||||||
expect(value).toBe('hey there!');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,76 +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 { Injectable } from '@angular/core';
|
|
||||||
import { AppConfigService } from '@alfresco/adf-core';
|
|
||||||
import { Store } from '@ngrx/store';
|
|
||||||
import { AppStore } from '../../store/states';
|
|
||||||
import { ActionRef } from './action-ref';
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class ActionService {
|
|
||||||
actions: Array<ActionRef> = [];
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
private config: AppConfigService,
|
|
||||||
private store: Store<AppStore>
|
|
||||||
) {}
|
|
||||||
|
|
||||||
init() {
|
|
||||||
this.actions = this.config.get<Array<ActionRef>>(
|
|
||||||
'extensions.core.actions',
|
|
||||||
[]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
getActionById(id: string): ActionRef {
|
|
||||||
return this.actions.find(action => action.id === id);
|
|
||||||
}
|
|
||||||
|
|
||||||
runActionById(id: string, context?: any) {
|
|
||||||
const action = this.getActionById(id);
|
|
||||||
if (action) {
|
|
||||||
const { type, payload } = action;
|
|
||||||
const expression = this.runExpression(payload, context);
|
|
||||||
|
|
||||||
this.store.dispatch({ type, payload: expression });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
runExpression(value: string, context?: any) {
|
|
||||||
const pattern = new RegExp(/\$\((.*\)?)\)/g);
|
|
||||||
const matches = pattern.exec(value);
|
|
||||||
|
|
||||||
if (matches && matches.length > 1) {
|
|
||||||
const expression = matches[1];
|
|
||||||
const fn = new Function('context', `return ${expression}`);
|
|
||||||
const result = fn(context);
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
}
|
|
@ -31,13 +31,13 @@ import {
|
|||||||
OnInit,
|
OnInit,
|
||||||
OnDestroy
|
OnDestroy
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
import { ContentActionExtension } from '../../content-action.extension';
|
|
||||||
import { AppStore, SelectionState } from '../../../store/states';
|
import { AppStore, SelectionState } 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 { appSelection } from '../../../store/selectors/app.selectors';
|
||||||
import { Subject } from 'rxjs/Rx';
|
import { Subject } from 'rxjs/Rx';
|
||||||
import { takeUntil } from 'rxjs/operators';
|
import { takeUntil } from 'rxjs/operators';
|
||||||
|
import { ContentActionRef } from '../../action.extensions';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'aca-toolbar-action',
|
selector: 'aca-toolbar-action',
|
||||||
@ -47,7 +47,7 @@ import { takeUntil } from 'rxjs/operators';
|
|||||||
host: { class: 'aca-toolbar-action' }
|
host: { class: 'aca-toolbar-action' }
|
||||||
})
|
})
|
||||||
export class ToolbarActionComponent implements OnInit, OnDestroy {
|
export class ToolbarActionComponent implements OnInit, OnDestroy {
|
||||||
@Input() entry: ContentActionExtension;
|
@Input() entry: ContentActionRef;
|
||||||
|
|
||||||
selection: SelectionState;
|
selection: SelectionState;
|
||||||
onDestroy$: Subject<boolean> = new Subject<boolean>();
|
onDestroy$: Subject<boolean> = new Subject<boolean>();
|
||||||
|
@ -30,21 +30,38 @@ import { AboutComponent } from '../components/about/about.component';
|
|||||||
import { LayoutComponent } from '../components/layout/layout.component';
|
import { LayoutComponent } from '../components/layout/layout.component';
|
||||||
import { ToolbarActionComponent } from './components/toolbar-action/toolbar-action.component';
|
import { ToolbarActionComponent } from './components/toolbar-action/toolbar-action.component';
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
import { RuleService } from './rules/rule.service';
|
import { every, some } from './evaluators/core.evaluators';
|
||||||
import { ActionService } from './actions/action.service';
|
import {
|
||||||
|
canCreateFolder,
|
||||||
|
hasFolderSelected,
|
||||||
|
canUpdateSelectedFolder,
|
||||||
|
hasFileSelected,
|
||||||
|
canDownloadSelection
|
||||||
|
} from './evaluators/app.evaluators';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [CommonModule, CoreModule.forChild()],
|
imports: [CommonModule, CoreModule.forChild()],
|
||||||
declarations: [ToolbarActionComponent],
|
declarations: [ToolbarActionComponent],
|
||||||
exports: [ToolbarActionComponent],
|
exports: [ToolbarActionComponent],
|
||||||
entryComponents: [AboutComponent],
|
entryComponents: [AboutComponent],
|
||||||
providers: [ExtensionService, RuleService, ActionService]
|
providers: [ExtensionService]
|
||||||
})
|
})
|
||||||
export class CoreExtensionsModule {
|
export class CoreExtensionsModule {
|
||||||
constructor(extensions: ExtensionService) {
|
constructor(extensions: ExtensionService) {
|
||||||
extensions
|
extensions
|
||||||
.setComponent('aca:layouts/main', LayoutComponent)
|
.setComponent('app.layout.main', LayoutComponent)
|
||||||
.setComponent('aca:components/about', AboutComponent)
|
.setComponent('app.components.about', AboutComponent)
|
||||||
.setAuthGuard('aca:auth', AuthGuardEcm);
|
.setAuthGuard('app.auth', AuthGuardEcm)
|
||||||
|
|
||||||
|
.setEvaluator('core.every', every)
|
||||||
|
.setEvaluator('core.some', some)
|
||||||
|
.setEvaluator('app.selection.canDownload', canDownloadSelection)
|
||||||
|
.setEvaluator('app.selection.file', hasFileSelected)
|
||||||
|
.setEvaluator('app.selection.folder', hasFolderSelected)
|
||||||
|
.setEvaluator(
|
||||||
|
'app.selection.folder.canUpdate',
|
||||||
|
canUpdateSelectedFolder
|
||||||
|
)
|
||||||
|
.setEvaluator('app.navigation.folder.canCreate', canCreateFolder);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,9 +23,8 @@
|
|||||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { RuleContext } from './rule-context';
|
|
||||||
import { RuleParameter } from './rule-parameter';
|
|
||||||
import { Node } from 'alfresco-js-api';
|
import { Node } from 'alfresco-js-api';
|
||||||
|
import { RuleContext, RuleParameter } from '../rule.extensions';
|
||||||
|
|
||||||
export function canCreateFolder(context: RuleContext, ...args: RuleParameter[]): boolean {
|
export function canCreateFolder(context: RuleContext, ...args: RuleParameter[]): boolean {
|
||||||
const folder = context.navigation.currentFolder;
|
const folder = context.navigation.currentFolder;
|
@ -23,8 +23,7 @@
|
|||||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { RuleContext } from './rule-context';
|
import { RuleContext, RuleParameter } from '../rule.extensions';
|
||||||
import { RuleParameter } from './rule-parameter';
|
|
||||||
|
|
||||||
export function every(context: RuleContext, ...args: RuleParameter[]): boolean {
|
export function every(context: RuleContext, ...args: RuleParameter[]): boolean {
|
||||||
if (!args || args.length === 0) {
|
if (!args || args.length === 0) {
|
@ -23,13 +23,26 @@
|
|||||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { RuleRef } from './rules/rule-ref';
|
import { NavBarGroupRef } from './navbar.extensions';
|
||||||
import { ActionRef } from './actions/action-ref';
|
import { RouteRef } from './routing.extensions';
|
||||||
import { RouteRef } from './route-ref';
|
import { RuleRef } from './rule.extensions';
|
||||||
|
import { ActionRef, ContentActionRef } from './action.extensions';
|
||||||
|
|
||||||
export interface ExtensionConfig {
|
export interface ExtensionConfig {
|
||||||
|
version: string;
|
||||||
|
references?: Array<string>;
|
||||||
rules?: Array<RuleRef>;
|
rules?: Array<RuleRef>;
|
||||||
routes?: Array<RouteRef>;
|
routes?: Array<RouteRef>;
|
||||||
actions?: Array<ActionRef>;
|
actions?: Array<ActionRef>;
|
||||||
features?: { [key: string]: any };
|
features?: {
|
||||||
|
[key: string]: any;
|
||||||
|
create?: Array<ContentActionRef>;
|
||||||
|
viewer?: {
|
||||||
|
openWith?: Array<ContentActionRef>;
|
||||||
|
};
|
||||||
|
navbar?: Array<NavBarGroupRef>;
|
||||||
|
content?: {
|
||||||
|
actions?: Array<ContentActionRef>;
|
||||||
|
};
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
@ -26,22 +26,98 @@
|
|||||||
import { TestBed } from '@angular/core/testing';
|
import { TestBed } from '@angular/core/testing';
|
||||||
import { AppTestingModule } from '../testing/app-testing.module';
|
import { AppTestingModule } from '../testing/app-testing.module';
|
||||||
import { ExtensionService } from './extension.service';
|
import { ExtensionService } from './extension.service';
|
||||||
import { AppConfigService } from '@alfresco/adf-core';
|
import { Store } from '@ngrx/store';
|
||||||
import { ContentActionType } from './content-action.extension';
|
import { AppStore } from '../store/states';
|
||||||
|
import { ContentActionType } from './action.extensions';
|
||||||
|
|
||||||
describe('ExtensionService', () => {
|
describe('ExtensionService', () => {
|
||||||
let config: AppConfigService;
|
|
||||||
let extensions: ExtensionService;
|
let extensions: ExtensionService;
|
||||||
|
let store: Store<AppStore>;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
imports: [AppTestingModule]
|
imports: [AppTestingModule]
|
||||||
});
|
});
|
||||||
|
|
||||||
|
store = TestBed.get(Store);
|
||||||
extensions = TestBed.get(ExtensionService);
|
extensions = TestBed.get(ExtensionService);
|
||||||
|
});
|
||||||
|
|
||||||
config = TestBed.get(AppConfigService);
|
describe('actions', () => {
|
||||||
config.config['extensions'] = {};
|
beforeEach(() => {
|
||||||
|
extensions.setup({
|
||||||
|
version: '1.0.0',
|
||||||
|
actions: [
|
||||||
|
{
|
||||||
|
id: 'aca:actions/create-folder',
|
||||||
|
type: 'CREATE_FOLDER',
|
||||||
|
payload: 'folder-name'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should load actions from the config', () => {
|
||||||
|
expect(extensions.actions.length).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should find action by id', () => {
|
||||||
|
const action = extensions.getActionById(
|
||||||
|
'aca:actions/create-folder'
|
||||||
|
);
|
||||||
|
expect(action).toBeTruthy();
|
||||||
|
expect(action.type).toBe('CREATE_FOLDER');
|
||||||
|
expect(action.payload).toBe('folder-name');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not find action by id', () => {
|
||||||
|
const action = extensions.getActionById('missing');
|
||||||
|
expect(action).toBeFalsy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should run the action via store', () => {
|
||||||
|
spyOn(store, 'dispatch').and.stub();
|
||||||
|
|
||||||
|
extensions.runActionById('aca:actions/create-folder');
|
||||||
|
expect(store.dispatch).toHaveBeenCalledWith({
|
||||||
|
type: 'CREATE_FOLDER',
|
||||||
|
payload: 'folder-name'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not use store if action is missing', () => {
|
||||||
|
spyOn(store, 'dispatch').and.stub();
|
||||||
|
|
||||||
|
extensions.runActionById('missing');
|
||||||
|
expect(store.dispatch).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('expressions', () => {
|
||||||
|
it('should eval static value', () => {
|
||||||
|
const value = extensions.runExpression('hello world');
|
||||||
|
expect(value).toBe('hello world');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should eval string as an expression', () => {
|
||||||
|
const value = extensions.runExpression('$( "hello world" )');
|
||||||
|
expect(value).toBe('hello world');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should eval expression with no context', () => {
|
||||||
|
const value = extensions.runExpression('$( 1 + 1 )');
|
||||||
|
expect(value).toBe(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should eval expression with context', () => {
|
||||||
|
const context = {
|
||||||
|
a: 'hey',
|
||||||
|
b: 'there'
|
||||||
|
};
|
||||||
|
const expression = '$( context.a + " " + context.b + "!" )';
|
||||||
|
const value = extensions.runExpression(expression, context);
|
||||||
|
expect(value).toBe('hey there!');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('auth guards', () => {
|
describe('auth guards', () => {
|
||||||
@ -54,8 +130,6 @@ describe('ExtensionService', () => {
|
|||||||
|
|
||||||
extensions.authGuards['guard1'] = guard1;
|
extensions.authGuards['guard1'] = guard1;
|
||||||
extensions.authGuards['guard2'] = guard2;
|
extensions.authGuards['guard2'] = guard2;
|
||||||
|
|
||||||
extensions.init();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should fetch auth guards by ids', () => {
|
it('should fetch auth guards by ids', () => {
|
||||||
@ -86,7 +160,6 @@ describe('ExtensionService', () => {
|
|||||||
component1 = {};
|
component1 = {};
|
||||||
|
|
||||||
extensions.components['component-1'] = component1;
|
extensions.components['component-1'] = component1;
|
||||||
extensions.init();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should fetch registered component', () => {
|
it('should fetch registered component', () => {
|
||||||
@ -105,22 +178,21 @@ describe('ExtensionService', () => {
|
|||||||
let guard1;
|
let guard1;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
config.config.extensions = {
|
extensions.setup({
|
||||||
core: {
|
version: '1.0.0',
|
||||||
routes: [
|
routes: [
|
||||||
{
|
{
|
||||||
id: 'aca:routes/about',
|
id: 'aca:routes/about',
|
||||||
path: 'ext/about',
|
path: 'ext/about',
|
||||||
component: 'aca:components/about',
|
component: 'aca:components/about',
|
||||||
layout: 'aca:layouts/main',
|
layout: 'aca:layouts/main',
|
||||||
auth: ['aca:auth'],
|
auth: ['aca:auth'],
|
||||||
data: {
|
data: {
|
||||||
title: 'Custom About'
|
title: 'Custom About'
|
||||||
}
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
}
|
]
|
||||||
};
|
});
|
||||||
|
|
||||||
component1 = {};
|
component1 = {};
|
||||||
component2 = {};
|
component2 = {};
|
||||||
@ -129,8 +201,6 @@ describe('ExtensionService', () => {
|
|||||||
|
|
||||||
guard1 = {};
|
guard1 = {};
|
||||||
extensions.authGuards['aca:auth'] = guard1;
|
extensions.authGuards['aca:auth'] = guard1;
|
||||||
|
|
||||||
extensions.init();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should load routes from the config', () => {
|
it('should load routes from the config', () => {
|
||||||
@ -166,60 +236,54 @@ describe('ExtensionService', () => {
|
|||||||
|
|
||||||
describe('content actions', () => {
|
describe('content actions', () => {
|
||||||
it('should load content actions from the config', () => {
|
it('should load content actions from the config', () => {
|
||||||
config.config.extensions = {
|
extensions.setup({
|
||||||
core: {
|
version: '1.0.0',
|
||||||
features: {
|
features: {
|
||||||
content: {
|
content: {
|
||||||
actions: [
|
actions: [
|
||||||
{
|
{
|
||||||
id: 'aca:toolbar/separator-1',
|
id: 'aca:toolbar/separator-1',
|
||||||
order: 1,
|
order: 1,
|
||||||
type: 'separator'
|
type: ContentActionType.separator,
|
||||||
},
|
title: 'action1',
|
||||||
{
|
},
|
||||||
id: 'aca:toolbar/separator-2',
|
{
|
||||||
order: 2,
|
id: 'aca:toolbar/separator-2',
|
||||||
type: 'separator'
|
order: 2,
|
||||||
}
|
type: ContentActionType.separator,
|
||||||
]
|
title: 'action2'
|
||||||
}
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
});
|
||||||
|
|
||||||
extensions.init();
|
|
||||||
expect(extensions.contentActions.length).toBe(2);
|
expect(extensions.contentActions.length).toBe(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should have an empty content action list if config is empty', () => {
|
|
||||||
config.config.extensions = {};
|
|
||||||
extensions.init();
|
|
||||||
expect(extensions.contentActions).toEqual([]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should sort content actions by order', () => {
|
it('should sort content actions by order', () => {
|
||||||
config.config.extensions = {
|
extensions.setup({
|
||||||
core: {
|
version: '1.0.0',
|
||||||
features: {
|
features: {
|
||||||
content: {
|
content: {
|
||||||
actions: [
|
actions: [
|
||||||
{
|
{
|
||||||
id: 'aca:toolbar/separator-2',
|
id: 'aca:toolbar/separator-2',
|
||||||
order: 2,
|
order: 2,
|
||||||
type: 'separator'
|
type: ContentActionType.separator,
|
||||||
},
|
title: 'action2'
|
||||||
{
|
},
|
||||||
id: 'aca:toolbar/separator-1',
|
{
|
||||||
order: 1,
|
id: 'aca:toolbar/separator-1',
|
||||||
type: 'separator'
|
order: 1,
|
||||||
}
|
type: ContentActionType.separator,
|
||||||
]
|
title: 'action1'
|
||||||
}
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
});
|
||||||
|
|
||||||
extensions.init();
|
|
||||||
expect(extensions.contentActions.length).toBe(2);
|
expect(extensions.contentActions.length).toBe(2);
|
||||||
expect(extensions.contentActions[0].id).toBe(
|
expect(extensions.contentActions[0].id).toBe(
|
||||||
'aca:toolbar/separator-1'
|
'aca:toolbar/separator-1'
|
||||||
@ -232,94 +296,97 @@ describe('ExtensionService', () => {
|
|||||||
|
|
||||||
describe('open with', () => {
|
describe('open with', () => {
|
||||||
it('should load [open with] actions for the viewer', () => {
|
it('should load [open with] actions for the viewer', () => {
|
||||||
config.config.extensions = {
|
extensions.setup({
|
||||||
core: {
|
version: '1.0.0',
|
||||||
features: {
|
features: {
|
||||||
viewer: {
|
viewer: {
|
||||||
'open-with': [
|
openWith: [
|
||||||
{
|
{
|
||||||
disabled: false,
|
disabled: false,
|
||||||
id: 'aca:viewer/action1',
|
id: 'aca:viewer/action1',
|
||||||
order: 100,
|
order: 100,
|
||||||
icon: 'build',
|
icon: 'build',
|
||||||
title: 'Snackbar',
|
title: 'Snackbar',
|
||||||
action: 'aca:actions/info'
|
type: ContentActionType.default,
|
||||||
|
actions: {
|
||||||
|
click: 'aca:actions/info'
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
}
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
});
|
||||||
|
|
||||||
extensions.init();
|
|
||||||
expect(extensions.openWithActions.length).toBe(1);
|
expect(extensions.openWithActions.length).toBe(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should have an empty [open with] list if config is empty', () => {
|
|
||||||
config.config.extensions = {};
|
|
||||||
extensions.init();
|
|
||||||
expect(extensions.openWithActions).toEqual([]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should load only enabled [open with] actions for the viewer', () => {
|
it('should load only enabled [open with] actions for the viewer', () => {
|
||||||
config.config.extensions = {
|
extensions.setup({
|
||||||
core: {
|
version: '1.0.0',
|
||||||
features: {
|
features: {
|
||||||
viewer: {
|
viewer: {
|
||||||
'open-with': [
|
openWith: [
|
||||||
{
|
{
|
||||||
id: 'aca:viewer/action2',
|
id: 'aca:viewer/action2',
|
||||||
order: 200,
|
order: 200,
|
||||||
icon: 'build',
|
icon: 'build',
|
||||||
title: 'Snackbar',
|
title: 'Snackbar',
|
||||||
action: 'aca:actions/info'
|
type: ContentActionType.default,
|
||||||
},
|
actions: {
|
||||||
{
|
click: 'aca:actions/info'
|
||||||
disabled: true,
|
|
||||||
id: 'aca:viewer/action1',
|
|
||||||
order: 100,
|
|
||||||
icon: 'build',
|
|
||||||
title: 'Snackbar',
|
|
||||||
action: 'aca:actions/info'
|
|
||||||
}
|
}
|
||||||
]
|
},
|
||||||
}
|
{
|
||||||
|
disabled: true,
|
||||||
|
id: 'aca:viewer/action1',
|
||||||
|
order: 100,
|
||||||
|
icon: 'build',
|
||||||
|
title: 'Snackbar',
|
||||||
|
type: ContentActionType.default,
|
||||||
|
actions: {
|
||||||
|
click: 'aca:actions/info'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
});
|
||||||
|
|
||||||
extensions.init();
|
|
||||||
expect(extensions.openWithActions.length).toBe(1);
|
expect(extensions.openWithActions.length).toBe(1);
|
||||||
expect(extensions.openWithActions[0].id).toBe('aca:viewer/action2');
|
expect(extensions.openWithActions[0].id).toBe('aca:viewer/action2');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should sort [open with] actions by order', () => {
|
it('should sort [open with] actions by order', () => {
|
||||||
config.config.extensions = {
|
extensions.setup({
|
||||||
core: {
|
version: '1.0.0',
|
||||||
features: {
|
features: {
|
||||||
viewer: {
|
viewer: {
|
||||||
'open-with': [
|
openWith: [
|
||||||
{
|
{
|
||||||
id: 'aca:viewer/action2',
|
id: 'aca:viewer/action2',
|
||||||
order: 200,
|
order: 200,
|
||||||
icon: 'build',
|
icon: 'build',
|
||||||
title: 'Snackbar',
|
title: 'Snackbar',
|
||||||
action: 'aca:actions/info'
|
type: ContentActionType.default,
|
||||||
},
|
actions: {
|
||||||
{
|
click: 'aca:actions/info'
|
||||||
id: 'aca:viewer/action1',
|
|
||||||
order: 100,
|
|
||||||
icon: 'build',
|
|
||||||
title: 'Snackbar',
|
|
||||||
action: 'aca:actions/info'
|
|
||||||
}
|
}
|
||||||
]
|
},
|
||||||
}
|
{
|
||||||
|
id: 'aca:viewer/action1',
|
||||||
|
order: 100,
|
||||||
|
icon: 'build',
|
||||||
|
title: 'Snackbar',
|
||||||
|
type: ContentActionType.default,
|
||||||
|
actions: {
|
||||||
|
click: 'aca:actions/info'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
});
|
||||||
|
|
||||||
extensions.init();
|
|
||||||
expect(extensions.openWithActions.length).toBe(2);
|
expect(extensions.openWithActions.length).toBe(2);
|
||||||
expect(extensions.openWithActions[0].id).toBe('aca:viewer/action1');
|
expect(extensions.openWithActions[0].id).toBe('aca:viewer/action1');
|
||||||
expect(extensions.openWithActions[1].id).toBe('aca:viewer/action2');
|
expect(extensions.openWithActions[1].id).toBe('aca:viewer/action2');
|
||||||
@ -328,67 +395,47 @@ describe('ExtensionService', () => {
|
|||||||
|
|
||||||
describe('create', () => {
|
describe('create', () => {
|
||||||
it('should load [create] actions from config', () => {
|
it('should load [create] actions from config', () => {
|
||||||
config.config.extensions = {
|
extensions.setup({
|
||||||
core: {
|
version: '1.0.0',
|
||||||
features: {
|
features: {
|
||||||
create: [
|
create: [
|
||||||
{
|
{
|
||||||
disabled: false,
|
id: 'aca:create/folder',
|
||||||
id: 'aca:create/folder',
|
order: 100,
|
||||||
order: 100,
|
icon: 'create_new_folder',
|
||||||
icon: 'create_new_folder',
|
title: 'ext: Create Folder',
|
||||||
title: 'ext: Create Folder',
|
type: ContentActionType.default
|
||||||
target: {
|
}
|
||||||
permissions: ['create'],
|
]
|
||||||
action: 'aca:actions/create-folder'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
});
|
||||||
|
|
||||||
extensions.init();
|
|
||||||
expect(extensions.createActions.length).toBe(1);
|
expect(extensions.createActions.length).toBe(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should have an empty [create] actions if config is empty', () => {
|
|
||||||
config.config.extensions = {};
|
|
||||||
extensions.init();
|
|
||||||
expect(extensions.createActions).toEqual([]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should sort [create] actions by order', () => {
|
it('should sort [create] actions by order', () => {
|
||||||
config.config.extensions = {
|
extensions.setup({
|
||||||
core: {
|
version: '1.0.0',
|
||||||
features: {
|
features: {
|
||||||
create: [
|
create: [
|
||||||
{
|
{
|
||||||
id: 'aca:create/folder',
|
id: 'aca:create/folder',
|
||||||
order: 100,
|
order: 100,
|
||||||
icon: 'create_new_folder',
|
icon: 'create_new_folder',
|
||||||
title: 'ext: Create Folder',
|
title: 'ext: Create Folder',
|
||||||
target: {
|
type: ContentActionType.default
|
||||||
permissions: ['create'],
|
},
|
||||||
action: 'aca:actions/create-folder'
|
{
|
||||||
}
|
id: 'aca:create/folder-2',
|
||||||
},
|
order: 10,
|
||||||
{
|
icon: 'create_new_folder',
|
||||||
id: 'aca:create/folder-2',
|
title: 'ext: Create Folder',
|
||||||
order: 10,
|
type: ContentActionType.default
|
||||||
icon: 'create_new_folder',
|
}
|
||||||
title: 'ext: Create Folder',
|
]
|
||||||
target: {
|
|
||||||
permissions: ['create'],
|
|
||||||
action: 'aca:actions/create-folder'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
});
|
||||||
|
|
||||||
extensions.init();
|
|
||||||
expect(extensions.createActions.length).toBe(2);
|
expect(extensions.createActions.length).toBe(2);
|
||||||
expect(extensions.createActions[0].id).toBe('aca:create/folder-2');
|
expect(extensions.createActions[0].id).toBe('aca:create/folder-2');
|
||||||
expect(extensions.createActions[1].id).toBe('aca:create/folder');
|
expect(extensions.createActions[1].id).toBe('aca:create/folder');
|
||||||
|
@ -24,69 +24,190 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { Injectable, Type } from '@angular/core';
|
import { Injectable, Type } from '@angular/core';
|
||||||
import { AppConfigService } from '@alfresco/adf-core';
|
import { HttpClient } from '@angular/common/http';
|
||||||
import {
|
import { Store } from '@ngrx/store';
|
||||||
ContentActionExtension,
|
|
||||||
ContentActionType
|
|
||||||
} from './content-action.extension';
|
|
||||||
import { OpenWithExtension } from './open-with.extension';
|
|
||||||
import { NavigationExtension } from './navigation.extension';
|
|
||||||
import { Route } from '@angular/router';
|
import { Route } from '@angular/router';
|
||||||
import { Node } from 'alfresco-js-api';
|
import { ExtensionConfig } from './extension.config';
|
||||||
import { RuleService } from './rules/rule.service';
|
import { AppStore, SelectionState } from '../store/states';
|
||||||
import { ActionService } from './actions/action.service';
|
import { NavigationState } from '../store/states/navigation.state';
|
||||||
import { ActionRef } from './actions/action-ref';
|
import { selectionWithFolder } from '../store/selectors/app.selectors';
|
||||||
import { RouteRef } from './route-ref';
|
import { NavBarGroupRef } from './navbar.extensions';
|
||||||
|
import { RouteRef } from './routing.extensions';
|
||||||
|
import { RuleContext, RuleRef, RuleEvaluator } from './rule.extensions';
|
||||||
|
import { ActionRef, ContentActionRef, ContentActionType } from './action.extensions';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ExtensionService {
|
export class ExtensionService implements RuleContext {
|
||||||
|
configPath = 'assets/app.extensions.json';
|
||||||
|
pluginsPath = 'assets/plugins';
|
||||||
|
|
||||||
contentActions: Array<ContentActionExtension> = [];
|
defaults = {
|
||||||
openWithActions: Array<OpenWithExtension> = [];
|
layout: 'app.layout.main',
|
||||||
createActions: Array<ContentActionExtension> = [];
|
auth: ['app.auth']
|
||||||
|
};
|
||||||
|
|
||||||
|
rules: Array<RuleRef> = [];
|
||||||
routes: Array<RouteRef> = [];
|
routes: Array<RouteRef> = [];
|
||||||
|
actions: Array<ActionRef> = [];
|
||||||
|
|
||||||
|
contentActions: Array<ContentActionRef> = [];
|
||||||
|
openWithActions: Array<ContentActionRef> = [];
|
||||||
|
createActions: Array<ContentActionRef> = [];
|
||||||
|
navbar: Array<NavBarGroupRef> = [];
|
||||||
|
|
||||||
authGuards: { [key: string]: Type<{}> } = {};
|
authGuards: { [key: string]: Type<{}> } = {};
|
||||||
components: { [key: string]: Type<{}> } = {};
|
components: { [key: string]: Type<{}> } = {};
|
||||||
|
|
||||||
constructor(
|
evaluators: { [key: string]: RuleEvaluator } = {};
|
||||||
private config: AppConfigService,
|
selection: SelectionState;
|
||||||
private ruleService: RuleService,
|
navigation: NavigationState;
|
||||||
private actionService: ActionService
|
|
||||||
) {}
|
|
||||||
|
|
||||||
// initialise extension service
|
constructor(private http: HttpClient, private store: Store<AppStore>) {
|
||||||
// in future will also load and merge data from the external plugins
|
this.store.select(selectionWithFolder).subscribe(result => {
|
||||||
init() {
|
this.selection = result.selection;
|
||||||
this.routes = this.config.get<Array<RouteRef>>(
|
this.navigation = result.navigation;
|
||||||
'extensions.core.routes',
|
});
|
||||||
[]
|
}
|
||||||
);
|
|
||||||
|
|
||||||
this.contentActions = this.config
|
load(): Promise<boolean> {
|
||||||
.get<Array<ContentActionExtension>>(
|
return new Promise<any>(resolve => {
|
||||||
'extensions.core.features.content.actions',
|
this.loadConfig(this.configPath, 0).then(result => {
|
||||||
[]
|
let config = result.config;
|
||||||
)
|
|
||||||
.sort(this.sortByOrder);
|
|
||||||
|
|
||||||
this.openWithActions = this.config
|
if (config.references && config.references.length > 0) {
|
||||||
.get<Array<OpenWithExtension>>(
|
const plugins = config.references.map(
|
||||||
'extensions.core.features.viewer.open-with',
|
(name, idx) => this.loadConfig(`${this.pluginsPath}/${name}`, idx)
|
||||||
[]
|
);
|
||||||
)
|
|
||||||
.filter(entry => !entry.disabled)
|
|
||||||
.sort(this.sortByOrder);
|
|
||||||
|
|
||||||
this.createActions = this.config
|
Promise.all(plugins).then((results => {
|
||||||
.get<Array<ContentActionExtension>>(
|
const configs = results
|
||||||
'extensions.core.features.create',
|
.filter(entry => entry)
|
||||||
[]
|
.sort(this.sortByOrder)
|
||||||
)
|
.map(entry => entry.config);
|
||||||
.sort(this.sortByOrder);
|
|
||||||
|
|
||||||
this.ruleService.init();
|
if (configs.length > 0) {
|
||||||
this.actionService.init();
|
config = this.mergeConfigs(config, ...configs);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setup(config);
|
||||||
|
resolve(true);
|
||||||
|
}));
|
||||||
|
} else {
|
||||||
|
this.setup(config);
|
||||||
|
resolve(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
setup(config: ExtensionConfig) {
|
||||||
|
if (!config) {
|
||||||
|
console.error('Extension configuration not found');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.rules = this.loadRules(config);
|
||||||
|
this.actions = this.loadActions(config);
|
||||||
|
this.routes = this.loadRoutes(config);
|
||||||
|
this.contentActions = this.loadContentActions(config);
|
||||||
|
this.openWithActions = this.loadViewerOpenWith(config);
|
||||||
|
this.createActions = this.loadCreateActions(config);
|
||||||
|
this.navbar = this.loadNavBar(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected loadConfig(url: string, order: number): Promise<{ order: number, config: ExtensionConfig }> {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
this.http.get<ExtensionConfig>(url).subscribe(
|
||||||
|
config => {
|
||||||
|
resolve({
|
||||||
|
order,
|
||||||
|
config
|
||||||
|
});
|
||||||
|
},
|
||||||
|
error => {
|
||||||
|
console.log(error);
|
||||||
|
resolve(null);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected loadCreateActions(config: ExtensionConfig): Array<ContentActionRef> {
|
||||||
|
if (config && config.features) {
|
||||||
|
return (config.features.create || []).sort(
|
||||||
|
this.sortByOrder
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
protected loadContentActions(config: ExtensionConfig) {
|
||||||
|
if (config && config.features && config.features.content) {
|
||||||
|
return (config.features.content.actions || []).sort(
|
||||||
|
this.sortByOrder
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
protected loadNavBar(config: ExtensionConfig): any {
|
||||||
|
if (config && config.features) {
|
||||||
|
return (config.features.navbar || [])
|
||||||
|
.filter(entry => !entry.disabled)
|
||||||
|
.sort(this.sortByOrder)
|
||||||
|
.map(group => {
|
||||||
|
return {
|
||||||
|
...group,
|
||||||
|
items: (group.items || [])
|
||||||
|
.filter(item => !item.disabled)
|
||||||
|
.sort(this.sortByOrder)
|
||||||
|
.map(item => {
|
||||||
|
const routeRef = this.getRouteById(item.route);
|
||||||
|
const url = `/${routeRef ? routeRef.path : item.route}`;
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
url
|
||||||
|
};
|
||||||
|
})
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected loadViewerOpenWith(config: ExtensionConfig): Array<ContentActionRef> {
|
||||||
|
if (config && config.features && config.features.viewer) {
|
||||||
|
return (config.features.viewer.openWith || [])
|
||||||
|
.filter(entry => !entry.disabled)
|
||||||
|
.sort(this.sortByOrder);
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
protected loadRules(config: ExtensionConfig): Array<RuleRef> {
|
||||||
|
if (config && config.rules) {
|
||||||
|
return config.rules;
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
protected loadRoutes(config: ExtensionConfig): Array<RouteRef> {
|
||||||
|
if (config) {
|
||||||
|
return config.routes || [];
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
protected loadActions(config: ExtensionConfig): Array<ActionRef> {
|
||||||
|
if (config) {
|
||||||
|
return config.actions || [];
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
setEvaluator(key: string, value: RuleEvaluator): ExtensionService {
|
||||||
|
this.evaluators[key] = value;
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
setAuthGuard(key: string, value: Type<{}>): ExtensionService {
|
setAuthGuard(key: string, value: Type<{}>): ExtensionService {
|
||||||
@ -98,45 +219,14 @@ export class ExtensionService {
|
|||||||
return this.routes.find(route => route.id === id);
|
return this.routes.find(route => route.id === id);
|
||||||
}
|
}
|
||||||
|
|
||||||
getActionById(id: string): ActionRef {
|
|
||||||
return this.actionService.getActionById(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
runActionById(id: string, context?: any) {
|
|
||||||
this.actionService.runActionById(id, context);
|
|
||||||
}
|
|
||||||
|
|
||||||
getAuthGuards(ids: string[]): Array<Type<{}>> {
|
getAuthGuards(ids: string[]): Array<Type<{}>> {
|
||||||
return (ids || [])
|
return (ids || [])
|
||||||
.map(id => this.authGuards[id])
|
.map(id => this.authGuards[id])
|
||||||
.filter(guard => guard);
|
.filter(guard => guard);
|
||||||
}
|
}
|
||||||
|
|
||||||
getNavigationGroups(): Array<NavigationExtension[]> {
|
getNavigationGroups(): Array<NavBarGroupRef> {
|
||||||
const settings = this.config.get<any>(
|
return this.navbar;
|
||||||
'extensions.core.features.navigation'
|
|
||||||
);
|
|
||||||
if (settings) {
|
|
||||||
const groups = Object.keys(settings).map(key => {
|
|
||||||
return settings[key]
|
|
||||||
.map(group => {
|
|
||||||
const customRoute = this.getRouteById(group.route);
|
|
||||||
const route = `/${
|
|
||||||
customRoute ? customRoute.path : group.route
|
|
||||||
}`;
|
|
||||||
|
|
||||||
return {
|
|
||||||
...group,
|
|
||||||
route
|
|
||||||
};
|
|
||||||
})
|
|
||||||
.filter(entry => !entry.disabled);
|
|
||||||
});
|
|
||||||
|
|
||||||
return groups;
|
|
||||||
}
|
|
||||||
|
|
||||||
return [];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setComponent(id: string, value: Type<{}>): ExtensionService {
|
setComponent(id: string, value: Type<{}>): ExtensionService {
|
||||||
@ -150,11 +240,15 @@ export class ExtensionService {
|
|||||||
|
|
||||||
getApplicationRoutes(): Array<Route> {
|
getApplicationRoutes(): Array<Route> {
|
||||||
return this.routes.map(route => {
|
return this.routes.map(route => {
|
||||||
const guards = this.getAuthGuards(route.auth);
|
const guards = this.getAuthGuards(
|
||||||
|
route.auth && route.auth.length > 0
|
||||||
|
? route.auth
|
||||||
|
: this.defaults.auth
|
||||||
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
path: route.path,
|
path: route.path,
|
||||||
component: this.getComponentById(route.layout),
|
component: this.getComponentById(route.layout || this.defaults.layout),
|
||||||
canActivateChild: guards,
|
canActivateChild: guards,
|
||||||
canActivate: guards,
|
canActivate: guards,
|
||||||
children: [
|
children: [
|
||||||
@ -168,8 +262,7 @@ export class ExtensionService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// evaluates create actions for the folder node
|
getCreateActions(): Array<ContentActionRef> {
|
||||||
getFolderCreateActions(folder: Node): Array<ContentActionExtension> {
|
|
||||||
return this.createActions
|
return this.createActions
|
||||||
.filter(this.filterEnabled)
|
.filter(this.filterEnabled)
|
||||||
.filter(action => this.filterByRules(action))
|
.filter(action => this.filterByRules(action))
|
||||||
@ -177,18 +270,18 @@ export class ExtensionService {
|
|||||||
let disabled = false;
|
let disabled = false;
|
||||||
|
|
||||||
if (action.rules && action.rules.enabled) {
|
if (action.rules && action.rules.enabled) {
|
||||||
disabled = !this.ruleService.evaluateRule(action.rules.enabled);
|
disabled = !this.evaluateRule(action.rules.enabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...action,
|
...action,
|
||||||
disabled
|
disabled
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// evaluates content actions for the selection and parent folder node
|
// evaluates content actions for the selection and parent folder node
|
||||||
getAllowedContentActions(): Array<ContentActionExtension> {
|
getAllowedContentActions(): Array<ContentActionRef> {
|
||||||
return this.contentActions
|
return this.contentActions
|
||||||
.filter(this.filterEnabled)
|
.filter(this.filterEnabled)
|
||||||
.filter(action => this.filterByRules(action))
|
.filter(action => this.filterByRules(action))
|
||||||
@ -211,11 +304,11 @@ export class ExtensionService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
reduceSeparators(
|
reduceSeparators(
|
||||||
acc: ContentActionExtension[],
|
acc: ContentActionRef[],
|
||||||
el: ContentActionExtension,
|
el: ContentActionRef,
|
||||||
i: number,
|
i: number,
|
||||||
arr: ContentActionExtension[]
|
arr: ContentActionRef[]
|
||||||
): ContentActionExtension[] {
|
): ContentActionRef[] {
|
||||||
// remove duplicate separators
|
// remove duplicate separators
|
||||||
if (i > 0) {
|
if (i > 0) {
|
||||||
const prev = arr[i - 1];
|
const prev = arr[i - 1];
|
||||||
@ -238,9 +331,9 @@ export class ExtensionService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
reduceEmptyMenus(
|
reduceEmptyMenus(
|
||||||
acc: ContentActionExtension[],
|
acc: ContentActionRef[],
|
||||||
el: ContentActionExtension
|
el: ContentActionRef
|
||||||
): ContentActionExtension[] {
|
): ContentActionRef[] {
|
||||||
if (el.type === ContentActionType.menu) {
|
if (el.type === ContentActionType.menu) {
|
||||||
if ((el.children || []).length === 0) {
|
if ((el.children || []).length === 0) {
|
||||||
return acc;
|
return acc;
|
||||||
@ -262,7 +355,7 @@ export class ExtensionService {
|
|||||||
return !entry.disabled;
|
return !entry.disabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
copyAction(action: ContentActionExtension): ContentActionExtension {
|
copyAction(action: ContentActionRef): ContentActionRef {
|
||||||
return {
|
return {
|
||||||
...action,
|
...action,
|
||||||
children: (action.children || []).map(child =>
|
children: (action.children || []).map(child =>
|
||||||
@ -271,10 +364,70 @@ export class ExtensionService {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
filterByRules(action: ContentActionExtension): boolean {
|
filterByRules(action: ContentActionRef): boolean {
|
||||||
if (action && action.rules && action.rules.visible) {
|
if (action && action.rules && action.rules.visible) {
|
||||||
return this.ruleService.evaluateRule(action.rules.visible);
|
return this.evaluateRule(action.rules.visible);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getActionById(id: string): ActionRef {
|
||||||
|
return this.actions.find(action => action.id === id);
|
||||||
|
}
|
||||||
|
|
||||||
|
runActionById(id: string, context?: any) {
|
||||||
|
const action = this.getActionById(id);
|
||||||
|
if (action) {
|
||||||
|
const { type, payload } = action;
|
||||||
|
const expression = this.runExpression(payload, context);
|
||||||
|
|
||||||
|
this.store.dispatch({ type, payload: expression });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
runExpression(value: string, context?: any) {
|
||||||
|
const pattern = new RegExp(/\$\((.*\)?)\)/g);
|
||||||
|
const matches = pattern.exec(value);
|
||||||
|
|
||||||
|
if (matches && matches.length > 1) {
|
||||||
|
const expression = matches[1];
|
||||||
|
const fn = new Function('context', `return ${expression}`);
|
||||||
|
const result = fn(context);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
evaluateRule(ruleId: string): boolean {
|
||||||
|
const ruleRef = this.rules.find(ref => ref.id === ruleId);
|
||||||
|
if (ruleRef) {
|
||||||
|
const evaluator = this.evaluators[ruleRef.type];
|
||||||
|
if (evaluator) {
|
||||||
|
return evaluator(this, ...ruleRef.parameters);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// todo: requires overwrite support for array entries
|
||||||
|
// todo: overwrite only particular areas, don't touch version or other top-level props
|
||||||
|
protected mergeConfigs(...objects): any {
|
||||||
|
const result = {};
|
||||||
|
|
||||||
|
objects.forEach(source => {
|
||||||
|
Object.keys(source).forEach(prop => {
|
||||||
|
if (prop in result && Array.isArray(result[prop])) {
|
||||||
|
result[prop] = result[prop].concat(source[prop]);
|
||||||
|
} else if (prop in result && typeof result[prop] === 'object') {
|
||||||
|
result[prop] = this.mergeConfigs(result[prop], source[prop]);
|
||||||
|
} else {
|
||||||
|
result[prop] = source[prop];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,12 +23,22 @@
|
|||||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export interface NavigationExtension {
|
export interface NavBarGroupRef {
|
||||||
|
id: string;
|
||||||
|
items: Array<NavBarLinkRef>;
|
||||||
|
|
||||||
|
order?: number;
|
||||||
|
disabled?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface NavBarLinkRef {
|
||||||
id: string;
|
id: string;
|
||||||
order: number;
|
|
||||||
icon: string;
|
icon: string;
|
||||||
title: string;
|
title: string;
|
||||||
route: string;
|
route: string;
|
||||||
|
|
||||||
|
url?: string; // evaluated at runtime based on route ref
|
||||||
description?: string;
|
description?: string;
|
||||||
|
order?: number;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
}
|
}
|
@ -1,33 +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/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
export interface OpenWithExtension {
|
|
||||||
id: string;
|
|
||||||
order?: number;
|
|
||||||
icon: string;
|
|
||||||
title: string;
|
|
||||||
action: string;
|
|
||||||
disabled?: boolean;
|
|
||||||
}
|
|
@ -27,7 +27,8 @@ export interface RouteRef {
|
|||||||
id: string;
|
id: string;
|
||||||
path: string;
|
path: string;
|
||||||
component: string;
|
component: string;
|
||||||
layout: string;
|
|
||||||
auth: string[];
|
layout?: string;
|
||||||
|
auth?: string[];
|
||||||
data?: { [key: string]: string };
|
data?: { [key: string]: string };
|
||||||
}
|
}
|
@ -23,12 +23,24 @@
|
|||||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { SelectionState } from '../../store/states';
|
import { SelectionState } from '../store/states';
|
||||||
import { RuleEvaluator } from './rule.service';
|
import { NavigationState } from '../store/states/navigation.state';
|
||||||
import { NavigationState } from '../../store/states/navigation.state';
|
|
||||||
|
export type RuleEvaluator = (context: RuleContext, ...args: any[]) => boolean;
|
||||||
|
|
||||||
export interface RuleContext {
|
export interface RuleContext {
|
||||||
selection: SelectionState;
|
selection: SelectionState;
|
||||||
navigation: NavigationState;
|
navigation: NavigationState;
|
||||||
evaluators: { [key: string]: RuleEvaluator };
|
evaluators: { [key: string]: RuleEvaluator };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class RuleRef {
|
||||||
|
type: string;
|
||||||
|
id?: string;
|
||||||
|
parameters?: Array<RuleParameter>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RuleParameter {
|
||||||
|
type: string;
|
||||||
|
value: any;
|
||||||
|
}
|
@ -1,29 +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/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
export interface RuleParameter {
|
|
||||||
type: string;
|
|
||||||
value: any;
|
|
||||||
}
|
|
@ -1,34 +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 { RuleParameter } from './rule-parameter';
|
|
||||||
import { RuleEvaluator } from './rule.service';
|
|
||||||
|
|
||||||
export class RuleRef {
|
|
||||||
type: string;
|
|
||||||
id?: string;
|
|
||||||
parameters?: Array<RuleParameter>;
|
|
||||||
evaluator?: RuleEvaluator;
|
|
||||||
}
|
|
@ -1,97 +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 { Injectable } from '@angular/core';
|
|
||||||
import { AppConfigService } from '@alfresco/adf-core';
|
|
||||||
import { every, some } from './core.evaluators';
|
|
||||||
import { RuleContext } from './rule-context';
|
|
||||||
import { RuleRef } from './rule-ref';
|
|
||||||
import { createSelector, Store } from '@ngrx/store';
|
|
||||||
import {
|
|
||||||
appSelection,
|
|
||||||
appNavigation
|
|
||||||
} from '../../store/selectors/app.selectors';
|
|
||||||
import { AppStore, SelectionState } from '../../store/states';
|
|
||||||
import { NavigationState } from '../../store/states/navigation.state';
|
|
||||||
import { canCreateFolder, hasFolderSelected, canUpdateSelectedFolder, hasFileSelected, canDownloadSelection } from './app.evaluators';
|
|
||||||
|
|
||||||
export type RuleEvaluator = (context: RuleContext, ...args: any[]) => boolean;
|
|
||||||
|
|
||||||
export const selectionWithFolder = createSelector(
|
|
||||||
appSelection,
|
|
||||||
appNavigation,
|
|
||||||
(selection, navigation) => {
|
|
||||||
return {
|
|
||||||
selection,
|
|
||||||
navigation
|
|
||||||
};
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class RuleService implements RuleContext {
|
|
||||||
rules: Array<RuleRef> = [];
|
|
||||||
evaluators: { [key: string]: RuleEvaluator } = {};
|
|
||||||
selection: SelectionState;
|
|
||||||
navigation: NavigationState;
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
private config: AppConfigService,
|
|
||||||
private store: Store<AppStore>
|
|
||||||
) {
|
|
||||||
this.evaluators['core.every'] = every;
|
|
||||||
this.evaluators['core.some'] = some;
|
|
||||||
this.evaluators['app.selection.canDownload'] = canDownloadSelection;
|
|
||||||
this.evaluators['app.selection.file'] = hasFileSelected;
|
|
||||||
this.evaluators['app.selection.folder'] = hasFolderSelected;
|
|
||||||
this.evaluators['app.selection.folder.canUpdate'] = canUpdateSelectedFolder;
|
|
||||||
this.evaluators['app.navigation.folder.canCreate'] = canCreateFolder;
|
|
||||||
|
|
||||||
this.store
|
|
||||||
.select(selectionWithFolder)
|
|
||||||
.subscribe(result => {
|
|
||||||
this.selection = result.selection;
|
|
||||||
this.navigation = result.navigation;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
init() {
|
|
||||||
this.rules = this.config
|
|
||||||
.get<Array<RuleRef>>('extensions.core.rules', [])
|
|
||||||
.map(rule => {
|
|
||||||
rule.evaluator = this.evaluators[rule.type];
|
|
||||||
return rule;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
evaluateRule(ruleId: string): boolean {
|
|
||||||
const ruleRef = this.rules.find(ref => ref.id === ruleId);
|
|
||||||
|
|
||||||
if (ruleRef.evaluator) {
|
|
||||||
return ruleRef.evaluator(this, ...ruleRef.parameters);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
@ -36,3 +36,14 @@ 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 selectionWithFolder = createSelector(
|
||||||
|
appSelection,
|
||||||
|
appNavigation,
|
||||||
|
(selection, navigation) => {
|
||||||
|
return {
|
||||||
|
selection,
|
||||||
|
navigation
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
@ -60,8 +60,6 @@ import { NodeActionsService } from '../services/node-actions.service';
|
|||||||
import { NodePermissionService } from '../services/node-permission.service';
|
import { NodePermissionService } from '../services/node-permission.service';
|
||||||
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';
|
||||||
import { RuleService } from '../extensions/rules/rule.service';
|
|
||||||
import { ActionService } from '../extensions/actions/action.service';
|
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
@ -114,9 +112,7 @@ import { ActionService } from '../extensions/actions/action.service';
|
|||||||
NodeActionsService,
|
NodeActionsService,
|
||||||
NodePermissionService,
|
NodePermissionService,
|
||||||
ContentApiService,
|
ContentApiService,
|
||||||
ExtensionService,
|
ExtensionService
|
||||||
RuleService,
|
|
||||||
ActionService
|
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
export class AppTestingModule {}
|
export class AppTestingModule {}
|
||||||
|
189
src/assets/app.extensions.json
Normal file
189
src/assets/app.extensions.json
Normal file
@ -0,0 +1,189 @@
|
|||||||
|
{
|
||||||
|
"$schema": "../../extension.schema.json",
|
||||||
|
"name": "app",
|
||||||
|
"version": "1.0.0",
|
||||||
|
|
||||||
|
"references": [
|
||||||
|
"plugin1.json",
|
||||||
|
"plugin2.json"
|
||||||
|
],
|
||||||
|
|
||||||
|
"rules": [
|
||||||
|
{
|
||||||
|
"id": "app.create.canCreateFolder",
|
||||||
|
"type": "app.navigation.folder.canCreate"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "app.toolbar.canEditFolder",
|
||||||
|
"type": "core.every",
|
||||||
|
"parameters": [
|
||||||
|
{ "type": "rule", "value": "app.selection.folder" },
|
||||||
|
{ "type": "rule", "value": "app.selection.folder.canUpdate" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "app.toolbar.canViewFile",
|
||||||
|
"type": "app.selection.file"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "app.toolbar.canDownload",
|
||||||
|
"type": "app.selection.canDownload"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
|
"actions": [
|
||||||
|
{
|
||||||
|
"id": "app.actions.createFolder",
|
||||||
|
"type": "CREATE_FOLDER"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "app.actions.editFolder",
|
||||||
|
"type": "EDIT_FOLDER"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "app.actions.download",
|
||||||
|
"type": "DOWNLOAD_NODES"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "app.actions.preview",
|
||||||
|
"type": "VIEW_FILE"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
|
"features": {
|
||||||
|
"create": [
|
||||||
|
{
|
||||||
|
"id": "app.create.folder",
|
||||||
|
"type": "default",
|
||||||
|
"icon": "create_new_folder",
|
||||||
|
"title": "ext: Create Folder",
|
||||||
|
"actions": {
|
||||||
|
"click": "app.actions.createFolder"
|
||||||
|
},
|
||||||
|
"rules": {
|
||||||
|
"enabled": "app.create.canCreateFolder"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"navbar": [
|
||||||
|
{
|
||||||
|
"id": "app.navbar.primary",
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"id": "app.navbar.personalFiles",
|
||||||
|
"icon": "folder",
|
||||||
|
"title": "APP.BROWSE.PERSONAL.SIDENAV_LINK.LABEL",
|
||||||
|
"description": "APP.BROWSE.PERSONAL.SIDENAV_LINK.TOOLTIP",
|
||||||
|
"route": "personal-files"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "app.navbar.libraries",
|
||||||
|
"icon": "group_work",
|
||||||
|
"title": "APP.BROWSE.LIBRARIES.SIDENAV_LINK.LABEL",
|
||||||
|
"description": "APP.BROWSE.LIBRARIES.SIDENAV_LINK.TOOLTIP",
|
||||||
|
"route": "libraries"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "app.navbar.secondary",
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"id": "app.navbar.shared",
|
||||||
|
"icon": "people",
|
||||||
|
"title": "APP.BROWSE.SHARED.SIDENAV_LINK.LABEL",
|
||||||
|
"description": "APP.BROWSE.SHARED.SIDENAV_LINK.TOOLTIP",
|
||||||
|
"route": "shared"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "app.navbar.recentFiles",
|
||||||
|
"icon": "schedule",
|
||||||
|
"title": "APP.BROWSE.RECENT.SIDENAV_LINK.LABEL",
|
||||||
|
"description": "APP.BROWSE.RECENT.SIDENAV_LINK.TOOLTIP",
|
||||||
|
"route": "recent-files"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "app.navbar.favorites",
|
||||||
|
"icon": "star",
|
||||||
|
"title": "APP.BROWSE.FAVORITES.SIDENAV_LINK.LABEL",
|
||||||
|
"description": "APP.BROWSE.FAVORITES.SIDENAV_LINK.TOOLTIP",
|
||||||
|
"route": "favorites"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "app.navbar.trashcan",
|
||||||
|
"icon": "delete",
|
||||||
|
"title": "APP.BROWSE.TRASHCAN.SIDENAV_LINK.LABEL",
|
||||||
|
"description": "APP.BROWSE.TRASHCAN.SIDENAV_LINK.TOOLTIP",
|
||||||
|
"route": "trashcan"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"content": {
|
||||||
|
"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": "app.actions.createFolder"
|
||||||
|
},
|
||||||
|
"rules": {
|
||||||
|
"visible": "app.create.canCreateFolder"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "app.toolbar.preview",
|
||||||
|
"type": "button",
|
||||||
|
"order": 15,
|
||||||
|
"title": "APP.ACTIONS.VIEW",
|
||||||
|
"icon": "open_in_browser",
|
||||||
|
"actions": {
|
||||||
|
"click": "app.actions.preview"
|
||||||
|
},
|
||||||
|
"rules": {
|
||||||
|
"visible": "app.toolbar.canViewFile"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "app.toolbar.download",
|
||||||
|
"type": "button",
|
||||||
|
"order": 20,
|
||||||
|
"title": "APP.ACTIONS.DOWNLOAD",
|
||||||
|
"icon": "get_app",
|
||||||
|
"actions": {
|
||||||
|
"click": "app.actions.download"
|
||||||
|
},
|
||||||
|
"rules": {
|
||||||
|
"visible": "app.toolbar.canDownload"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "app.toolbar.editFolder",
|
||||||
|
"type": "button",
|
||||||
|
"order": 30,
|
||||||
|
"title": "APP.ACTIONS.EDIT",
|
||||||
|
"icon": "create",
|
||||||
|
"actions": {
|
||||||
|
"click": "app.actions.editFolder"
|
||||||
|
},
|
||||||
|
"rules": {
|
||||||
|
"visible": "app.toolbar.canEditFolder"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "app.toolbar.separator.2",
|
||||||
|
"order": 200,
|
||||||
|
"type": "separator"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
0
src/assets/plugins/.gitkeep
Normal file
0
src/assets/plugins/.gitkeep
Normal file
66
src/assets/plugins/plugin1.json
Normal file
66
src/assets/plugins/plugin1.json
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
{
|
||||||
|
"$schema": "../../../extension.schema.json",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"name": "plugin1",
|
||||||
|
"description": "demo plugin",
|
||||||
|
|
||||||
|
"actions": [
|
||||||
|
{
|
||||||
|
"id": "plugin1.actions.settings",
|
||||||
|
"type": "NAVIGATE_URL",
|
||||||
|
"payload": "/settings"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "plugin1.actions.info",
|
||||||
|
"type": "SNACKBAR_INFO",
|
||||||
|
"payload": "I'm a nice little popup raised by extension."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "plugin1.actions.node-name",
|
||||||
|
"type": "SNACKBAR_INFO",
|
||||||
|
"payload": "$('Action for ' + context.selection.first.entry.name)"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
|
"features": {
|
||||||
|
"viewer": {
|
||||||
|
"openWith": [
|
||||||
|
{
|
||||||
|
"id": "plugin1.viewer.openWith.action1",
|
||||||
|
"type": "default",
|
||||||
|
"icon": "build",
|
||||||
|
"title": "Snackbar",
|
||||||
|
"actions": {
|
||||||
|
"click": "plugin1.actions.info"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"content": {
|
||||||
|
"actions": [
|
||||||
|
{
|
||||||
|
"id": "plugin1.toolbar.menu1",
|
||||||
|
"type": "menu",
|
||||||
|
"icon": "storage",
|
||||||
|
"order": 300,
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"id": "plugin1.toolbar.menu1.settings",
|
||||||
|
"type": "button",
|
||||||
|
"title": "Settings",
|
||||||
|
"icon": "settings_applications",
|
||||||
|
"actions": {
|
||||||
|
"click": "plugin1.actions.settings"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "plugin1.toolbar.separator3",
|
||||||
|
"order": 301,
|
||||||
|
"type": "separator"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
42
src/assets/plugins/plugin2.json
Normal file
42
src/assets/plugins/plugin2.json
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
{
|
||||||
|
"$schema": "../../../extension.schema.json",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"name": "plugin2",
|
||||||
|
"description": "demo plugin",
|
||||||
|
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"id": "plugin2.routes.about",
|
||||||
|
"path": "ext/about",
|
||||||
|
"component": "app.components.about",
|
||||||
|
"data": {
|
||||||
|
"title": "Custom About"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
|
"features": {
|
||||||
|
"navbar": [
|
||||||
|
{
|
||||||
|
"id": "plugin2.navbar.group1",
|
||||||
|
"disabled": true,
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"id": "plugin2.navbar.group1.link1",
|
||||||
|
"icon": "build",
|
||||||
|
"title": "About (native)",
|
||||||
|
"description": "Uses native application route",
|
||||||
|
"route": "about"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "plugin2.navbar.group1.link2",
|
||||||
|
"icon": "build",
|
||||||
|
"title": "About (custom)",
|
||||||
|
"description": "Uses custom defined route",
|
||||||
|
"route": "plugin2.routes.about"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
@ -69,7 +69,6 @@
|
|||||||
"no-trailing-whitespace": true,
|
"no-trailing-whitespace": true,
|
||||||
"no-unnecessary-initializer": true,
|
"no-unnecessary-initializer": true,
|
||||||
"no-unused-expression": true,
|
"no-unused-expression": true,
|
||||||
"no-use-before-declare": true,
|
|
||||||
"no-var-keyword": true,
|
"no-var-keyword": true,
|
||||||
"object-literal-sort-keys": false,
|
"object-literal-sort-keys": false,
|
||||||
"one-line": [
|
"one-line": [
|
||||||
|
Loading…
x
Reference in New Issue
Block a user