mirror of
https://github.com/Alfresco/alfresco-content-app.git
synced 2025-05-12 17:04:46 +00:00
basic docs on extensibility apis (#543)
basic docs on extensibility apis
This commit is contained in:
parent
744b03d22e
commit
22eac50d27
829
docs/extending.md
Normal file
829
docs/extending.md
Normal file
@ -0,0 +1,829 @@
|
||||
---
|
||||
title: Extending
|
||||
---
|
||||
|
||||
<p class="danger">
|
||||
Work is still in progress, the documentation and examples may change.
|
||||
</p>
|
||||
|
||||
# Extending
|
||||
|
||||
Application extensibility is performed via the root `/src/assets/app.extensions.json`,
|
||||
and any number of external plugins that are references of the main entry point.
|
||||
|
||||
The application also comes with the `/src/assets/plugins/` folder
|
||||
already preconfigured to store external files.
|
||||
|
||||
You can create plugins that change, toggle or extend the following areas:
|
||||
|
||||
* Navigation sidebar links and groups
|
||||
* Context Menu
|
||||
* Toolbar entries
|
||||
* buttons
|
||||
* menu buttons
|
||||
* separators
|
||||
* Viewer actions
|
||||
* "Open With" entries
|
||||
* "More actions" toolbar entries
|
||||
|
||||
Extensions can also:
|
||||
|
||||
* Overwrite or disable extension points of the main application or other plugins
|
||||
* Change rules, actions or any visual element
|
||||
* Register new application routes based on empty pages or layouts
|
||||
* Register new rule evaluators, components, guards, etc.
|
||||
|
||||
## Format
|
||||
|
||||
The format is represented by a JSON file with the structure similar to the following one:
|
||||
|
||||
```json
|
||||
{
|
||||
"$name": "app",
|
||||
"$version": "1.0.0",
|
||||
|
||||
"routes": [],
|
||||
"actions": [],
|
||||
"rules": [],
|
||||
"features": {}
|
||||
}
|
||||
```
|
||||
|
||||
### Schema
|
||||
|
||||
You can find the JSON schema at the project root folder: [extension.schema.json](https://github.com/Alfresco/alfresco-content-app/blob/master/extension.schema.json).
|
||||
|
||||
<p class="tip">
|
||||
Schema allows validating extension files, provides code completion and documentation hints.
|
||||
</p>
|
||||
|
||||
```json
|
||||
{
|
||||
"$schema": "../../extension.schema.json",
|
||||
"$name": "app",
|
||||
"$version": "1.0.0",
|
||||
}
|
||||
```
|
||||
|
||||
### Multiple files
|
||||
|
||||
You can have multiple extension files distributed separately.
|
||||
All additional files are linked via the `$references` property,
|
||||
the order of declaration defines also the order of loading.
|
||||
|
||||
```json
|
||||
{
|
||||
"$schema": "../../extension.schema.json",
|
||||
"$name": "app",
|
||||
"$version": "1.0.0",
|
||||
"$references": [
|
||||
"plugin1.json",
|
||||
"plugin2.json"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
<p class="warning">
|
||||
Always keep in mind that all extension files are merged together at runtime.
|
||||
That allows plugins overwriting the code from the main application or altering other plugins.
|
||||
</p>
|
||||
|
||||
## Routes
|
||||
|
||||
To create a new route, populate the `routes` section with the corresponding entries.
|
||||
|
||||
```json
|
||||
{
|
||||
"$schema": "../../../extension.schema.json",
|
||||
"$version": "1.0.0",
|
||||
"$name": "plugin1",
|
||||
|
||||
"routes": [
|
||||
{
|
||||
"id": "plugin1.routes.bin",
|
||||
"path": "ext/bin",
|
||||
"layout": "app.layout.main",
|
||||
"component": "app.components.trashcan"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Route properties
|
||||
|
||||
| Name | Description |
|
||||
| --- | --- |
|
||||
| **id** | Unique identifier. |
|
||||
| **path** | Runtime path of the route. |
|
||||
| **component** | The main [component](#components) to use for the route. |
|
||||
| *layout* | The layout [component](#components) to use for the route. |
|
||||
| *auth* | List of [authentication guards](#authentication-guards). Defaults to `[ "app.auth" ]`. |
|
||||
| *data* | Custom property bag to carry with the route. |
|
||||
|
||||
<p class="tip">
|
||||
Use the `app.layout.main` value for the `layout` property to get the default application layout,
|
||||
with header, navigation sidebar and main content area.
|
||||
<br/><br/>
|
||||
Leave the `layout` property empty if you want your route component take the whole page.
|
||||
</p>
|
||||
|
||||
You can define the full route schema like in the next example:
|
||||
|
||||
```json
|
||||
{
|
||||
"$schema": "../../../extension.schema.json",
|
||||
"$version": "1.0.0",
|
||||
"$name": "plugin1",
|
||||
|
||||
"routes": [
|
||||
{
|
||||
"id": "plugin1.routes.bin",
|
||||
"path": "ext/bin",
|
||||
"component": "app.components.trashcan",
|
||||
"layout": "app.layout.main",
|
||||
"auth": [ "app.auth" ],
|
||||
"data": {
|
||||
"title": "Custom Trashcan"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Authentication Guards
|
||||
|
||||
Below is the list of the authentication guards main application registers on startup.
|
||||
|
||||
| Key | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| app.auth | AuthGuardEcm | ADF guard, validates ACS authentication and redirects to Login if needed. |
|
||||
|
||||
You can refer those guards from within your custom extensions,
|
||||
or [register](#registration) your custom implementations.
|
||||
|
||||
## Components
|
||||
|
||||
You can register any Angular component to participate in extensibility.
|
||||
|
||||
The components are used to create custom:
|
||||
|
||||
* routes and pages
|
||||
* toolbar buttons
|
||||
* menu items
|
||||
|
||||
| Key | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| app.layout.main | LayoutComponent | Main application layout with the menu bar, navigation sidebar and main content area to project your components. |
|
||||
| app.components.trashcan | TrashcanComponent | Trashcan component, used for demo purposes. |
|
||||
| app.toolbar.toggleInfoDrawer | ToggleInfoDrawerComponent | The toolbar button component that toggles Info Drawer for the selection. |
|
||||
| app.toolbar.toggleFavorite | ToggleFavoriteComponent | The toolbar button component that toggles Favorite state for the selection. |
|
||||
|
||||
<p class="tip">
|
||||
See [Registration](#registration) section for more details
|
||||
on how to register your own entries to be re-used at runtime.
|
||||
</p>
|
||||
|
||||
Note that custom extensions can also replace any existing component at runtime by a known identifier,
|
||||
besides registering a new one.
|
||||
|
||||
## Actions
|
||||
|
||||
| Name | Description |
|
||||
| --- | --- |
|
||||
| **id** | Unique identifier. |
|
||||
| **type** | Action type, see [Application Actions](#application-actions) for more details. |
|
||||
| *payload* | Action payload, a string containing value or expression. |
|
||||
|
||||
```json
|
||||
{
|
||||
"$schema": "../../../extension.schema.json",
|
||||
"$version": "1.0.0",
|
||||
"$name": "plugin1",
|
||||
|
||||
"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)"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Value expressions
|
||||
|
||||
You can use light-weight expression syntax to provide custom parameters for the action payloads.
|
||||
|
||||
```text
|
||||
$(<expression>)
|
||||
```
|
||||
|
||||
Expressions are valid JavaScript blocks that evaluate to values.
|
||||
|
||||
Examples:
|
||||
|
||||
```text
|
||||
$('hello world') // 'hello world'
|
||||
$('hello' + ', ' + 'world') // 'hello, world'
|
||||
$(1 + 1) // 2
|
||||
$([1, 2, 1 + 2]) // [1, 2, 3]
|
||||
```
|
||||
|
||||
## Application Actions
|
||||
|
||||
Application is using NgRx (Reactive libraries for Angular, inspired by Redux).
|
||||
To get more information on NxRx please refer to the following resources:
|
||||
|
||||
* [Comprehensive Introduction to @ngrx/store](https://gist.github.com/btroncone/a6e4347326749f938510)
|
||||
|
||||
Most of the application features are already exposed in the form of NgRx Actions and corresponding Effects.
|
||||
You can invoke any action via a single `Store` dispatcher, similar to the following:
|
||||
|
||||
```typescript
|
||||
export class MyComponent {
|
||||
|
||||
constructor(private store: Store<AppStore>) {}
|
||||
|
||||
onClick() {
|
||||
this.store.dispatch(new SearchByTermAction('*'));
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
The code above demonstrates a simple 'click' handler that invokes `Search by Term` feature
|
||||
and automatically redirects user to the **Search Results** page.
|
||||
|
||||
Another example demonstrates viewing a node from a custom application service API:
|
||||
|
||||
```typescript
|
||||
export class MyService {
|
||||
|
||||
constructor(private store: Store<AppStore>) {}
|
||||
|
||||
viewFile(node: MinimalNodeEntity) {
|
||||
this.store.dispatch(new ViewFileAction(node));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Using with Extensions
|
||||
|
||||
You can invoke every application action from the extensions, i.e. buttons, menus, etc.
|
||||
|
||||
<p class="tip">
|
||||
Many of the actions take currently selected nodes if no payload provided.
|
||||
That simplifies declaring and invoking actions from the extension files.
|
||||
</p>
|
||||
|
||||
In the example below, we create a new entry to the "NEW" menu dropdown
|
||||
and provide a new `Create Folder` command that invokes the `CREATE_FOLDER` application action.
|
||||
|
||||
```json
|
||||
{
|
||||
"$schema": "../../../extension.schema.json",
|
||||
"$version": "1.0.0",
|
||||
"$name": "plugin1",
|
||||
|
||||
"features": {
|
||||
"create": [
|
||||
{
|
||||
"id": "app.create.folder",
|
||||
"type": "default",
|
||||
"icon": "create_new_folder",
|
||||
"title": "Create Folder",
|
||||
"actions": {
|
||||
"click": "CREATE_FOLDER"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The `CREATE_FOLDER` action will trigger corresponding NgRx Effects to show the dialog
|
||||
and perform document list reload if needed.
|
||||
|
||||
Below is the list of public actions types you can use in the plugin definitions as a reference to the action:
|
||||
|
||||
| Name | Payload | Description |
|
||||
| --- | --- | --- |
|
||||
| SET_CURRENT_FOLDER | Node | Notify components about currently opened folder. |
|
||||
| SET_CURRENT_URL | string | Notify components about current browser URL. |
|
||||
| SET_USER_PROFILE | Person | Assign current user profile. |
|
||||
| TOGGLE_INFO_DRAWER | n/a | Toggle info drawer for the selected node. |
|
||||
| ADD_FAVORITE | MinimalNodeEntity[] | Add nodes (or selection) to favorites. |
|
||||
| REMOVE_FAVORITE | MinimalNodeEntity[] | Removes nodes (or selection) from favorites. |
|
||||
| DELETE_LIBRARY | string | Delete a Library by id. Takes selected node if payload not provided. |
|
||||
| CREATE_LIBRARY | n/a | Invoke a "Create Library" dialog. |
|
||||
| SET_SELECTED_NODES | MinimalNodeEntity[] | Notify components about selected nodes. |
|
||||
| DELETE_NODES | MinimalNodeEntity[] | Delete the nodes (or selection). Supports undo actions. |
|
||||
| UNDO_DELETE_NODES | any[] | Reverts deletion of nodes (or selection). |
|
||||
| RESTORE_DELETED_NODES | MinimalNodeEntity[] | Restores deleted nodes (or selection). Typically used with Trashcan. |
|
||||
| PURGE_DELETED_NODES | MinimalNodeEntity[] | Permanently delete nodes (or selection). Typically used with Trashcan. |
|
||||
| DOWNLOAD_NODES | MinimalNodeEntity[] | Download nodes (or selections). Creates a ZIP archive for folders or multiple items. |
|
||||
| CREATE_FOLDER | string | Invoke a "Create Folder" dialog for the opened folder (or the parent folder id in the payload). |
|
||||
| EDIT_FOLDER | MinimalNodeEntity | Invoke an "Edit Folder" dialog for the node (or selection). |
|
||||
| SHARE_NODE | MinimalNodeEntity | Invoke a "Share" dialog for the node (or selection). |
|
||||
| UNSHARE_NODES | MinimalNodeEntity[] | Remove nodes (or selection) from the shared nodes (does not remove content). |
|
||||
| COPY_NODES | MinimalNodeEntity[] | Invoke a "Copy" dialog for the nodes (or selection). Supports undo actions. |
|
||||
| MOVE_NODES | MinimalNodeEntity[] | Invoke a "Move" dialog for the nodes (or selection). Supports undo actions. |
|
||||
| MANAGE_PERMISSIONS | MinimalNodeEntity | Invoke a "Manage Permissions" dialog for the node (or selection). |
|
||||
| MANAGE_VERSIONS | MinimalNodeEntity | Invoke a "Manage Versions" dialog for the node (or selection). |
|
||||
| NAVIGATE_URL | string | Navigate to a given route URL within the application. |
|
||||
| NAVIGATE_ROUTE | any[] | Navigate to a particular Route (supports parameters) |
|
||||
| NAVIGATE_FOLDER | MinimalNodeEntity | Navigate to a folder based on the Node properties. |
|
||||
| NAVIGATE_PARENT_FOLDER | MinimalNodeEntity | Navigate to a containing folder based on the Node properties. |
|
||||
| SEARCH_BY_TERM | string | Perform a simple search by the term and navigate to Search results. |
|
||||
| SNACKBAR_INFO | string | Show information snackbar with the message provided. |
|
||||
| SNACKBAR_WARNING | string | Show warning snackbar with the message provided. |
|
||||
| SNACKBAR_ERROR | string | Show error snackbar with the message provided. |
|
||||
| UPLOAD_FILES | n/a | Invoke "Upload Files" dialog and upload files to the currently opened folder. |
|
||||
| UPLOAD_FOLDER | n/a | Invoke "Upload Folder" dialog and upload selected folder to the currently opened one. |
|
||||
| VIEW_FILE | MinimalNodeEntity | Preview the file (or selection) in the Viewer. |
|
||||
|
||||
## Rules
|
||||
|
||||
Rules allow evaluating conditions for extension components.
|
||||
For example, you can disable or hide elements based on certain rules.
|
||||
|
||||
Every rule is backed by a condition evaluator.
|
||||
|
||||
```json
|
||||
{
|
||||
"$schema": "../../../extension.schema.json",
|
||||
"$version": "1.0.0",
|
||||
"$name": "plugin1",
|
||||
|
||||
"rules": [
|
||||
{
|
||||
"id": "app.trashcan",
|
||||
"type": "app.navigation.isTrashcan"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Rules can accept other rules as parameters:
|
||||
|
||||
```json
|
||||
{
|
||||
"$schema": "../../../extension.schema.json",
|
||||
"$version": "1.0.0",
|
||||
"$name": "plugin1",
|
||||
|
||||
"rules": [
|
||||
{
|
||||
"id": "app.toolbar.favorite.canAdd",
|
||||
"type": "core.every",
|
||||
"parameters": [
|
||||
{ "type": "rule", "value": "app.selection.canAddFavorite" },
|
||||
{ "type": "rule", "value": "app.navigation.isNotRecentFiles" },
|
||||
{ "type": "rule", "value": "app.navigation.isNotSharedFiles" },
|
||||
{ "type": "rule", "value": "app.navigation.isNotSearchResults" }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
<p class="tip">
|
||||
You can also negate any rule by utilizing a `!` prefix:
|
||||
`!app.navigation.isTrashcan` is the opposite of the `app.navigation.isTrashcan`.
|
||||
</p>
|
||||
|
||||
It is also possible to use inline references to registered evaluators without declaring rules,
|
||||
in case you do not need providing extra parameters, or chaining multiple rules together.
|
||||
|
||||
### Core Evaluators
|
||||
|
||||
You can create new rules by chaining other rules and evaluators.
|
||||
|
||||
| Key | Description |
|
||||
| --- | --- |
|
||||
| core.every | Evaluates to `true` if all chained rules evaluate to `true`. |
|
||||
| core.some | Evaluates to `true` if at least one of the chained rules evaluates to `true`. |
|
||||
| core.not | Evaluates to `true` if all chained rules evaluate to `false`. |
|
||||
|
||||
Below is an example of the composite rule definition that combines the following conditions:
|
||||
|
||||
* user has selected a single file
|
||||
* user is not using **Trashcan** page
|
||||
|
||||
```json
|
||||
{
|
||||
"$schema": "../../../extension.schema.json",
|
||||
"$version": "1.0.0",
|
||||
"$name": "plugin1",
|
||||
|
||||
"rules": [
|
||||
{
|
||||
"id": "app.toolbar.canViewFile",
|
||||
"type": "core.every",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "rule",
|
||||
"value": "app.selection.file"
|
||||
},
|
||||
{
|
||||
"type": "rule",
|
||||
"value": "core.not",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "rule",
|
||||
"value": "app.navigation.isTrashcan"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
You can now declare a toolbar button action that is based on the rule above.
|
||||
|
||||
```json
|
||||
{
|
||||
"$schema": "../../../extension.schema.json",
|
||||
"$version": "1.0.0",
|
||||
"$name": "plugin1",
|
||||
|
||||
"features": {
|
||||
"content": {
|
||||
"actions": [
|
||||
{
|
||||
"id": "app.toolbar.preview",
|
||||
"type": "button",
|
||||
"title": "View File",
|
||||
"icon": "open_in_browser",
|
||||
"actions": {
|
||||
"click": "VIEW_FILE"
|
||||
},
|
||||
"rules": {
|
||||
"visible": "app.toolbar.canViewFile"
|
||||
}
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The button will be visible only when the linked rule evaluates to `true`.
|
||||
|
||||
### Application Evaluators
|
||||
|
||||
| Key | Description |
|
||||
| --- | --- |
|
||||
| app.selection.canDelete | User has permission to delete selected node(s). |
|
||||
| app.selection.canDownload | User can download selected node(s). |
|
||||
| app.selection.notEmpty | At least one node is selected. |
|
||||
| app.selection.canUnshare | User is able to remove selected node(s) from public sharing. |
|
||||
| app.selection.canAddFavorite | User can add selected node(s) to favorites. |
|
||||
| app.selection.canRemoveFavorite | User can remove selected node(s) from favorites. |
|
||||
| app.selection.first.canUpdate | User has permission to update selected node(s). |
|
||||
| app.selection.file | A single File node is selected. |
|
||||
| app.selection.file.canShare | User is able to remove selected file from public sharing. |
|
||||
| app.selection.library | A single Library node is selected. |
|
||||
| app.selection.folder | A single Folder node is selected. |
|
||||
| app.selection.folder.canUpdate | User has permissions to update selected folder. |
|
||||
|
||||
### Navigation Evaluators
|
||||
|
||||
The application exposes a set of navigation-related evaluators
|
||||
to help developers restrict or enable certain actions based on the route or page displayed.
|
||||
|
||||
The negated evaluators are provided just to simplify development,
|
||||
and to avoid having a complex rule trees just to negate the rules,
|
||||
for example mixing `core.every` and `core.not`.
|
||||
|
||||
<p class="tip">
|
||||
You can also negate any rule by utilizing a `!` prefix:
|
||||
`!app.navigation.isTrashcan` is the opposite of the `app.navigation.isTrashcan`.
|
||||
</p>
|
||||
|
||||
| Key | Description |
|
||||
| --- | --- |
|
||||
| app.navigation.folder.canCreate | User can create content in the currently opened folder. |
|
||||
| app.navigation.folder.canUpload | User can upload content to the currently opened folder. |
|
||||
| app.navigation.isTrashcan | User is using **Trashcan** page. |
|
||||
| app.navigation.isNotTrashcan | Current page is not a **Trashcan**. |
|
||||
| app.navigation.isLibraries | User is using **Libraries** page. |
|
||||
| app.navigation.isNotLibraries | Current page is not **Libraries**. |
|
||||
| app.navigation.isSharedFiles | User is using **Shared Files** page. |
|
||||
| app.navigation.isNotSharedFiles | Current page is not **Shared Files**. |
|
||||
| app.navigation.isFavorites | User is using **Favorites** page. |
|
||||
| app.navigation.isNotFavorites | Current page is not **Favorites** |
|
||||
| app.navigation.isRecentFiles | User is using **Recent Files** page. |
|
||||
| app.navigation.isNotRecentFiles | Current page is not **Recent Files**. |
|
||||
| app.navigation.isSearchResults | User is using **Search Results** page. |
|
||||
| app.navigation.isNotSearchResults | Current page is not **Search Results**. |
|
||||
|
||||
<p class="tip">
|
||||
See [Registration](#registration) section for more details
|
||||
on how to register your own entries to be re-used at runtime.
|
||||
</p>
|
||||
|
||||
#### Example
|
||||
|
||||
The rule in the example below evaluates to `true` if all the conditions are met:
|
||||
|
||||
- user has selected node(s)
|
||||
- user is not using **Trashcan** page
|
||||
- user is not using **Libraries** page
|
||||
|
||||
```json
|
||||
{
|
||||
"$schema": "../../../extension.schema.json",
|
||||
"$version": "1.0.0",
|
||||
"$name": "plugin1",
|
||||
|
||||
"rules": [
|
||||
{
|
||||
"id": "app.toolbar.canCopyNode",
|
||||
"type": "core.every",
|
||||
"parameters": [
|
||||
{ "type": "rule", "value": "app.selection.notEmpty" },
|
||||
{ "type": "rule", "value": "app.navigation.isNotTrashcan" },
|
||||
{ "type": "rule", "value": "app.navigation.isNotLibraries" }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Application Features
|
||||
|
||||
### Extending Create Menu
|
||||
|
||||
### Extending Navigation Sidebar
|
||||
|
||||
### Extending Toolbar
|
||||
|
||||
### Extending Context Menu
|
||||
|
||||
### Extending Viewer
|
||||
|
||||
#### Open With actions
|
||||
|
||||
#### Toolbar actions
|
||||
|
||||
## Registration
|
||||
|
||||
You can use `ExtensionService` to register custom components, authentication guards,
|
||||
rule evaluators, etc.
|
||||
|
||||
It is recommended to register custom content during application startup
|
||||
by utilising the `APP_INITIALIZER` injection token that comes with Angular.
|
||||
In that case all plugins will be available right after main application component is ready.
|
||||
|
||||
Update the main application module `app.module.ts`, or create your own module,
|
||||
and use the following snippet to register custom content:
|
||||
|
||||
```typescript
|
||||
export function setupExtensions(extensions: ExtensionService): Function {
|
||||
return () =>
|
||||
new Promise(resolve => {
|
||||
|
||||
extensions.setComponents({
|
||||
'plugin1.components.my': MyComponent1,
|
||||
'plugin1.layouts.my': MyLayout
|
||||
});
|
||||
|
||||
extensions.setAuthGuards({
|
||||
'plugin.auth': MyAuthGuard
|
||||
});
|
||||
|
||||
extensions.setEvaluators({
|
||||
'plugin1.rules.custom1': MyCustom1Evaluator,
|
||||
'plugin1.rules.custom2': MyCustom2Evaluator
|
||||
});
|
||||
|
||||
resolve(true);
|
||||
});
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
declarations: [ MyComponent1, MyLayout ],
|
||||
entryComponents: [ MyComponent1, MyLayout ],
|
||||
providers: [
|
||||
{
|
||||
provide: APP_INITIALIZER,
|
||||
useFactory: setupExtensions,
|
||||
deps: [ ExtensionService ],
|
||||
multi: true
|
||||
}
|
||||
]
|
||||
})
|
||||
export class MyExtensionModule {}
|
||||
```
|
||||
|
||||
<p class="warning">
|
||||
According to Angular rules, all components that are created dynamically at runtime
|
||||
need to be registered within the `entryComponents` section of the NgModule.
|
||||
</p>
|
||||
|
||||
The registration API is not limited to the custom content only.
|
||||
You can replace any existing entries by replacing the values from your module.
|
||||
|
||||
## Creating custom evaluator
|
||||
|
||||
Rule evaluators are plain JavaScript (or TypeScript) functions
|
||||
that take `RuleContext` reference and an optional list of `RuleParameter` instances.
|
||||
|
||||
Application provides a special [RuleEvaluator](https://github.com/Alfresco/alfresco-content-app/blob/master/src/app/extensions/rule.extensions.ts#L30) type alias for evaluator functions:
|
||||
|
||||
```typescript
|
||||
export type RuleEvaluator = (context: RuleContext, ...args: any[]) => boolean;
|
||||
```
|
||||
|
||||
Create a function that is going to check if user has selected one or multiple nodes.
|
||||
|
||||
```typescript
|
||||
export function hasSelection(
|
||||
context: RuleContext,
|
||||
...args: RuleParameter[]
|
||||
): boolean {
|
||||
return !context.selection.isEmpty;
|
||||
}
|
||||
```
|
||||
|
||||
The `context` is a reference to a special instance of the [RuleContext](https://github.com/Alfresco/alfresco-content-app/blob/master/src/app/extensions/rule.extensions.ts#L32) type,
|
||||
that provides each evaluator access to runtime entities.
|
||||
|
||||
```typescript
|
||||
export interface RuleContext {
|
||||
selection: SelectionState;
|
||||
navigation: NavigationState;
|
||||
permissions: NodePermissions;
|
||||
|
||||
getEvaluator(key: string): RuleEvaluator;
|
||||
}
|
||||
```
|
||||
|
||||
The `SelectionState` interface exposes information about global selection state:
|
||||
|
||||
```typescript
|
||||
export interface SelectionState {
|
||||
count: number;
|
||||
nodes: MinimalNodeEntity[];
|
||||
libraries: SiteEntry[];
|
||||
isEmpty: boolean;
|
||||
first?: MinimalNodeEntity;
|
||||
last?: MinimalNodeEntity;
|
||||
folder?: MinimalNodeEntity;
|
||||
file?: MinimalNodeEntity;
|
||||
library?: SiteEntry;
|
||||
}
|
||||
```
|
||||
|
||||
Next, register the function you have created earlier with the `ExtensionService` and give it a unique identifier:
|
||||
|
||||
```typescript
|
||||
extensions.setEvaluators({
|
||||
'plugin1.rules.hasSelection': hasSelection
|
||||
});
|
||||
```
|
||||
|
||||
Now, the `plugin1.rules.hasSelection` evaluator can be used as an inline rule reference,
|
||||
or part of the composite rule like `core.every`.
|
||||
|
||||
|
||||
<p class="tip">
|
||||
See [Registration](#registration) section for more details
|
||||
on how to register your own entries to be re-used at runtime.
|
||||
</p>
|
||||
|
||||
## Tutorials
|
||||
|
||||
### Custom route with parameters
|
||||
|
||||
In this tutorial, we are going to implement the following features:
|
||||
|
||||
* update the **Trashcan** component to receive and log route parameters
|
||||
* create a new route that points to the **Trashcan** component and uses main layout
|
||||
* create an action reference that allows redirecting to the new route
|
||||
* create a button in the **New** menu that invokes an action
|
||||
|
||||
Update `src/app/components/trashcan/trashcan.component.ts` and append the following code to the `ngOnInit` body:
|
||||
|
||||
```typescript
|
||||
import { ActivatedRoute, Params } from '@angular/router';
|
||||
|
||||
@Component({...})
|
||||
export class TrashcanComponent {
|
||||
|
||||
constructor(
|
||||
// ...
|
||||
private route: ActivatedRoute
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
// ...
|
||||
|
||||
this.route.params.subscribe(({ nodeId }: Params) => {
|
||||
console.log('node: ', nodeId);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
The code above logs current route parameters to the browser console
|
||||
and is a simple proof the integration works as expected.
|
||||
|
||||
Next, add a new route definition as in the example below:
|
||||
|
||||
```json
|
||||
{
|
||||
"$schema": "../../../extension.schema.json",
|
||||
"$version": "1.0.0",
|
||||
"$name": "plugin1",
|
||||
|
||||
"routes": [
|
||||
{
|
||||
"id": "custom.routes.trashcan",
|
||||
"path": "ext/trashcan/:nodeId",
|
||||
"component": "app.components.trashcan",
|
||||
"layout": "app.layout.main",
|
||||
"auth": [ "app.auth" ]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
The template above creates a new route reference with the id `custom.routes.trashcan` that points to the `ext/trashcan/` route and accepts the `nodeId` parameter.
|
||||
|
||||
Also, we are going to use default application layout (`app.layout.main`)
|
||||
and authentication guards (`app.auth`).
|
||||
|
||||
Next, create an action reference for the `NAVIGATE_ROUTE` application action
|
||||
and pass route parameters: `/ext/trashcan` for the path, and `10` for the `nodeId` value.
|
||||
|
||||
```json
|
||||
{
|
||||
"$schema": "../../../extension.schema.json",
|
||||
"$version": "1.0.0",
|
||||
"$name": "plugin1",
|
||||
|
||||
"routes": [...],
|
||||
|
||||
"actions": [
|
||||
{
|
||||
"id": "custom.actions.trashcan",
|
||||
"type": "NAVIGATE_ROUTE",
|
||||
"payload": "$(['/ext/trashcan', '10'])"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Finally, declare a new menu item for the `NEW` button and use the `custom.actions.trashcan` action created above.
|
||||
|
||||
```json
|
||||
{
|
||||
"$schema": "../../../extension.schema.json",
|
||||
"$version": "1.0.0",
|
||||
"$name": "plugin1",
|
||||
|
||||
"routes": [...],
|
||||
"actions": [...],
|
||||
|
||||
"features": {
|
||||
"create": [
|
||||
{
|
||||
"id": "custom.create.trashcan",
|
||||
"type": "default",
|
||||
"icon": "build",
|
||||
"title": "Custom trashcan route",
|
||||
"actions": {
|
||||
"click": "custom.actions.trashcan"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Now, if you run the application, you should see a new menu item called "Custom Trashcan Route" in the "NEW" dropdown.
|
||||
Upon clicking this item you should navigate to the `/ext/trashcan/10` route containing a **Trashcan** component.
|
||||
|
||||
Check the browser console output and ensure you have the following output:
|
||||
|
||||
```text
|
||||
node: 10
|
||||
```
|
||||
|
||||
You have successfully created a new menu button that invokes your custom action
|
||||
and redirects you to the extra application route.
|
@ -1,3 +1,7 @@
|
||||
---
|
||||
title: Getting Started
|
||||
---
|
||||
|
||||
# Getting Started
|
||||
|
||||
## Prerequisites
|
||||
|
@ -1,3 +1,7 @@
|
||||
---
|
||||
title: Get Help
|
||||
---
|
||||
|
||||
# Where to get help
|
||||
|
||||
There are several ways to get help with building applications using the Alfresco Application Development Framework:
|
||||
|
@ -11,6 +11,7 @@
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script src="https://unpkg.com/docute@3/dist/docute.js"></script>
|
||||
<script src="https://unpkg.com/prismjs/components/prism-typescript.js"></script>
|
||||
<script>
|
||||
docute.init({
|
||||
nav: [
|
||||
@ -22,6 +23,10 @@
|
||||
title: 'Getting Started',
|
||||
path: 'getting-started'
|
||||
},
|
||||
{
|
||||
title: 'Extending',
|
||||
path: 'extending'
|
||||
},
|
||||
{
|
||||
title: 'Get Help',
|
||||
path: 'help'
|
||||
|
@ -315,6 +315,12 @@
|
||||
"type": "array",
|
||||
"items": { "$ref": "#/definitions/contentActionRef" },
|
||||
"minItems": 1
|
||||
},
|
||||
"contextActions": {
|
||||
"description": "Content actions (toolbar, context menus, etc.)",
|
||||
"type": "array",
|
||||
"items": { "$ref": "#/definitions/contentActionRef" },
|
||||
"minItems": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -36,15 +36,12 @@ import { ActivatedRoute, NavigationEnd, Router } from '@angular/router';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { ExtensionService } from './extensions/extension.service';
|
||||
import {
|
||||
SetAppNameAction,
|
||||
SetHeaderColorAction,
|
||||
SetLanguagePickerAction,
|
||||
SetLogoPathAction,
|
||||
SetSharedUrlAction,
|
||||
SnackbarErrorAction,
|
||||
SetCurrentUrlAction
|
||||
SetCurrentUrlAction,
|
||||
SetInitialStateAction
|
||||
} from './store/actions';
|
||||
import { AppStore } from './store/states/app.state';
|
||||
import { AppStore, AppState, INITIAL_APP_STATE } from './store/states/app.state';
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
@ -103,24 +100,18 @@ export class AppComponent implements OnInit {
|
||||
}
|
||||
|
||||
private loadAppSettings() {
|
||||
const headerColor = this.config.get<string>('headerColor');
|
||||
if (headerColor) {
|
||||
this.store.dispatch(new SetHeaderColorAction(headerColor));
|
||||
}
|
||||
const appName = this.config.get<string>('application.name');
|
||||
if (appName) {
|
||||
this.store.dispatch(new SetAppNameAction(appName));
|
||||
}
|
||||
const logoPath = this.config.get<string>('application.logo');
|
||||
if (logoPath) {
|
||||
this.store.dispatch(new SetLogoPathAction(logoPath));
|
||||
}
|
||||
const languagePicker = this.config.get<boolean>('languagePicker');
|
||||
this.store.dispatch(new SetLanguagePickerAction(languagePicker));
|
||||
|
||||
const sharedPreviewUrl =
|
||||
this.config.get<string>('ecmHost') + '/#/preview/s/';
|
||||
this.store.dispatch(new SetSharedUrlAction(sharedPreviewUrl));
|
||||
const state: AppState = {
|
||||
... INITIAL_APP_STATE,
|
||||
appName: this.config.get<string>('application.name'),
|
||||
headerColor: this.config.get<string>('headerColor'),
|
||||
logoPath: this.config.get<string>('application.logo'),
|
||||
sharedUrl: this.config.get<string>('ecmHost') + '/#/preview/s/'
|
||||
};
|
||||
|
||||
this.store.dispatch(new SetInitialStateAction(state));
|
||||
}
|
||||
|
||||
onFileUploadedError(error: FileUploadErrorEvent) {
|
||||
|
@ -18,6 +18,7 @@
|
||||
<adf-layout-header
|
||||
[logo]="logo$ | async"
|
||||
[color]="'accent'"
|
||||
[title]="appName$ | async"
|
||||
(clicked)="toggleMenu($event)">
|
||||
|
||||
<div class="adf-toolbar--spacer"></div>
|
||||
|
@ -29,7 +29,7 @@ import { Resolve, Router } from '@angular/router';
|
||||
import { Person } from 'alfresco-js-api';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { AppStore } from '../store/states/app.state';
|
||||
import { SetUserAction } from '../store/actions';
|
||||
import { SetUserProfileAction } from '../store/actions';
|
||||
import { ContentApiService } from './content-api.service';
|
||||
|
||||
@Injectable()
|
||||
@ -44,7 +44,7 @@ export class ProfileResolver implements Resolve<Person> {
|
||||
return new Observable(observer => {
|
||||
this.contentApi.getPerson('-me-').subscribe(
|
||||
person => {
|
||||
this.store.dispatch(new SetUserAction(person.entry));
|
||||
this.store.dispatch(new SetUserProfileAction(person.entry));
|
||||
observer.next(person.entry);
|
||||
observer.complete();
|
||||
},
|
||||
|
@ -30,6 +30,5 @@ export * from './actions/snackbar.actions';
|
||||
export * from './actions/router.actions';
|
||||
export * from './actions/viewer.actions';
|
||||
export * from './actions/search.actions';
|
||||
export * from './actions/user.actions';
|
||||
export * from './actions/library.actions';
|
||||
export * from './actions/upload.actions';
|
||||
|
@ -24,31 +24,20 @@
|
||||
*/
|
||||
|
||||
import { Action } from '@ngrx/store';
|
||||
import { Node } from 'alfresco-js-api';
|
||||
import { Node, Person } from 'alfresco-js-api';
|
||||
import { AppState } from '../states';
|
||||
|
||||
export const SET_APP_NAME = 'SET_APP_NAME';
|
||||
export const SET_HEADER_COLOR = 'SET_HEADER_COLOR';
|
||||
export const SET_LOGO_PATH = 'SET_LOGO_PATH';
|
||||
export const SET_INITIAL_STATE = 'SET_INITIAL_STATE';
|
||||
export const SET_LANGUAGE_PICKER = 'SET_LANGUAGE_PICKER';
|
||||
export const SET_SHARED_URL = 'SET_SHARED_URL';
|
||||
export const SET_CURRENT_FOLDER = 'SET_CURRENT_FOLDER';
|
||||
export const SET_CURRENT_URL = 'SET_CURRENT_URL';
|
||||
export const SET_USER_PROFILE = 'SET_USER_PROFILE';
|
||||
export const TOGGLE_INFO_DRAWER = 'TOGGLE_INFO_DRAWER';
|
||||
export const TOGGLE_DOCUMENT_DISPLAY_MODE = 'TOGGLE_DOCUMENT_DISPLAY_MODE';
|
||||
|
||||
export class SetAppNameAction implements Action {
|
||||
readonly type = SET_APP_NAME;
|
||||
constructor(public payload: string) {}
|
||||
}
|
||||
|
||||
export class SetHeaderColorAction implements Action {
|
||||
readonly type = SET_HEADER_COLOR;
|
||||
constructor(public payload: string) {}
|
||||
}
|
||||
|
||||
export class SetLogoPathAction implements Action {
|
||||
readonly type = SET_LOGO_PATH;
|
||||
constructor(public payload: string) {}
|
||||
export class SetInitialStateAction implements Action {
|
||||
readonly type = SET_INITIAL_STATE;
|
||||
constructor(public payload: AppState) {}
|
||||
}
|
||||
|
||||
export class SetLanguagePickerAction implements Action {
|
||||
@ -56,11 +45,6 @@ export class SetLanguagePickerAction implements Action {
|
||||
constructor(public payload: boolean) {}
|
||||
}
|
||||
|
||||
export class SetSharedUrlAction implements Action {
|
||||
readonly type = SET_SHARED_URL;
|
||||
constructor(public payload: string) {}
|
||||
}
|
||||
|
||||
export class SetCurrentFolderAction implements Action {
|
||||
readonly type = SET_CURRENT_FOLDER;
|
||||
constructor(public payload: Node) {}
|
||||
@ -71,6 +55,11 @@ export class SetCurrentUrlAction implements Action {
|
||||
constructor(public payload: string) {}
|
||||
}
|
||||
|
||||
export class SetUserProfileAction implements Action {
|
||||
readonly type = SET_USER_PROFILE;
|
||||
constructor(public payload: Person) { }
|
||||
}
|
||||
|
||||
export class ToggleInfoDrawerAction implements Action {
|
||||
readonly type = TOGGLE_INFO_DRAWER;
|
||||
constructor(public payload?: 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 { Action } from '@ngrx/store';
|
||||
import { Person } from 'alfresco-js-api';
|
||||
|
||||
export const SET_USER = 'SET_USER';
|
||||
|
||||
export class SetUserAction implements Action {
|
||||
readonly type = SET_USER;
|
||||
constructor(public payload: Person) { }
|
||||
}
|
@ -26,20 +26,12 @@
|
||||
import { Action } from '@ngrx/store';
|
||||
import { AppState, INITIAL_APP_STATE } from '../states/app.state';
|
||||
import {
|
||||
SET_HEADER_COLOR,
|
||||
SetHeaderColorAction,
|
||||
SET_APP_NAME,
|
||||
SetAppNameAction,
|
||||
SET_LOGO_PATH,
|
||||
SetLogoPathAction,
|
||||
SET_SELECTED_NODES,
|
||||
SetSelectedNodesAction,
|
||||
SET_USER,
|
||||
SetUserAction,
|
||||
SET_USER_PROFILE,
|
||||
SetUserProfileAction,
|
||||
SET_LANGUAGE_PICKER,
|
||||
SetLanguagePickerAction,
|
||||
SET_SHARED_URL,
|
||||
SetSharedUrlAction,
|
||||
SET_CURRENT_FOLDER,
|
||||
SetCurrentFolderAction,
|
||||
SET_CURRENT_URL,
|
||||
@ -49,7 +41,9 @@ import {
|
||||
TOGGLE_INFO_DRAWER,
|
||||
ToggleInfoDrawerAction,
|
||||
TOGGLE_DOCUMENT_DISPLAY_MODE,
|
||||
ToggleDocumentDisplayMode
|
||||
ToggleDocumentDisplayMode,
|
||||
SET_INITIAL_STATE,
|
||||
SetInitialStateAction
|
||||
} from '../actions/app.actions';
|
||||
|
||||
export function appReducer(
|
||||
@ -59,31 +53,22 @@ export function appReducer(
|
||||
let newState: AppState;
|
||||
|
||||
switch (action.type) {
|
||||
case SET_APP_NAME:
|
||||
newState = updateAppName(state, <SetAppNameAction>action);
|
||||
break;
|
||||
case SET_HEADER_COLOR:
|
||||
newState = updateHeaderColor(state, <SetHeaderColorAction>action);
|
||||
break;
|
||||
case SET_LOGO_PATH:
|
||||
newState = updateLogoPath(state, <SetLogoPathAction>action);
|
||||
case SET_INITIAL_STATE:
|
||||
newState = Object.assign({}, (<SetInitialStateAction>action).payload);
|
||||
break;
|
||||
case SET_SELECTED_NODES:
|
||||
newState = updateSelectedNodes(state, <SetSelectedNodesAction>(
|
||||
action
|
||||
));
|
||||
break;
|
||||
case SET_USER:
|
||||
newState = updateUser(state, <SetUserAction>action);
|
||||
case SET_USER_PROFILE:
|
||||
newState = updateUser(state, <SetUserProfileAction>action);
|
||||
break;
|
||||
case SET_LANGUAGE_PICKER:
|
||||
newState = updateLanguagePicker(state, <SetLanguagePickerAction>(
|
||||
action
|
||||
));
|
||||
break;
|
||||
case SET_SHARED_URL:
|
||||
newState = updateSharedUrl(state, <SetSharedUrlAction>action);
|
||||
break;
|
||||
case SET_CURRENT_FOLDER:
|
||||
newState = updateCurrentFolder(state, <SetCurrentFolderAction>(
|
||||
action
|
||||
@ -107,15 +92,6 @@ export function appReducer(
|
||||
return newState;
|
||||
}
|
||||
|
||||
function updateHeaderColor(
|
||||
state: AppState,
|
||||
action: SetHeaderColorAction
|
||||
): AppState {
|
||||
const newState = Object.assign({}, state);
|
||||
newState.headerColor = action.payload;
|
||||
return newState;
|
||||
}
|
||||
|
||||
function updateLanguagePicker(
|
||||
state: AppState,
|
||||
action: SetLanguagePickerAction
|
||||
@ -125,28 +101,7 @@ function updateLanguagePicker(
|
||||
return newState;
|
||||
}
|
||||
|
||||
function updateAppName(state: AppState, action: SetAppNameAction): AppState {
|
||||
const newState = Object.assign({}, state);
|
||||
newState.appName = action.payload;
|
||||
return newState;
|
||||
}
|
||||
|
||||
function updateLogoPath(state: AppState, action: SetLogoPathAction): AppState {
|
||||
const newState = Object.assign({}, state);
|
||||
newState.logoPath = action.payload;
|
||||
return newState;
|
||||
}
|
||||
|
||||
function updateSharedUrl(
|
||||
state: AppState,
|
||||
action: SetSharedUrlAction
|
||||
): AppState {
|
||||
const newState = Object.assign({}, state);
|
||||
newState.sharedUrl = action.payload;
|
||||
return newState;
|
||||
}
|
||||
|
||||
function updateUser(state: AppState, action: SetUserAction): AppState {
|
||||
function updateUser(state: AppState, action: SetUserProfileAction): AppState {
|
||||
const newState = Object.assign({}, state);
|
||||
const user = action.payload;
|
||||
|
||||
|
@ -136,6 +136,7 @@
|
||||
{
|
||||
"id": "app.create.folder",
|
||||
"type": "default",
|
||||
"order": 100,
|
||||
"icon": "create_new_folder",
|
||||
"title": "APP.NEW_MENU.MENU_ITEMS.CREATE_FOLDER",
|
||||
"description": "APP.NEW_MENU.TOOLTIPS.CREATE_FOLDER",
|
||||
@ -150,6 +151,7 @@
|
||||
{
|
||||
"id": "app.create.uploadFile",
|
||||
"type": "default",
|
||||
"order": 200,
|
||||
"icon": "file_upload",
|
||||
"title": "APP.NEW_MENU.MENU_ITEMS.UPLOAD_FILE",
|
||||
"description": "APP.NEW_MENU.TOOLTIPS.UPLOAD_FILES",
|
||||
@ -164,6 +166,7 @@
|
||||
{
|
||||
"id": "app.create.uploadFolder",
|
||||
"type": "default",
|
||||
"order": 300,
|
||||
"icon": "file_upload",
|
||||
"title": "APP.NEW_MENU.MENU_ITEMS.UPLOAD_FOLDER",
|
||||
"description": "APP.NEW_MENU.TOOLTIPS.UPLOAD_FOLDERS",
|
||||
@ -182,6 +185,7 @@
|
||||
"items": [
|
||||
{
|
||||
"id": "app.navbar.personalFiles",
|
||||
"order": 100,
|
||||
"icon": "folder",
|
||||
"title": "APP.BROWSE.PERSONAL.SIDENAV_LINK.LABEL",
|
||||
"description": "APP.BROWSE.PERSONAL.SIDENAV_LINK.TOOLTIP",
|
||||
@ -189,6 +193,7 @@
|
||||
},
|
||||
{
|
||||
"id": "app.navbar.libraries",
|
||||
"order": 200,
|
||||
"icon": "group_work",
|
||||
"title": "APP.BROWSE.LIBRARIES.SIDENAV_LINK.LABEL",
|
||||
"description": "APP.BROWSE.LIBRARIES.SIDENAV_LINK.TOOLTIP",
|
||||
@ -201,6 +206,7 @@
|
||||
"items": [
|
||||
{
|
||||
"id": "app.navbar.shared",
|
||||
"order": 100,
|
||||
"icon": "people",
|
||||
"title": "APP.BROWSE.SHARED.SIDENAV_LINK.LABEL",
|
||||
"description": "APP.BROWSE.SHARED.SIDENAV_LINK.TOOLTIP",
|
||||
@ -208,6 +214,7 @@
|
||||
},
|
||||
{
|
||||
"id": "app.navbar.recentFiles",
|
||||
"order": 200,
|
||||
"icon": "schedule",
|
||||
"title": "APP.BROWSE.RECENT.SIDENAV_LINK.LABEL",
|
||||
"description": "APP.BROWSE.RECENT.SIDENAV_LINK.TOOLTIP",
|
||||
@ -215,6 +222,7 @@
|
||||
},
|
||||
{
|
||||
"id": "app.navbar.favorites",
|
||||
"order": 300,
|
||||
"icon": "star",
|
||||
"title": "APP.BROWSE.FAVORITES.SIDENAV_LINK.LABEL",
|
||||
"description": "APP.BROWSE.FAVORITES.SIDENAV_LINK.TOOLTIP",
|
||||
@ -222,6 +230,7 @@
|
||||
},
|
||||
{
|
||||
"id": "app.navbar.trashcan",
|
||||
"order": 400,
|
||||
"icon": "delete",
|
||||
"title": "APP.BROWSE.TRASHCAN.SIDENAV_LINK.LABEL",
|
||||
"description": "APP.BROWSE.TRASHCAN.SIDENAV_LINK.TOOLTIP",
|
||||
@ -235,7 +244,7 @@
|
||||
{
|
||||
"id": "app.toolbar.preview",
|
||||
"type": "button",
|
||||
"order": 15,
|
||||
"order": 100,
|
||||
"title": "APP.ACTIONS.VIEW",
|
||||
"icon": "open_in_browser",
|
||||
"actions": {
|
||||
@ -248,7 +257,7 @@
|
||||
{
|
||||
"id": "app.toolbar.download",
|
||||
"type": "button",
|
||||
"order": 20,
|
||||
"order": 200,
|
||||
"title": "APP.ACTIONS.DOWNLOAD",
|
||||
"icon": "get_app",
|
||||
"actions": {
|
||||
@ -261,7 +270,7 @@
|
||||
{
|
||||
"id": "app.toolbar.editFolder",
|
||||
"type": "button",
|
||||
"order": 30,
|
||||
"order": 300,
|
||||
"title": "APP.ACTIONS.EDIT",
|
||||
"icon": "create",
|
||||
"actions": {
|
||||
@ -274,6 +283,7 @@
|
||||
{
|
||||
"id": "app.toolbar.purgeDeletedNodes",
|
||||
"type": "button",
|
||||
"order": 400,
|
||||
"title": "APP.ACTIONS.DELETE_PERMANENT",
|
||||
"icon": "delete_forever",
|
||||
"actions": {
|
||||
@ -286,6 +296,7 @@
|
||||
{
|
||||
"id": "app.toolbar.restoreDeletedNodes",
|
||||
"type": "button",
|
||||
"order": 500,
|
||||
"title": "APP.ACTIONS.RESTORE",
|
||||
"icon": "restore",
|
||||
"actions": {
|
||||
@ -298,6 +309,7 @@
|
||||
{
|
||||
"id": "app.toolbar.createLibrary",
|
||||
"type": "button",
|
||||
"order": 600,
|
||||
"title": "Create Library",
|
||||
"icon": "create_new_folder",
|
||||
"actions": {
|
||||
@ -310,6 +322,7 @@
|
||||
{
|
||||
"id": "app.toolbar.info",
|
||||
"type": "custom",
|
||||
"order": 700,
|
||||
"component": "app.toolbar.toggleInfoDrawer",
|
||||
"rules": {
|
||||
"visible": "app.toolbar.info"
|
||||
@ -318,6 +331,7 @@
|
||||
{
|
||||
"id": "app.toolbar.more",
|
||||
"type": "menu",
|
||||
"order": 10000,
|
||||
"icon": "more_vert",
|
||||
"title": "APP.ACTIONS.MORE",
|
||||
"children": [
|
||||
@ -325,6 +339,7 @@
|
||||
"id": "app.toolbar.favorite",
|
||||
"comment": "workaround for Recent Files and Search API issue",
|
||||
"type": "custom",
|
||||
"order": 100,
|
||||
"component": "app.toolbar.toggleFavorite",
|
||||
"rules": {
|
||||
"visible": "app.toolbar.favorite.canToggle"
|
||||
@ -333,6 +348,7 @@
|
||||
{
|
||||
"id": "app.toolbar.favorite.add",
|
||||
"type": "button",
|
||||
"order": 200,
|
||||
"title": "APP.ACTIONS.FAVORITE",
|
||||
"icon": "star_border",
|
||||
"actions": {
|
||||
@ -345,6 +361,7 @@
|
||||
{
|
||||
"id": "app.toolbar.favorite.remove",
|
||||
"type": "button",
|
||||
"order": 300,
|
||||
"title": "APP.ACTIONS.FAVORITE",
|
||||
"icon": "star",
|
||||
"actions": {
|
||||
@ -357,6 +374,7 @@
|
||||
{
|
||||
"id": "app.toolbar.copy",
|
||||
"type": "button",
|
||||
"order": 400,
|
||||
"title": "APP.ACTIONS.COPY",
|
||||
"icon": "content_copy",
|
||||
"actions": {
|
||||
@ -369,6 +387,7 @@
|
||||
{
|
||||
"id": "app.toolbar.move",
|
||||
"type": "button",
|
||||
"order": 500,
|
||||
"title": "APP.ACTIONS.MOVE",
|
||||
"icon": "library_books",
|
||||
"actions": {
|
||||
@ -381,6 +400,7 @@
|
||||
{
|
||||
"id": "app.toolbar.share",
|
||||
"type": "button",
|
||||
"order": 600,
|
||||
"title": "APP.ACTIONS.SHARE",
|
||||
"icon": "share",
|
||||
"actions": {
|
||||
@ -393,6 +413,7 @@
|
||||
{
|
||||
"id": "app.toolbar.unshare",
|
||||
"type": "button",
|
||||
"order": 700,
|
||||
"title": "APP.ACTIONS.UNSHARE",
|
||||
"icon": "stop_screen_share",
|
||||
"actions": {
|
||||
@ -405,6 +426,7 @@
|
||||
{
|
||||
"id": "app.toolbar.delete",
|
||||
"type": "button",
|
||||
"order": 800,
|
||||
"title": "APP.ACTIONS.DELETE",
|
||||
"icon": "delete",
|
||||
"actions": {
|
||||
@ -417,6 +439,7 @@
|
||||
{
|
||||
"id": "app.toolbar.deleteLibrary",
|
||||
"type": "button",
|
||||
"order": 900,
|
||||
"title": "APP.ACTIONS.DELETE",
|
||||
"icon": "delete",
|
||||
"actions": {
|
||||
@ -429,6 +452,7 @@
|
||||
{
|
||||
"id": "app.toolbar.versions",
|
||||
"type": "button",
|
||||
"order": 1000,
|
||||
"title": "APP.ACTIONS.VERSIONS",
|
||||
"icon": "history",
|
||||
"actions": {
|
||||
@ -441,6 +465,7 @@
|
||||
{
|
||||
"id": "app.toolbar.permissions",
|
||||
"type": "button",
|
||||
"order": 1100,
|
||||
"title": "APP.ACTIONS.PERMISSIONS",
|
||||
"icon": "settings_input_component",
|
||||
"actions": {
|
||||
@ -457,7 +482,7 @@
|
||||
{
|
||||
"id": "app.contextmenu.download",
|
||||
"type": "button",
|
||||
"order": 10,
|
||||
"order": 100,
|
||||
"title": "APP.ACTIONS.DOWNLOAD",
|
||||
"icon": "get_app",
|
||||
"actions": {
|
||||
@ -470,7 +495,7 @@
|
||||
{
|
||||
"id": "app.contextmenu.preview",
|
||||
"type": "button",
|
||||
"order": 15,
|
||||
"order": 200,
|
||||
"title": "APP.ACTIONS.VIEW",
|
||||
"icon": "open_in_browser",
|
||||
"actions": {
|
||||
@ -483,7 +508,7 @@
|
||||
{
|
||||
"id": "app.contextmenu.editFolder",
|
||||
"type": "button",
|
||||
"order": 20,
|
||||
"order": 300,
|
||||
"title": "APP.ACTIONS.EDIT",
|
||||
"icon": "create",
|
||||
"actions": {
|
||||
@ -497,7 +522,7 @@
|
||||
"id": "app.contextmenu.share",
|
||||
"type": "button",
|
||||
"title": "APP.ACTIONS.SHARE",
|
||||
"order": 25,
|
||||
"order": 400,
|
||||
"icon": "share",
|
||||
"actions": {
|
||||
"click": "SHARE_NODE"
|
||||
@ -510,7 +535,7 @@
|
||||
"id": "app.contextmenu.favorite.add",
|
||||
"type": "button",
|
||||
"title": "APP.ACTIONS.FAVORITE",
|
||||
"order": 30,
|
||||
"order": 500,
|
||||
"icon": "star_border",
|
||||
"actions": {
|
||||
"click": "ADD_FAVORITE"
|
||||
@ -523,7 +548,7 @@
|
||||
"id": "app.contextmenu.favorite.remove",
|
||||
"type": "button",
|
||||
"title": "APP.ACTIONS.FAVORITE",
|
||||
"order": 30,
|
||||
"order": 600,
|
||||
"icon": "star",
|
||||
"actions": {
|
||||
"click": "REMOVE_FAVORITE"
|
||||
@ -536,7 +561,7 @@
|
||||
"id": "app.contextmenu.copy",
|
||||
"type": "button",
|
||||
"title": "APP.ACTIONS.COPY",
|
||||
"order": 35,
|
||||
"order": 700,
|
||||
"icon": "content_copy",
|
||||
"actions": {
|
||||
"click": "COPY_NODES"
|
||||
@ -549,7 +574,7 @@
|
||||
"id": "app.contextmenu.move",
|
||||
"type": "button",
|
||||
"title": "APP.ACTIONS.MOVE",
|
||||
"order": 40,
|
||||
"order": 800,
|
||||
"icon": "library_books",
|
||||
"actions": {
|
||||
"click": "MOVE_NODES"
|
||||
@ -562,7 +587,7 @@
|
||||
"id": "app.contextmenu.delete",
|
||||
"type": "button",
|
||||
"title": "APP.ACTIONS.DELETE",
|
||||
"order": 45,
|
||||
"order": 900,
|
||||
"icon": "delete",
|
||||
"actions": {
|
||||
"click": "DELETE_NODES"
|
||||
@ -575,7 +600,7 @@
|
||||
"id": "app.contextmenu.versions",
|
||||
"type": "button",
|
||||
"title": "APP.ACTIONS.VERSIONS",
|
||||
"order": 50,
|
||||
"order": 1000,
|
||||
"icon": "history",
|
||||
"actions": {
|
||||
"click": "MANAGE_VERSIONS"
|
||||
@ -589,7 +614,7 @@
|
||||
"type": "button",
|
||||
"title": "APP.ACTIONS.PERMISSIONS",
|
||||
"icon": "settings_input_component",
|
||||
"order": 55,
|
||||
"order": 1100,
|
||||
"actions": {
|
||||
"click": "MANAGE_PERMISSIONS"
|
||||
},
|
||||
@ -600,6 +625,7 @@
|
||||
{
|
||||
"id": "app.contextmenu.purgeDeletedNodes",
|
||||
"type": "button",
|
||||
"order": 1200,
|
||||
"title": "APP.ACTIONS.DELETE_PERMANENT",
|
||||
"icon": "delete_forever",
|
||||
"actions": {
|
||||
@ -612,6 +638,7 @@
|
||||
{
|
||||
"id": "app.contextmenu.restoreDeletedNodes",
|
||||
"type": "button",
|
||||
"order": 1300,
|
||||
"title": "APP.ACTIONS.RESTORE",
|
||||
"icon": "restore",
|
||||
"actions": {
|
||||
@ -628,6 +655,7 @@
|
||||
{
|
||||
"id": "app.viewer.favorite.add",
|
||||
"type": "button",
|
||||
"order": 100,
|
||||
"title": "APP.ACTIONS.FAVORITE",
|
||||
"icon": "star_border",
|
||||
"actions": {
|
||||
@ -640,6 +668,7 @@
|
||||
{
|
||||
"id": "app.viewer.favorite.remove",
|
||||
"type": "button",
|
||||
"order": 200,
|
||||
"title": "APP.ACTIONS.FAVORITE",
|
||||
"icon": "star",
|
||||
"actions": {
|
||||
@ -652,6 +681,7 @@
|
||||
{
|
||||
"id": "app.viewer.share",
|
||||
"type": "button",
|
||||
"order": 300,
|
||||
"title": "APP.ACTIONS.SHARE",
|
||||
"icon": "share",
|
||||
"actions": {
|
||||
@ -664,6 +694,7 @@
|
||||
{
|
||||
"id": "app.viewer.copy",
|
||||
"type": "button",
|
||||
"order": 400,
|
||||
"title": "APP.ACTIONS.COPY",
|
||||
"icon": "content_copy",
|
||||
"actions": {
|
||||
@ -676,6 +707,7 @@
|
||||
{
|
||||
"id": "app.viewer.move",
|
||||
"type": "button",
|
||||
"order": 500,
|
||||
"title": "APP.ACTIONS.MOVE",
|
||||
"icon": "library_books",
|
||||
"actions": {
|
||||
@ -688,6 +720,7 @@
|
||||
{
|
||||
"id": "app.viewer.delete",
|
||||
"type": "button",
|
||||
"order": 600,
|
||||
"title": "APP.ACTIONS.DELETE",
|
||||
"icon": "delete",
|
||||
"actions": {
|
||||
@ -700,6 +733,7 @@
|
||||
{
|
||||
"id": "app.viewer.versions",
|
||||
"type": "button",
|
||||
"order": 700,
|
||||
"title": "APP.ACTIONS.VERSIONS",
|
||||
"icon": "history",
|
||||
"actions": {
|
||||
@ -712,6 +746,7 @@
|
||||
{
|
||||
"id": "app.viewer.permissions",
|
||||
"type": "button",
|
||||
"order": 800,
|
||||
"title": "APP.ACTIONS.PERMISSIONS",
|
||||
"icon": "settings_input_component",
|
||||
"actions": {
|
||||
|
Loading…
x
Reference in New Issue
Block a user