reusable extensibility lib ()

reusable extensibility lib (part 1)
This commit is contained in:
Denys Vuika 2018-08-29 16:38:44 +01:00 committed by GitHub
parent 091e0d3e3f
commit c916ab4cd1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
88 changed files with 1312 additions and 1123 deletions
angular.json
docs
e2e/components/info-drawer
package-lock.jsonpackage.json
projects
src/app
tsconfig.json

@ -239,6 +239,46 @@
}
}
}
},
"adf-extensions": {
"root": "projects/adf-extensions",
"sourceRoot": "projects/adf-extensions/src",
"projectType": "library",
"prefix": "lib",
"architect": {
"build": {
"builder": "@angular-devkit/build-ng-packagr:build",
"options": {
"tsConfig": "projects/adf-extensions/tsconfig.lib.json",
"project": "projects/adf-extensions/ng-package.json"
},
"configurations": {
"production": {
"project": "projects/adf-extensions/ng-package.prod.json"
}
}
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"main": "projects/adf-extensions/src/test.ts",
"tsConfig": "projects/adf-extensions/tsconfig.spec.json",
"karmaConfig": "projects/adf-extensions/karma.conf.js"
}
},
"lint": {
"builder": "@angular-devkit/build-angular:tslint",
"options": {
"tsConfig": [
"projects/adf-extensions/tsconfig.lib.json",
"projects/adf-extensions/tsconfig.spec.json"
],
"exclude": [
"**/node_modules/**"
]
}
}
}
}
},
"defaultProject": "alfresco-content-app",

@ -1119,51 +1119,44 @@ As with other content actions, custom plugins can disable, update or extend `Ope
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.
It is recommended to register custom content from within the module constructor.
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);
});
}
import { ExtensionsModule, ExtensionService } from '@alfresco/adf-extensions';
@NgModule({
imports: [ ExtensionsModule.forChild() ]
declarations: [ MyComponent1, MyLayout ],
entryComponents: [ MyComponent1, MyLayout ],
providers: [
{
provide: APP_INITIALIZER,
useFactory: setupExtensions,
deps: [ ExtensionService ],
multi: true
}
]
entryComponents: [ MyComponent1, MyLayout ]
})
export class MyExtensionModule {}
export class MyExtensionModule {
constructor(extensions: ExtensionService) {
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
});
}
}
```
Use `ExtensionsModule.forChild()` when importing into the child modules,
and `ExtensionsModule.forRoot()` for the main application module.
<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.

@ -38,7 +38,7 @@ export class InfoDrawer extends Component {
tabLabel: '.mat-tab-label-content',
tabActiveLabel: '.mat-tab-label-active',
activeTabContent: '.mat-tab-body-active .mat-tab-body-content app-dynamic-tab',
activeTabContent: '.mat-tab-body-active .mat-tab-body-content adf-dynamic-tab',
next: '.mat-tab-header-pagination-after .mat-tab-header-pagination-chevron',
previous: '.mat-tab-header-pagination-before .mat-tab-header-pagination-chevron'
};

160
package-lock.json generated

@ -977,19 +977,19 @@
}
},
"@angular-devkit/schematics": {
"version": "0.7.4",
"resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-0.7.4.tgz",
"integrity": "sha512-Gkm2mBMm6a0mNKqZsAcS42VaO7zNSIXhIKbMyZQIcQ1ZMwbsx+Rs0dliQwFVfEVCNGc1pjh0Idc5O5V/g/N5Fw==",
"version": "0.7.5",
"resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-0.7.5.tgz",
"integrity": "sha512-E7HkQeJawUskf2gPnogMc+cTdjJ2Iv3QEZOgprh/ExEmBYByWkGDRX5fQOuy8wME8VZqUBvQACZaVkEredn5EA==",
"dev": true,
"requires": {
"@angular-devkit/core": "0.7.4",
"@angular-devkit/core": "0.7.5",
"rxjs": "^6.0.0"
},
"dependencies": {
"@angular-devkit/core": {
"version": "0.7.4",
"resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-0.7.4.tgz",
"integrity": "sha512-Blh44vzZVzE8B9xIwjRoo7hXPGSDdlrrax0rntvt3DDGVTjsSGm43qT95aDmXiwJruOCJNC5DsaP3+tTAkAyQQ==",
"version": "0.7.5",
"resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-0.7.5.tgz",
"integrity": "sha512-r99BZvvuNAqSRm05jXfx0sb3Ip0zvHPtAM6NReXzWPoqaVFpjVUdj/CKA+9HWG/Zt9meG9pEQt/HKK8UXaZDVA==",
"dev": true,
"requires": {
"ajv": "~6.4.0",
@ -1384,32 +1384,32 @@
}
},
"@angular/animations": {
"version": "6.1.3",
"resolved": "https://registry.npmjs.org/@angular/animations/-/animations-6.1.3.tgz",
"integrity": "sha512-uLKq+bdfo+/jLW/C6lkUVsB7m+e8j18MjZGHlphI07jW6KvutX+AXdPUI/RMkkWRjZp11aF727PAQ6y3DyqB+Q==",
"version": "6.1.4",
"resolved": "https://registry.npmjs.org/@angular/animations/-/animations-6.1.4.tgz",
"integrity": "sha512-R+akCyIneyqJ5wAf9VaymvxbxM4Iw3YsUdylO9rrr9wAUhzmzWhCSGK9bncwL4+d5rbd0n1u+8A8Gm0dZe1P1A==",
"requires": {
"tslib": "^1.9.0"
}
},
"@angular/cdk": {
"version": "6.4.5",
"resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-6.4.5.tgz",
"integrity": "sha512-bbz8lHzY1NXPqrvHcZTOOD6+BiDNPKXC00xwzls62NPmiueBaWGwQdk7s3sa/0kCyq1ZKrPD3KQIDMyytxlzzw==",
"version": "6.4.6",
"resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-6.4.6.tgz",
"integrity": "sha512-XKSoeSP4htpOq2UIyF9KDhIJtEQ3wyhZRjDxyRSNmJ9OsuRZxJAGCAzOX5RpMszOyFZgUNVycOi+1lHDe0JrZg==",
"requires": {
"tslib": "^1.7.1"
}
},
"@angular/cli": {
"version": "6.1.4",
"resolved": "https://registry.npmjs.org/@angular/cli/-/cli-6.1.4.tgz",
"integrity": "sha512-9lkFGZGV38p5p/s4p91hc8r4z1WaUKgige8pGNBIuW93esnkjoll5/NKx4siU2wsaPd/4njaP5f2iuUuyYNtbg==",
"version": "6.1.5",
"resolved": "https://registry.npmjs.org/@angular/cli/-/cli-6.1.5.tgz",
"integrity": "sha512-QNVUSC8mPdiaxubneqNZISy+wec3gwbKoXjcaQ9/45baOnp662j2iJXwiMh6Atn0YUM4u1iUsz1uHyARMtgZmw==",
"dev": true,
"requires": {
"@angular-devkit/architect": "0.7.4",
"@angular-devkit/core": "0.7.4",
"@angular-devkit/schematics": "0.7.4",
"@schematics/angular": "0.7.4",
"@schematics/update": "0.7.4",
"@angular-devkit/architect": "0.7.5",
"@angular-devkit/core": "0.7.5",
"@angular-devkit/schematics": "0.7.5",
"@schematics/angular": "0.7.5",
"@schematics/update": "0.7.5",
"opn": "^5.3.0",
"rxjs": "^6.0.0",
"semver": "^5.1.0",
@ -1418,19 +1418,19 @@
},
"dependencies": {
"@angular-devkit/architect": {
"version": "0.7.4",
"resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.7.4.tgz",
"integrity": "sha512-qcxLtA5XhUCqNyyMOD+s7oIVywNnhUNE1qoopnm6MN0FJ1n7iQMU5TPZBTiXDWQVnbGODObi7tGo7gFnEBML5Q==",
"version": "0.7.5",
"resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.7.5.tgz",
"integrity": "sha512-zwCpGdx3JDE+Y+LiWh9ErRX+fpFPTRHtEd2PDJmfQsdlIWfjxSR5U9vi3+bSRW2n6IFiH2GCYMS31R64rfMwbg==",
"dev": true,
"requires": {
"@angular-devkit/core": "0.7.4",
"@angular-devkit/core": "0.7.5",
"rxjs": "^6.0.0"
}
},
"@angular-devkit/core": {
"version": "0.7.4",
"resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-0.7.4.tgz",
"integrity": "sha512-Blh44vzZVzE8B9xIwjRoo7hXPGSDdlrrax0rntvt3DDGVTjsSGm43qT95aDmXiwJruOCJNC5DsaP3+tTAkAyQQ==",
"version": "0.7.5",
"resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-0.7.5.tgz",
"integrity": "sha512-r99BZvvuNAqSRm05jXfx0sb3Ip0zvHPtAM6NReXzWPoqaVFpjVUdj/CKA+9HWG/Zt9meG9pEQt/HKK8UXaZDVA==",
"dev": true,
"requires": {
"ajv": "~6.4.0",
@ -1825,25 +1825,25 @@
}
},
"@angular/common": {
"version": "6.1.3",
"resolved": "https://registry.npmjs.org/@angular/common/-/common-6.1.3.tgz",
"integrity": "sha512-1V3pDdEty4hYsdpePlcNUE8rF1w1NP8LW6Q1ICNk86MI472W1U9ZTDFwCYcQYDiYMtzBrgXcnE1q6u1rqTdygQ==",
"version": "6.1.4",
"resolved": "https://registry.npmjs.org/@angular/common/-/common-6.1.4.tgz",
"integrity": "sha512-vpedSD5Rbuj9kLq9W/aeQBVugplimTJPPeuW/zUXHWVOOOk4Y7KBw5g4JdYw2ocSoY3z+dRl/6fR0JTi9+muaA==",
"requires": {
"tslib": "^1.9.0"
}
},
"@angular/compiler": {
"version": "6.1.3",
"resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-6.1.3.tgz",
"integrity": "sha512-r8Nv4wo2QNmsGs/sjxcR6z6YG17TfaAAxAl/6yk3z3DNdDM76cBwTi9hurXlmZKPU6/2YFI+ZwvhBrGwaOZd5Q==",
"version": "6.1.4",
"resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-6.1.4.tgz",
"integrity": "sha512-HjSK9Jjx6f1jpXy2TALKp2ByAXycZKKD39M9K2g+feTTrpUtd3iDEDLG4S/yok9qs4e1k3L8fxr/qBngQuv23A==",
"requires": {
"tslib": "^1.9.0"
}
},
"@angular/compiler-cli": {
"version": "6.1.3",
"resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-6.1.3.tgz",
"integrity": "sha512-YSVoLMaCF0mvt0CPRGldKMCJDGju82XhsupbDOZsMddVG6hRl8dRbem7OChJp+8GV+UNsfbP/X2o4ERLROHXRA==",
"version": "6.1.4",
"resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-6.1.4.tgz",
"integrity": "sha512-ueTe5THcPIKjXOb1+LvqEqh35QPihEGObvJIpudMTqkJHguOr2WXKbbgxzF8QWuIBhOHR9fjtaSgNY5Kk2mfTg==",
"dev": true,
"requires": {
"chokidar": "^1.4.2",
@ -1861,9 +1861,9 @@
}
},
"@angular/core": {
"version": "6.1.3",
"resolved": "https://registry.npmjs.org/@angular/core/-/core-6.1.3.tgz",
"integrity": "sha512-pqRfQphqIEExhDWM3RRusvLY6gFN0zdITC7TqQy6Wof6VKgWOvfHiHPbiamw4kpEzflMekuOeNm0s6h6hIUnWA==",
"version": "6.1.4",
"resolved": "https://registry.npmjs.org/@angular/core/-/core-6.1.4.tgz",
"integrity": "sha512-8r2LpD4MR0hAYjWkElD/I6iXcugMK/HrpdtopDlRcxW2f6XuMN4mu8eS3g2fu72PwdGhtMcqDDFlbeJ8k599lw==",
"requires": {
"tslib": "^1.9.0"
}
@ -1877,31 +1877,31 @@
}
},
"@angular/forms": {
"version": "6.1.3",
"resolved": "https://registry.npmjs.org/@angular/forms/-/forms-6.1.3.tgz",
"integrity": "sha512-8pHVQ97S0W//GsYsGuGV/SyzkMAF/bcE1AqDg4HXNU3mKOl5lthfM+PxHz23Cdzkg/GVInwRNxUl2Q+6lb8zuw==",
"version": "6.1.4",
"resolved": "https://registry.npmjs.org/@angular/forms/-/forms-6.1.4.tgz",
"integrity": "sha512-O/rYF36zM15fbXPv4Tj7NlYiCazko6+Eb4o9Ls0nJXMOxt8pRwdYjgGMaOtYprtbH89YlnmJU/gav5Z58JG7sw==",
"requires": {
"tslib": "^1.9.0"
}
},
"@angular/http": {
"version": "6.1.3",
"resolved": "https://registry.npmjs.org/@angular/http/-/http-6.1.3.tgz",
"integrity": "sha512-1IZ+8i1gIoPDD57Yv/8fEjzdFfWM7nY5JZGyjWUkWxq/A19zjkR4C1nRCI6KiWHk0QFx2e1Sl6OFqyiphwhalw==",
"version": "6.1.4",
"resolved": "https://registry.npmjs.org/@angular/http/-/http-6.1.4.tgz",
"integrity": "sha512-HD3+ouMAqsgA6hFUS8AQDVlZTbkuNuyomGY1I85IpqXmO7GneszN6y5xIf8XO5ke4UYgvk5UhRrKJ9UG9VtUOw==",
"requires": {
"tslib": "^1.9.0"
}
},
"@angular/language-service": {
"version": "6.1.3",
"resolved": "https://registry.npmjs.org/@angular/language-service/-/language-service-6.1.3.tgz",
"integrity": "sha512-cAIptulTWn1LGFTmRzBbNY4axhnyQl/YaocX0JqutmI+eaWTofSiDwHVB2spYSDTWhy9fg+OMK2KmhghpZmc1g==",
"version": "6.1.4",
"resolved": "https://registry.npmjs.org/@angular/language-service/-/language-service-6.1.4.tgz",
"integrity": "sha512-kvnAphJ7VrKJpm1gN3sFjGp/5gQxu/FAw03yD1f1z+C+aHrNKoxaS9pp9NdOIT/DWlxR/BcEDF4gzMNMTJ9/wA==",
"dev": true
},
"@angular/material": {
"version": "6.4.5",
"resolved": "https://registry.npmjs.org/@angular/material/-/material-6.4.5.tgz",
"integrity": "sha512-3wDRfGqlRSo3CBA1XuPXSz7zAwZF5kotXEgbZhIAocv+nsXa73DyPNceAQ++Pu7rFR3ipg6McyggP0OCOgv7NQ==",
"version": "6.4.6",
"resolved": "https://registry.npmjs.org/@angular/material/-/material-6.4.6.tgz",
"integrity": "sha512-SUSg9MhLv4IZj6Nh8qoCLDImZugCQ+Jvvt+/cDIaTn6TrT6ZenDHc6jOhbGFesU6FuBDBFIXMiuBPD9kBr7vaA==",
"requires": {
"parse5": "^5.0.0",
"tslib": "^1.7.1"
@ -1916,33 +1916,33 @@
}
},
"@angular/material-moment-adapter": {
"version": "6.4.5",
"resolved": "https://registry.npmjs.org/@angular/material-moment-adapter/-/material-moment-adapter-6.4.5.tgz",
"integrity": "sha512-xgPMnntOfwTX4Uuz7g/GQ4IcRn6tiSJCGSikwTK5yRJjUEGguHmBch57q2Xjr/A+WfmGYZnVfzy3V9t/XsNKqA==",
"version": "6.4.6",
"resolved": "https://registry.npmjs.org/@angular/material-moment-adapter/-/material-moment-adapter-6.4.6.tgz",
"integrity": "sha512-Bvj/zwtEjF1bqOF1z/M0VcWYOwlRjmCBjvPVmNZ1Bxf7HE8yyRf8B+AAv46sn/EhhAv/eMNU9bzFcxrR/vEHAg==",
"requires": {
"tslib": "^1.7.1"
}
},
"@angular/platform-browser": {
"version": "6.1.3",
"resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-6.1.3.tgz",
"integrity": "sha512-ml844B5g8UtQaOK2QCdTRSTRHWa1elTbg4ph65GTs0lNMH/tLFNn8tvcPOfMh21ZV3fh6yqA6a9wMjSMTVSiWg==",
"version": "6.1.4",
"resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-6.1.4.tgz",
"integrity": "sha512-46UPtC360+3E4eeOQk45qp+r+d0Qnsujyot+XtVKQmTSHTInDlwfIGA9TBTw8GyAs3O65i80LRkDHFz9BM2pmw==",
"requires": {
"tslib": "^1.9.0"
}
},
"@angular/platform-browser-dynamic": {
"version": "6.1.3",
"resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-6.1.3.tgz",
"integrity": "sha512-6b4Y8f4yhuUJrAVsHyYWRbRSaIQUzZWSbYvWLVZeXnt4HKzRhX+zO5yTA/eXCXAce+fRIU239FbkeyqtpuogPQ==",
"version": "6.1.4",
"resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-6.1.4.tgz",
"integrity": "sha512-a/WVCsAa0qdtLNuK6h8q+nwtXmJqOlc+dJrBK3vz1umhc80nFZeaUPMTnkRwphk6WVE8xHvjb41PZsI8jF9CBQ==",
"requires": {
"tslib": "^1.9.0"
}
},
"@angular/router": {
"version": "6.1.3",
"resolved": "https://registry.npmjs.org/@angular/router/-/router-6.1.3.tgz",
"integrity": "sha512-6sb1yH/a2CACcbXZ6d+PYWGgwV3BXupCQXBd8Q5h7o3/r5zz18VATdRqBWtSIOmFr11wekJrGGRMxDCpTDlXvg==",
"version": "6.1.4",
"resolved": "https://registry.npmjs.org/@angular/router/-/router-6.1.4.tgz",
"integrity": "sha512-ANdJmpPkr4BMW3/ixJ/qSbsQk4CwR3BPDJp3Iua/xLy7i+9h0bcs11Lpdyo5U3esPYpcpc6TE3ofptyD6xpyGg==",
"requires": {
"tslib": "^1.9.0"
}
@ -2018,20 +2018,20 @@
}
},
"@schematics/angular": {
"version": "0.7.4",
"resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-0.7.4.tgz",
"integrity": "sha512-/KJH5fPv+40VdadWmVnXR2GlvFZKYH01eYw7XOi77gXM896gk2tGlWmm+6RjUaVyec49ivusmmhRJEjiTyA7NA==",
"version": "0.7.5",
"resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-0.7.5.tgz",
"integrity": "sha512-NrtvFwHCoWon8KInsvA1jdPu4pVJGa8GAWM/jqnE7HpwPwM7hMML08lV0P8r3NX5t2/i0CKvfp4AAEr5MXorEQ==",
"dev": true,
"requires": {
"@angular-devkit/core": "0.7.4",
"@angular-devkit/schematics": "0.7.4",
"@angular-devkit/core": "0.7.5",
"@angular-devkit/schematics": "0.7.5",
"typescript": ">=2.6.2 <2.10"
},
"dependencies": {
"@angular-devkit/core": {
"version": "0.7.4",
"resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-0.7.4.tgz",
"integrity": "sha512-Blh44vzZVzE8B9xIwjRoo7hXPGSDdlrrax0rntvt3DDGVTjsSGm43qT95aDmXiwJruOCJNC5DsaP3+tTAkAyQQ==",
"version": "0.7.5",
"resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-0.7.5.tgz",
"integrity": "sha512-r99BZvvuNAqSRm05jXfx0sb3Ip0zvHPtAM6NReXzWPoqaVFpjVUdj/CKA+9HWG/Zt9meG9pEQt/HKK8UXaZDVA==",
"dev": true,
"requires": {
"ajv": "~6.4.0",
@ -2426,13 +2426,13 @@
}
},
"@schematics/update": {
"version": "0.7.4",
"resolved": "https://registry.npmjs.org/@schematics/update/-/update-0.7.4.tgz",
"integrity": "sha512-HC8AgVrwF4fmtFVQ2y7wheB7k8sqbLNBFmTH51C6+5XJegSFnT5AjkRoWkbHS5Wuifxma1wMjJKqZZrH6vgWFQ==",
"version": "0.7.5",
"resolved": "https://registry.npmjs.org/@schematics/update/-/update-0.7.5.tgz",
"integrity": "sha512-pwNkXGtlzyCV6tsTPe8AgUuMCkmubcz94zgL6pSMdEe122yXBcKnr/PKqG9QzD/gGwmOcHUE9EWcuRtU5kdFpA==",
"dev": true,
"requires": {
"@angular-devkit/core": "0.7.4",
"@angular-devkit/schematics": "0.7.4",
"@angular-devkit/core": "0.7.5",
"@angular-devkit/schematics": "0.7.5",
"npm-registry-client": "^8.5.1",
"rxjs": "^6.0.0",
"semver": "^5.3.0",
@ -2440,9 +2440,9 @@
},
"dependencies": {
"@angular-devkit/core": {
"version": "0.7.4",
"resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-0.7.4.tgz",
"integrity": "sha512-Blh44vzZVzE8B9xIwjRoo7hXPGSDdlrrax0rntvt3DDGVTjsSGm43qT95aDmXiwJruOCJNC5DsaP3+tTAkAyQQ==",
"version": "0.7.5",
"resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-0.7.5.tgz",
"integrity": "sha512-r99BZvvuNAqSRm05jXfx0sb3Ip0zvHPtAM6NReXzWPoqaVFpjVUdj/CKA+9HWG/Zt9meG9pEQt/HKK8UXaZDVA==",
"dev": true,
"requires": {
"ajv": "~6.4.0",

@ -4,12 +4,14 @@
"license": "LGPL-3.0",
"scripts": {
"ng": "ng",
"start": "npm run server-versions && ng build aca-dev-tools && ng serve --open",
"start": "npm run server-versions && npm run build.libs.dev && ng serve --open",
"start:prod": "npm run server-versions && ng serve --prod --open",
"build": "npm run server-versions && ng build aca-dev-tools --prod && ng build --prod",
"build:dev": "npm run server-versions && ng build aca-dev-tools && ng build",
"test": "ng test alfresco-content-app --code-coverage",
"test:ci": "ng test alfresco-content-app --code-coverage --watch=false && cat ./coverage/lcov.info | ./node_modules/.bin/codacy-coverage && rm -rf ./coverage",
"build.libs.dev": "ng build adf-extensions && ng build aca-dev-tools",
"build.libs.prod": "ng build adf-extensions --prod && ng build aca-dev-tools --prod",
"build": "npm run server-versions && npm run build.libs.prod && ng build --prod",
"build:dev": "npm run server-versions && npm run build.libs.dev && ng build",
"test": "npm run build.libs.dev && ng test alfresco-content-app --code-coverage",
"test:ci": "npm run build.libs.dev && ng test alfresco-content-app --code-coverage --watch=false && cat ./coverage/lcov.info | ./node_modules/.bin/codacy-coverage && rm -rf ./coverage",
"lint": "ng lint",
"server-versions": "rimraf ./src/versions.json && npm list --depth=0 --json=true --prod=true > ./src/versions.json || exit 0",
"_e2e": "ng e2e",
@ -24,19 +26,19 @@
"dependencies": {
"@alfresco/adf-content-services": "2.5.0",
"@alfresco/adf-core": "2.5.0",
"@angular/animations": "6.1.3",
"@angular/cdk": "^6.4.5",
"@angular/common": "6.1.3",
"@angular/compiler": "6.1.3",
"@angular/core": "6.1.3",
"@angular/animations": "6.1.4",
"@angular/cdk": "^6.4.6",
"@angular/common": "6.1.4",
"@angular/compiler": "6.1.4",
"@angular/core": "6.1.4",
"@angular/flex-layout": "^6.0.0-beta.17",
"@angular/forms": "6.1.3",
"@angular/http": "6.1.3",
"@angular/material": "^6.4.5",
"@angular/material-moment-adapter": "^6.4.5",
"@angular/platform-browser": "6.1.3",
"@angular/platform-browser-dynamic": "6.1.3",
"@angular/router": "6.1.3",
"@angular/forms": "6.1.4",
"@angular/http": "6.1.4",
"@angular/material": "^6.4.6",
"@angular/material-moment-adapter": "^6.4.6",
"@angular/platform-browser": "6.1.4",
"@angular/platform-browser-dynamic": "6.1.4",
"@angular/router": "6.1.4",
"@mat-datetimepicker/core": "^2.0.1",
"@mat-datetimepicker/moment": "^2.0.1",
"@ngrx/effects": "^6.1.0",
@ -60,9 +62,9 @@
"devDependencies": {
"@angular-devkit/build-angular": "~0.7.4",
"@angular-devkit/build-ng-packagr": "~0.7.4",
"@angular/cli": "^6.1.4",
"@angular/compiler-cli": "6.1.3",
"@angular/language-service": "6.1.3",
"@angular/cli": "^6.1.5",
"@angular/compiler-cli": "6.1.4",
"@angular/language-service": "6.1.4",
"@types/jasmine": "^2.5.53",
"@types/jasminewd2": "^2.0.2",
"@types/node": "9.3.0",

@ -4,6 +4,7 @@ import { FlexLayoutModule } from '@angular/flex-layout';
import { AcaDevToolsComponent } from './aca-dev-tools.component';
import { CoreModule } from '@alfresco/adf-core';
import { ContentModule } from '@alfresco/adf-content-services';
import { ExtensionService } from '@alfresco/adf-extensions';
@NgModule({
imports: [
@ -16,4 +17,10 @@ import { ContentModule } from '@alfresco/adf-content-services';
exports: [AcaDevToolsComponent],
entryComponents: [AcaDevToolsComponent]
})
export class AcaDevToolsModule {}
export class AcaDevToolsModule {
constructor(extensions: ExtensionService) {
extensions.setComponents({
'app.dev.tools.component': AcaDevToolsComponent
});
}
}

@ -1,15 +0,0 @@
import { TestBed, inject } from '@angular/core/testing';
import { AcaDevToolsService } from './aca-dev-tools.service';
describe('AcaDevToolsService', () => {
beforeEach(() => {
TestBed.configureTestingModule({
providers: [AcaDevToolsService]
});
});
it('should be created', inject([AcaDevToolsService], (service: AcaDevToolsService) => {
expect(service).toBeTruthy();
}));
});

@ -1,9 +0,0 @@
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class AcaDevToolsService {
constructor() { }
}

@ -2,6 +2,5 @@
* Public API Surface of aca-dev-tools
*/
export * from './lib/aca-dev-tools.service';
export * from './lib/aca-dev-tools.component';
export * from './lib/aca-dev-tools.module';

@ -0,0 +1,31 @@
// Karma configuration file, see link for more information
// https://karma-runner.github.io/1.0/config/configuration-file.html
module.exports = function (config) {
config.set({
basePath: '',
frameworks: ['jasmine', '@angular-devkit/build-angular'],
plugins: [
require('karma-jasmine'),
require('karma-chrome-launcher'),
require('karma-jasmine-html-reporter'),
require('karma-coverage-istanbul-reporter'),
require('@angular-devkit/build-angular/plugins/karma')
],
client: {
clearContext: false // leave Jasmine Spec Runner output visible in browser
},
coverageIstanbulReporter: {
dir: require('path').join(__dirname, '../../coverage'),
reports: ['html', 'lcovonly'],
fixWebpackSourcePaths: true
},
reporters: ['progress', 'kjhtml'],
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
autoWatch: true,
browsers: ['Chrome'],
singleRun: false
});
};

@ -0,0 +1,9 @@
{
"$schema": "../../node_modules/ng-packagr/ng-package.schema.json",
"dest": "../../dist/@alfresco/adf-extensions",
"deleteDestPath": false,
"lib": {
"languageLevel": ["dom", "es2017"],
"entryFile": "src/public_api.ts"
}
}

@ -0,0 +1,8 @@
{
"$schema": "../../node_modules/ng-packagr/ng-package.schema.json",
"dest": "../../dist/@alfresco/adf-extensions",
"lib": {
"languageLevel": ["dom", "es2017"],
"entryFile": "src/public_api.ts"
}
}

@ -0,0 +1,10 @@
{
"name": "@alfresco/adf-extensions",
"version": "0.0.1",
"peerDependencies": {
"@angular/common": "^6.0.0",
"@angular/core": "^6.0.0",
"@angular/http": "^6.1.4",
"alfresco-js-api": "^2.5.0"
}
}

@ -0,0 +1,67 @@
/*!
* @license
* Copyright 2016 - 2018 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {
Component,
Input,
ComponentRef,
OnInit,
ComponentFactoryResolver,
ViewChild,
ViewContainerRef,
OnDestroy
} from '@angular/core';
import { ExtensionService } from '../../services/extension.service';
@Component({
selector: 'adf-dynamic-component',
template: `<div #content></div>`
})
export class DynamicExtensionComponent implements OnInit, OnDestroy {
@ViewChild('content', { read: ViewContainerRef })
content: ViewContainerRef;
@Input() id: string;
private componentRef: ComponentRef<any>;
constructor(
private extensions: ExtensionService,
private componentFactoryResolver: ComponentFactoryResolver
) {}
ngOnInit() {
const componentType = this.extensions.getComponentById(this.id);
if (componentType) {
const factory = this.componentFactoryResolver.resolveComponentFactory(
componentType
);
if (factory) {
this.content.clear();
this.componentRef = this.content.createComponent(factory, 0);
// this.setupWidget(this.componentRef);
}
}
}
ngOnDestroy() {
if (this.componentRef) {
this.componentRef.destroy();
this.componentRef = null;
}
}
}

@ -36,10 +36,10 @@ import {
SimpleChanges
} from '@angular/core';
import { MinimalNodeEntryEntity } from 'alfresco-js-api';
import { ExtensionService } from '../../../extensions/extension.service';
import { ExtensionService } from '../../services/extension.service';
@Component({
selector: 'app-dynamic-tab',
selector: 'adf-dynamic-tab',
template: `<div #content></div>`
})
export class DynamicTabComponent implements OnInit, OnChanges, OnDestroy {

@ -0,0 +1,51 @@
/*!
* @license
* Copyright 2016 - 2018 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { ExtensionElement } from './extension-element';
export enum ContentActionType {
default = 'default',
button = 'button',
separator = 'separator',
menu = 'menu',
custom = 'custom'
}
export interface ContentActionRef extends ExtensionElement {
type: ContentActionType;
title?: string;
description?: string;
icon?: string;
children?: Array<ContentActionRef>;
component?: string;
actions?: {
click?: string;
[key: string]: string;
};
rules?: {
enabled?: string;
visible?: string;
[key: string]: string;
};
}
export interface ActionRef {
id: string;
type: string;
payload?: string;
}

@ -0,0 +1,23 @@
/*!
* @license
* Copyright 2016 - 2018 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export interface ExtensionElement {
id: string;
order?: number;
disabled?: boolean;
}

@ -1,26 +1,18 @@
/*!
* @license
* Alfresco Example Content Application
* Copyright 2016 - 2018 Alfresco Software, Ltd.
*
* Copyright (C) 2005 - 2018 Alfresco Software Limited
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* 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:
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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/>.
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { ContentActionRef, ContentActionType } from './action.extensions';

@ -0,0 +1,35 @@
/*!
* @license
* Copyright 2016 - 2018 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { RouteRef } from './routing.extensions';
import { RuleRef } from './rule.extensions';
import { ActionRef } from './action.extensions';
export interface ExtensionConfig {
$name: string;
$version: string;
$description?: string;
$references?: Array<string>;
rules?: Array<RuleRef>;
routes?: Array<RouteRef>;
actions?: Array<ActionRef>;
features?: {
[key: string]: any;
};
}

@ -0,0 +1,31 @@
/*!
* @license
* Copyright 2016 - 2018 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { ExtensionElement } from './extension-element';
export interface NavBarGroupRef extends ExtensionElement {
items: Array<NavBarLinkRef>;
}
export interface NavBarLinkRef extends ExtensionElement {
icon: string;
title: string;
route: string;
url?: string; // evaluated at runtime based on route ref
description?: string;
}

@ -0,0 +1,20 @@
/*!
* @license
* Copyright 2016 - 2018 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export interface NodePermissions {
check(source: any, permissions: string[], options?: any): boolean;
}

@ -0,0 +1,26 @@
/*!
* @license
* Copyright 2016 - 2018 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export interface RouteRef {
id: string;
path: string;
component: string;
layout?: string;
auth?: string[];
data?: { [key: string]: string };
}

@ -0,0 +1,44 @@
/*!
* @license
* Copyright 2016 - 2018 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { SelectionState } from '../store/states/selection.state';
import { NavigationState } from '../store/states/navigation.state';
import { NodePermissions } from './permission.extensions';
import { ProfileState } from '../store/states/profile.state';
export type RuleEvaluator = (context: RuleContext, ...args: any[]) => boolean;
export interface RuleContext {
selection: SelectionState;
navigation: NavigationState;
profile: ProfileState;
permissions: NodePermissions;
getEvaluator(key: string): RuleEvaluator;
}
export class RuleRef {
type: string;
id?: string;
parameters?: Array<RuleParameter>;
}
export interface RuleParameter {
type: string;
value: any;
parameters?: Array<RuleParameter>;
}

@ -0,0 +1,29 @@
/*!
* @license
* Copyright 2016 - 2018 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { ExtensionElement } from './extension-element';
export interface SidebarTabRef extends ExtensionElement {
title: string;
component: string;
icon?: string;
rules?: {
visible?: string;
[key: string]: string;
};
}

@ -0,0 +1,23 @@
/*!
* @license
* Copyright 2016 - 2018 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { ExtensionElement } from './extension-element';
export interface ViewerExtensionRef extends ExtensionElement {
fileExtension: string;
component: string;
}

@ -0,0 +1,63 @@
/*!
* @license
* Copyright 2016 - 2018 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { RuleContext, RuleParameter } from '../config/rule.extensions';
export function not(context: RuleContext, ...args: RuleParameter[]): boolean {
if (!args || args.length === 0) {
return false;
}
return args
.every(arg => {
const evaluator = context.getEvaluator(arg.value);
if (!evaluator) {
console.warn('evaluator not found: ' + arg.value);
}
return !evaluator(context, ...(arg.parameters || []));
});
}
export function every(context: RuleContext, ...args: RuleParameter[]): boolean {
if (!args || args.length === 0) {
return false;
}
return args
.every(arg => {
const evaluator = context.getEvaluator(arg.value);
if (!evaluator) {
console.warn('evaluator not found: ' + arg.value);
}
return evaluator(context, ...(arg.parameters || []));
});
}
export function some(context: RuleContext, ...args: RuleParameter[]): boolean {
if (!args || args.length === 0) {
return false;
}
return args
.some(arg => {
const evaluator = context.getEvaluator(arg.value);
if (!evaluator) {
console.warn('evaluator not found: ' + arg.value);
}
return evaluator(context, ...(arg.parameters || []));
});
}

@ -0,0 +1,42 @@
/*!
* @license
* Copyright 2016 - 2018 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { NgModule, ModuleWithProviders } from '@angular/core';
import { ExtensionLoaderService } from './services/extension-loader.service';
import { ExtensionService } from './services/extension.service';
import { DynamicExtensionComponent } from './components/dynamic-component/dynamic.component';
import { DynamicTabComponent } from './components/dynamic-tab/dynamic-tab.component';
@NgModule({
imports: [],
declarations: [DynamicExtensionComponent, DynamicTabComponent],
exports: [DynamicExtensionComponent, DynamicTabComponent]
})
export class ExtensionsModule {
static forRoot(): ModuleWithProviders {
return {
ngModule: ExtensionsModule,
providers: [ExtensionLoaderService, ExtensionService]
};
}
static forChild(): ModuleWithProviders {
return {
ngModule: ExtensionsModule
};
}
}

@ -1,38 +1,32 @@
/*!
* @license
* Alfresco Example Content Application
* Copyright 2016 - 2018 Alfresco Software, Ltd.
*
* Copyright (C) 2005 - 2018 Alfresco Software Limited
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* 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:
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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/>.
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Injectable } from '@angular/core';
import { ExtensionConfig } from './extension.config';
import { HttpClient } from '@angular/common/http';
import { ExtensionElement } from './extension-element';
import { ContentActionRef, ContentActionType, ActionRef } from './action.extensions';
import { RuleRef } from './rule.extensions';
import { RouteRef } from './routing.extensions';
import { sortByOrder, filterEnabled, getValue, mergeObjects } from './extension-utils';
import { Injectable } from '@angular/core';
import { ActionRef, ContentActionRef, ContentActionType } from '../config/action.extensions';
import { ExtensionElement } from '../config/extension-element';
import { filterEnabled, getValue, mergeObjects, sortByOrder } from '../config/extension-utils';
import { ExtensionConfig } from '../config/extension.config';
import { RouteRef } from '../config/routing.extensions';
import { RuleRef } from '../config/rule.extensions';
@Injectable()
@Injectable({
providedIn: 'root'
})
export class ExtensionLoaderService {
constructor(private http: HttpClient) {}
@ -47,14 +41,20 @@ export class ExtensionLoaderService {
config = JSON.parse(override);
}
const externalPlugins = localStorage.getItem('experimental.external-plugins') === 'true';
const externalPlugins =
localStorage.getItem('experimental.external-plugins') ===
'true';
if (externalPlugins && config.$references && config.$references.length > 0) {
const plugins = config.$references.map(
(name, idx) => this.loadConfig(`${pluginsPath}/${name}`, idx)
if (
externalPlugins &&
config.$references &&
config.$references.length > 0
) {
const plugins = config.$references.map((name, idx) =>
this.loadConfig(`${pluginsPath}/${name}`, idx)
);
Promise.all(plugins).then((results => {
Promise.all(plugins).then(results => {
const configs = results
.filter(entry => entry)
.sort(sortByOrder)
@ -65,7 +65,7 @@ export class ExtensionLoaderService {
}
resolve(config);
}));
});
} else {
resolve(config);
}

@ -0,0 +1,148 @@
/*!
* @license
* Copyright 2016 - 2018 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Injectable, Type } from '@angular/core';
import { RuleEvaluator, RuleRef, RuleContext, RuleParameter } from '../config/rule.extensions';
import { ExtensionConfig } from '../config/extension.config';
import { ExtensionLoaderService } from './extension-loader.service';
import { RouteRef } from '../config/routing.extensions';
import { ActionRef } from '../config/action.extensions';
import * as core from '../evaluators/core.evaluators';
@Injectable()
export class ExtensionService {
configPath = 'assets/app.extensions.json';
pluginsPath = 'assets/plugins';
rules: Array<RuleRef> = [];
routes: Array<RouteRef> = [];
actions: Array<ActionRef> = [];
authGuards: { [key: string]: Type<{}> } = {};
components: { [key: string]: Type<{}> } = {};
evaluators: { [key: string]: RuleEvaluator } = {};
constructor(private loader: ExtensionLoaderService) {}
async load(): Promise<ExtensionConfig> {
const config = await this.loader.load(
this.configPath,
this.pluginsPath
);
this.setup(config);
return config;
}
setup(config: ExtensionConfig) {
if (!config) {
console.error('Extension configuration not found');
return;
}
this.setEvaluators({
'core.every': core.every,
'core.some': core.some,
'core.not': core.not
});
this.rules = this.loader.getRules(config);
this.actions = this.loader.getActions(config);
this.routes = this.loader.getRoutes(config);
}
setEvaluators(values: { [key: string]: RuleEvaluator }) {
if (values) {
this.evaluators = Object.assign({}, this.evaluators, values);
}
}
setAuthGuards(values: { [key: string]: Type<{}> }) {
if (values) {
this.authGuards = Object.assign({}, this.authGuards, values);
}
}
setComponents(values: { [key: string]: Type<{}> }) {
if (values) {
this.components = Object.assign({}, this.components, values);
}
}
getRouteById(id: string): RouteRef {
return this.routes.find(route => route.id === id);
}
getAuthGuards(ids: string[]): Array<Type<{}>> {
return (ids || [])
.map(id => this.authGuards[id])
.filter(guard => guard);
}
getActionById(id: string): ActionRef {
return this.actions.find(action => action.id === id);
}
getEvaluator(key: string): RuleEvaluator {
if (key && key.startsWith('!')) {
const fn = this.evaluators[key.substring(1)];
return (context: RuleContext, ...args: RuleParameter[]): boolean => {
return !fn(context, ...args);
};
}
return this.evaluators[key];
}
evaluateRule(ruleId: string, context: RuleContext): boolean {
const ruleRef = this.getRuleById(ruleId);
if (ruleRef) {
const evaluator = this.getEvaluator(ruleRef.type);
if (evaluator) {
return evaluator(context, ...ruleRef.parameters);
}
} else {
const evaluator = this.getEvaluator(ruleId);
if (evaluator) {
return evaluator(context);
}
}
return false;
}
getComponentById(id: string): Type<{}> {
return this.components[id];
}
getRuleById(id: string): RuleRef {
return this.rules.find(ref => ref.id === id);
}
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;
}
}

@ -0,0 +1,23 @@
/*!
* @license
* Copyright 2016 - 2018 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Node } from 'alfresco-js-api';
export interface NavigationState {
currentFolder?: Node;
url?: string;
}

@ -0,0 +1,25 @@
/*!
* @license
* Copyright 2016 - 2018 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export interface ProfileState {
id: string;
isAdmin: boolean;
firstName: string;
lastName: string;
userName?: string;
initials?: string;
}

@ -0,0 +1,30 @@
/*!
* @license
* Copyright 2016 - 2018 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { MinimalNodeEntity, SiteEntry } from 'alfresco-js-api';
export interface SelectionState {
count: number;
nodes: MinimalNodeEntity[];
libraries: SiteEntry[];
isEmpty: boolean;
first?: MinimalNodeEntity;
last?: MinimalNodeEntity;
folder?: MinimalNodeEntity;
file?: MinimalNodeEntity;
library?: SiteEntry;
}

@ -0,0 +1,36 @@
/*!
* @license
* Copyright 2016 - 2018 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export * from './lib/config/action.extensions';
export * from './lib/config/extension-element';
export * from './lib/config/extension-utils';
export * from './lib/config/extension.config';
export * from './lib/config/navbar.extensions';
export * from './lib/config/permission.extensions';
export * from './lib/config/routing.extensions';
export * from './lib/config/rule.extensions';
export * from './lib/config/sidebar.extensions';
export * from './lib/config/viewer.extensions';
export * from './lib/services/extension-loader.service';
export * from './lib/services/extension.service';
export * from './lib/store/states/navigation.state';
export * from './lib/store/states/profile.state';
export * from './lib/store/states/selection.state';
export * from './lib/extensions.module';

@ -0,0 +1,22 @@
// This file is required by karma.conf.js and loads recursively all the .spec and framework files
import 'core-js/es7/reflect';
import 'zone.js/dist/zone';
import 'zone.js/dist/zone-testing';
import { getTestBed } from '@angular/core/testing';
import {
BrowserDynamicTestingModule,
platformBrowserDynamicTesting
} from '@angular/platform-browser-dynamic/testing';
declare const require: any;
// First, initialize the Angular testing environment.
getTestBed().initTestEnvironment(
BrowserDynamicTestingModule,
platformBrowserDynamicTesting()
);
// Then we find all the tests.
const context = require.context('./', true, /\.spec\.ts$/);
// And load the modules.
context.keys().map(context);

@ -0,0 +1,33 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "../../out-tsc/lib",
"target": "es2015",
"module": "es2015",
"moduleResolution": "node",
"declaration": true,
"sourceMap": true,
"inlineSources": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"importHelpers": true,
"types": [],
"lib": [
"dom",
"es2015"
]
},
"angularCompilerOptions": {
"annotateForClosureCompiler": true,
"skipTemplateCodegen": true,
"strictMetadataEmit": true,
"fullTemplateTypeCheck": true,
"strictInjectionParameters": true,
"flatModuleId": "AUTOGENERATED",
"flatModuleOutFile": "AUTOGENERATED"
},
"exclude": [
"src/test.ts",
"**/*.spec.ts"
]
}

@ -0,0 +1,17 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "../../out-tsc/spec",
"types": [
"jasmine",
"node"
]
},
"files": [
"src/test.ts"
],
"include": [
"**/*.spec.ts",
"**/*.d.ts"
]
}

@ -0,0 +1,17 @@
{
"extends": "../../tslint.json",
"rules": {
"directive-selector": [
true,
"attribute",
"adf",
"camelCase"
],
"component-selector": [
true,
"element",
"adf",
"kebab-case"
]
}
}

@ -34,7 +34,7 @@ import {
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, NavigationEnd, Router } from '@angular/router';
import { Store } from '@ngrx/store';
import { ExtensionService } from './extensions/extension.service';
import { AppExtensionService } from './extensions/extension.service';
import {
SetLanguagePickerAction,
SnackbarErrorAction,
@ -59,7 +59,7 @@ export class AppComponent implements OnInit {
private alfrescoApiService: AlfrescoApiService,
private authenticationService: AuthenticationService,
private uploadService: UploadService,
private extensions: ExtensionService
private extensions: AppExtensionService
) {}
ngOnInit() {

@ -63,7 +63,7 @@ import { ExperimentalGuard } from './services/experimental-guard.service';
import { AppStoreModule } from './store/app-store.module';
import { MaterialModule } from './material.module';
import { ContentApiService } from './services/content-api.service';
import { ExtensionsModule } from './extensions.module';
import { AppExtensionsModule } from './extensions.module';
import { CoreExtensionsModule } from './extensions/core.extensions.module';
import { SearchResultsRowComponent } from './components/search/search-results-row/search-results-row.component';
import { NodePermissionsDialogComponent } from './dialogs/node-permissions/node-permissions.dialog';
@ -75,6 +75,7 @@ import { ToggleInfoDrawerComponent } from './components/toolbar/toggle-info-draw
import { DocumentDisplayModeComponent } from './components/toolbar/document-display-mode/document-display-mode.component';
import { ToggleFavoriteComponent } from './components/toolbar/toggle-favorite/toggle-favorite.component';
import { ContextMenuModule } from './components/context-menu/context-menu.module';
import { ExtensionsModule } from '@alfresco/adf-extensions';
@NgModule({
imports: [
@ -91,7 +92,8 @@ import { ContextMenuModule } from './components/context-menu/context-menu.module
ContentModule.forRoot(),
AppStoreModule,
CoreExtensionsModule.forRoot(),
ExtensionsModule,
ExtensionsModule.forRoot(),
AppExtensionsModule,
DirectivesModule,
ContextMenuModule.forRoot(),

@ -41,7 +41,7 @@
</ng-container>
<ng-container *ngSwitchCase="'custom'">
<app-custom-component [id]="entry.component"></app-custom-component>
<adf-dynamic-component [id]="entry.component"></adf-dynamic-component>
</ng-container>
</ng-container>
</div>

@ -31,15 +31,15 @@ import { trigger } from '@angular/animations';
import { FocusKeyManager } from '@angular/cdk/a11y';
import { DOWN_ARROW, UP_ARROW } from '@angular/cdk/keycodes';
import { ExtensionService } from '../../extensions/extension.service';
import { AppStore, SelectionState } from '../../store/states';
import { AppExtensionService } from '../../extensions/extension.service';
import { AppStore } from '../../store/states';
import { appSelection } from '../../store/selectors/app.selectors';
import { Store } from '@ngrx/store';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { SelectionState, ContentActionRef } from '@alfresco/adf-extensions';
import { ContextMenuOverlayRef } from './context-menu-overlay';
import { ContentActionRef } from '../../extensions/action.extensions';
import { contextMenuAnimation } from './animations';
import { ContextMenuItemDirective } from './context-menu-item.directive';
@ -99,7 +99,7 @@ export class ContextMenuComponent implements OnInit, OnDestroy, AfterViewInit {
constructor(
private contextMenuOverlayRef: ContextMenuOverlayRef,
private extensions: ExtensionService,
private extensions: AppExtensionService,
private store: Store<AppStore>,
) { }

@ -33,6 +33,7 @@ import { ContextActionsDirective } from './context-menu.directive';
import { ContextMenuService } from './context-menu.service';
import { ContextMenuComponent } from './context-menu.component';
import { ContextMenuItemDirective } from './context-menu-item.directive';
import { ExtensionsModule } from '@alfresco/adf-extensions';
import { OutsideEventDirective } from './context-menu-outside-event.directive';
@NgModule({
@ -43,7 +44,8 @@ import { OutsideEventDirective } from './context-menu-outside-event.directive';
MatButtonModule,
BrowserModule,
CoreExtensionsModule.forChild(),
CoreModule.forChild()
CoreModule.forChild(),
ExtensionsModule.forChild()
],
declarations: [
ContextActionsDirective,

@ -27,7 +27,8 @@ import { Component, ViewEncapsulation } from '@angular/core';
import { Store } from '@ngrx/store';
import { Observable } from 'rxjs';
import { selectUser, appLanguagePicker } from '../../store/selectors/app.selectors';
import { AppStore, ProfileState } from '../../store/states';
import { AppStore } from '../../store/states';
import { ProfileState } from '@alfresco/adf-extensions';
@Component({
selector: 'aca-current-user',

@ -37,7 +37,7 @@ import { ContentManagementService } from '../../services/content-management.serv
import { AppStore } from '../../store/states/app.state';
import { PageComponent } from '../page.component';
import { ContentApiService } from '../../services/content-api.service';
import { ExtensionService } from '../../extensions/extension.service';
import { AppExtensionService } from '../../extensions/extension.service';
import { map } from 'rxjs/operators';
@Component({
@ -49,7 +49,7 @@ export class FavoritesComponent extends PageComponent implements OnInit {
constructor(
private router: Router,
store: Store<AppStore>,
extensions: ExtensionService,
extensions: AppExtensionService,
private contentApi: ContentApiService,
content: ContentManagementService,
private breakpointObserver: BreakpointObserver

@ -33,7 +33,7 @@ import { NodeActionsService } from '../../services/node-actions.service';
import { AppStore } from '../../store/states/app.state';
import { PageComponent } from '../page.component';
import { ContentApiService } from '../../services/content-api.service';
import { ExtensionService } from '../../extensions/extension.service';
import { AppExtensionService } from '../../extensions/extension.service';
import { SetCurrentFolderAction } from '../../store/actions';
import { BreakpointObserver, Breakpoints } from '@angular/cdk/layout';
import { debounceTime } from 'rxjs/operators';
@ -55,7 +55,7 @@ export class FilesComponent extends PageComponent implements OnInit, OnDestroy {
private nodeActionsService: NodeActionsService,
private uploadService: UploadService,
content: ContentManagementService,
extensions: ExtensionService,
extensions: AppExtensionService,
private breakpointObserver: BreakpointObserver) {
super(store, extensions, content);
}

@ -7,8 +7,8 @@
*ngFor="let tab of tabs"
[icon]="tab.icon"
[label]="tab.title | translate">
<app-dynamic-tab [node]="displayNode" [id]="tab.component" [attr.data-automation-id]="tab.component">
</app-dynamic-tab>
<adf-dynamic-tab [node]="displayNode" [id]="tab.component" [attr.data-automation-id]="tab.component">
</adf-dynamic-tab>
</adf-info-drawer-tab>
</adf-info-drawer>
</ng-container>

@ -26,8 +26,8 @@
import { Component, Input, OnChanges, OnInit } from '@angular/core';
import { MinimalNodeEntity, MinimalNodeEntryEntity } from 'alfresco-js-api';
import { ContentApiService } from '../../services/content-api.service';
import { ExtensionService } from '../../extensions/extension.service';
import { SidebarTabRef } from '../../extensions/sidebar.extensions';
import { AppExtensionService } from '../../extensions/extension.service';
import { SidebarTabRef } from '@alfresco/adf-extensions';
@Component({
selector: 'aca-info-drawer',
@ -43,7 +43,7 @@ export class InfoDrawerComponent implements OnChanges, OnInit {
constructor(
private contentApi: ContentApiService,
private extensions: ExtensionService
private extensions: AppExtensionService
) {}
ngOnInit() {

@ -23,25 +23,27 @@
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import {
ContentMetadataModule,
VersionManagerModule
} from '@alfresco/adf-content-services';
import { CoreModule } from '@alfresco/adf-core';
import { ContentMetadataModule, VersionManagerModule } from '@alfresco/adf-content-services';
import { InfoDrawerComponent } from './info-drawer.component';
import { ExtensionsModule } from '@alfresco/adf-extensions';
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { DirectivesModule } from '../../directives/directives.module';
import { MaterialModule } from '../../material.module';
import { MetadataTabComponent } from './metadata-tab/metadata-tab.component';
import { CommentsTabComponent } from './comments-tab/comments-tab.component';
import { InfoDrawerComponent } from './info-drawer.component';
import { MetadataTabComponent } from './metadata-tab/metadata-tab.component';
import { VersionsTabComponent } from './versions-tab/versions-tab.component';
import { DynamicTabComponent } from './dynamic-tab/dynamic-tab.component';
export function components() {
return [
InfoDrawerComponent,
MetadataTabComponent,
CommentsTabComponent,
VersionsTabComponent,
DynamicTabComponent
VersionsTabComponent
];
}
@ -50,18 +52,13 @@ export function components() {
CommonModule,
MaterialModule,
CoreModule.forChild(),
ExtensionsModule.forChild(),
ContentMetadataModule,
VersionManagerModule,
DirectivesModule
],
declarations: [
...components()
],
exports: [
...components()
],
entryComponents: [
...components()
]
declarations: [...components()],
exports: [...components()],
entryComponents: [...components()]
})
export class AppInfoDrawerModule {}

@ -34,7 +34,7 @@ import { AppStore } from '../../store/states/app.state';
import { SiteEntry } from 'alfresco-js-api';
import { ContentManagementService } from '../../services/content-management.service';
import { ContentApiService } from '../../services/content-api.service';
import { ExtensionService } from '../../extensions/extension.service';
import { AppExtensionService } from '../../extensions/extension.service';
import { map } from 'rxjs/operators';
@Component({
@ -48,7 +48,7 @@ export class LibrariesComponent extends PageComponent implements OnInit {
content: ContentManagementService,
private contentApi: ContentApiService,
store: Store<AppStore>,
extensions: ExtensionService,
extensions: AppExtensionService,
private router: Router,
private breakpointObserver: BreakpointObserver) {
super(store, extensions, content);

@ -24,18 +24,17 @@
*/
import { DocumentListComponent, ShareDataRow } from '@alfresco/adf-content-services';
import { ContentActionRef, SelectionState } from '@alfresco/adf-extensions';
import { OnDestroy, OnInit, ViewChild } from '@angular/core';
import { Store } from '@ngrx/store';
import { MinimalNodeEntity, MinimalNodeEntryEntity } from 'alfresco-js-api';
import { Observable, Subject, Subscription } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { Subject, Subscription , Observable } from 'rxjs';
import { SetSelectedNodesAction, ViewFileAction } from '../store/actions';
import { appSelection, sharedUrl, currentFolder, infoDrawerOpened, documentDisplayMode } from '../store/selectors/app.selectors';
import { AppStore } from '../store/states/app.state';
import { SelectionState } from '../store/states/selection.state';
import { ExtensionService } from '../extensions/extension.service';
import { AppExtensionService } from '../extensions/extension.service';
import { ContentManagementService } from '../services/content-management.service';
import { ContentActionRef } from '../extensions/action.extensions';
import { SetSelectedNodesAction, ViewFileAction } from '../store/actions';
import { appSelection, currentFolder, documentDisplayMode, infoDrawerOpened, sharedUrl } from '../store/selectors/app.selectors';
import { AppStore } from '../store/states/app.state';
export abstract class PageComponent implements OnInit, OnDestroy {
@ -63,7 +62,7 @@ export abstract class PageComponent implements OnInit, OnDestroy {
constructor(
protected store: Store<AppStore>,
protected extensions: ExtensionService,
protected extensions: AppExtensionService,
protected content: ContentManagementService) {}
ngOnInit() {

@ -34,7 +34,7 @@ import {
OnDestroy,
OnChanges
} from '@angular/core';
import { ExtensionService } from '../../extensions/extension.service';
import { AppExtensionService } from '../../extensions/extension.service';
import { MinimalNodeEntryEntity } from 'alfresco-js-api';
@Component({
@ -60,7 +60,7 @@ export class PreviewExtensionComponent implements OnInit, OnChanges, OnDestroy {
private componentRef: ComponentRef<any>;
constructor(
private extensions: ExtensionService,
private extensions: AppExtensionService,
private componentFactoryResolver: ComponentFactoryResolver
) {}

@ -31,11 +31,10 @@ import { AppStore } from '../../store/states/app.state';
import { SetSelectedNodesAction } from '../../store/actions';
import { PageComponent } from '../page.component';
import { ContentApiService } from '../../services/content-api.service';
import { ExtensionService } from '../../extensions/extension.service';
import { AppExtensionService } from '../../extensions/extension.service';
import { ContentManagementService } from '../../services/content-management.service';
import { ContentActionRef } from '../../extensions/action.extensions';
import { ContentActionRef, ViewerExtensionRef } from '@alfresco/adf-extensions';
import { ViewUtilService } from './view-util.service';
import { ViewerExtensionRef } from '../../extensions/viewer.extensions';
@Component({
selector: 'app-preview',
@ -65,7 +64,7 @@ export class PreviewComponent extends PageComponent implements OnInit {
private router: Router,
private viewUtils: ViewUtilService,
store: Store<AppStore>,
extensions: ExtensionService,
extensions: AppExtensionService,
content: ContentManagementService) {
super(store, extensions, content);
}

@ -30,7 +30,7 @@ import { ContentManagementService } from '../../services/content-management.serv
import { PageComponent } from '../page.component';
import { Store } from '@ngrx/store';
import { AppStore } from '../../store/states/app.state';
import { ExtensionService } from '../../extensions/extension.service';
import { AppExtensionService } from '../../extensions/extension.service';
@Component({
templateUrl: './recent-files.component.html'
@ -40,7 +40,7 @@ export class RecentFilesComponent extends PageComponent implements OnInit {
constructor(
store: Store<AppStore>,
extensions: ExtensionService,
extensions: AppExtensionService,
content: ContentManagementService,
private breakpointObserver: BreakpointObserver
) {

@ -31,7 +31,7 @@ import { PageComponent } from '../../page.component';
import { Store } from '@ngrx/store';
import { AppStore } from '../../../store/states/app.state';
import { NavigateToFolder } from '../../../store/actions';
import { ExtensionService } from '../../../extensions/extension.service';
import { AppExtensionService } from '../../../extensions/extension.service';
import { ContentManagementService } from '../../../services/content-management.service';
@Component({
@ -56,7 +56,7 @@ export class SearchResultsComponent extends PageComponent implements OnInit {
private queryBuilder: SearchQueryBuilderService,
private route: ActivatedRoute,
store: Store<AppStore>,
extensions: ExtensionService,
extensions: AppExtensionService,
content: ContentManagementService
) {
super(store, extensions, content);

@ -28,10 +28,11 @@ import { AppConfigService, StorageService, SettingsService } from '@alfresco/adf
import { Validators, FormGroup, FormBuilder } from '@angular/forms';
import { Observable } from 'rxjs';
import { Store } from '@ngrx/store';
import { AppStore, ProfileState } from '../../store/states';
import { AppStore } from '../../store/states';
import { appLanguagePicker, selectHeaderColor, selectAppName, selectUser } from '../../store/selectors/app.selectors';
import { MatCheckboxChange } from '@angular/material';
import { SetLanguagePickerAction } from '../../store/actions';
import { ProfileState } from '@alfresco/adf-extensions';
@Component({
selector: 'aca-settings',

@ -29,7 +29,7 @@ import { ContentManagementService } from '../../services/content-management.serv
import { PageComponent } from '../page.component';
import { Store } from '@ngrx/store';
import { AppStore } from '../../store/states/app.state';
import { ExtensionService } from '../../extensions/extension.service';
import { AppExtensionService } from '../../extensions/extension.service';
@Component({
templateUrl: './shared-files.component.html'
@ -39,7 +39,7 @@ export class SharedFilesComponent extends PageComponent implements OnInit {
constructor(
store: Store<AppStore>,
extensions: ExtensionService,
extensions: AppExtensionService,
content: ContentManagementService,
private breakpointObserver: BreakpointObserver
) {

@ -25,13 +25,12 @@
import { Subject } from 'rxjs';
import { Component, Input, OnInit, OnDestroy } from '@angular/core';
import { ExtensionService } from '../../extensions/extension.service';
import { AppExtensionService } from '../../extensions/extension.service';
import { Store } from '@ngrx/store';
import { AppStore } from '../../store/states';
import { currentFolder } from '../../store/selectors/app.selectors';
import { takeUntil } from 'rxjs/operators';
import { NavBarGroupRef } from '../../extensions/navbar.extensions';
import { ContentActionRef } from '../../extensions/action.extensions';
import { ContentActionRef, NavBarGroupRef } from '@alfresco/adf-extensions';
@Component({
selector: 'app-sidenav',
@ -47,7 +46,7 @@ export class SidenavComponent implements OnInit, OnDestroy {
constructor(
private store: Store<AppStore>,
private extensions: ExtensionService
private extensions: AppExtensionService
) {}
ngOnInit() {

@ -25,9 +25,10 @@
import { Component } from '@angular/core';
import { Store } from '@ngrx/store';
import { AppStore, SelectionState } from '../../../store/states';
import { AppStore } from '../../../store/states';
import { appSelection } from '../../../store/selectors/app.selectors';
import { Observable } from 'rxjs';
import { SelectionState } from '@alfresco/adf-extensions';
@Component({
selector: 'app-toggle-favorite',

@ -30,9 +30,9 @@ import { PageComponent } from '../page.component';
import { Store } from '@ngrx/store';
import { selectUser } from '../../store/selectors/app.selectors';
import { AppStore } from '../../store/states/app.state';
import { ProfileState } from '../../store/states/profile.state';
import { ExtensionService } from '../../extensions/extension.service';
import { AppExtensionService } from '../../extensions/extension.service';
import { Observable } from 'rxjs';
import { ProfileState } from '@alfresco/adf-extensions';
@Component({
templateUrl: './trashcan.component.html'
@ -42,7 +42,7 @@ export class TrashcanComponent extends PageComponent implements OnInit {
user$: Observable<ProfileState>;
constructor(content: ContentManagementService,
extensions: ExtensionService,
extensions: AppExtensionService,
store: Store<AppStore>,
private breakpointObserver: BreakpointObserver) {
super(store, extensions, content);

@ -1,6 +1,5 @@
import { NgModule } from '@angular/core';
import { AcaDevToolsModule, AcaDevToolsComponent } from 'aca-dev-tools';
import { ExtensionService } from './extensions/extension.service';
import { AcaDevToolsModule } from 'aca-dev-tools';
import { CodeEditorModule } from '@ngstack/code-editor';
// Main entry point for external extensions only.
@ -17,10 +16,4 @@ import { CodeEditorModule } from '@ngstack/code-editor';
AcaDevToolsModule
]
})
export class ExtensionsModule {
constructor(extensions: ExtensionService) {
extensions.setComponents({
'app.dev.tools.component': AcaDevToolsComponent
});
}
}
export class AppExtensionsModule {}

@ -1,60 +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 enum ContentActionType {
default = 'default',
button = 'button',
separator = 'separator',
menu = 'menu',
custom = 'custom'
}
export interface ContentActionRef {
id: string;
type: ContentActionType;
title?: string;
description?: string;
order?: number;
icon?: string;
disabled?: boolean;
children?: Array<ContentActionRef>;
component?: string;
actions?: {
click?: string;
[key: string]: string;
};
rules?: {
enabled?: string;
visible?: string;
[key: string]: string;
};
}
export interface ActionRef {
id: string;
type: string;
payload?: string;
}

@ -1,75 +0,0 @@
/*!
* @license
* Alfresco Example Content Application
*
* Copyright (C) 2005 - 2018 Alfresco Software Limited
*
* This file is part of the Alfresco Example Content Application.
* If the software was purchased under a paid Alfresco license, the terms of
* the paid license agreement will prevail. Otherwise, the software is
* provided under the following open source license terms:
*
* The Alfresco Example Content Application is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* The Alfresco Example Content Application is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
import {
Component,
Input,
ComponentRef,
OnInit,
ComponentFactoryResolver,
ViewChild,
ViewContainerRef,
OnDestroy
} from '@angular/core';
import { ExtensionService } from '../../extension.service';
@Component({
selector: 'app-custom-component',
template: `<div #content></div>`
})
export class CustomExtensionComponent implements OnInit, OnDestroy {
@ViewChild('content', { read: ViewContainerRef })
content: ViewContainerRef;
@Input() id: string;
private componentRef: ComponentRef<any>;
constructor(
private extensions: ExtensionService,
private componentFactoryResolver: ComponentFactoryResolver
) {}
ngOnInit() {
const componentType = this.extensions.getComponentById(this.id);
if (componentType) {
const factory = this.componentFactoryResolver.resolveComponentFactory(
componentType
);
if (factory) {
this.content.clear();
this.componentRef = this.content.createComponent(factory, 0);
// this.setupWidget(this.componentRef);
}
}
}
ngOnDestroy() {
if (this.componentRef) {
this.componentRef.destroy();
this.componentRef = null;
}
}
}

@ -26,7 +26,7 @@
<ng-container *ngFor="let child of entry.children; trackBy: trackByActionId">
<ng-container [ngSwitch]="child.type">
<ng-container *ngSwitchCase="'custom'">
<app-custom-component [id]="child.component"></app-custom-component>
<adf-dynamic-component [id]="child.component"></adf-dynamic-component>
</ng-container>
<ng-container *ngSwitchDefault>
<app-toolbar-button type="menu-item" [actionRef]="child"></app-toolbar-button>
@ -37,7 +37,7 @@
</ng-container>
<ng-container *ngSwitchCase="'custom'">
<app-custom-component [id]="entry.component"></app-custom-component>
<adf-dynamic-component [id]="entry.component"></adf-dynamic-component>
</ng-container>
</ng-container>

@ -31,8 +31,8 @@ import {
} from '@angular/core';
import { AppStore } from '../../../store/states';
import { Store } from '@ngrx/store';
import { ExtensionService } from '../../extension.service';
import { ContentActionRef } from '../../action.extensions';
import { AppExtensionService } from '../../extension.service';
import { ContentActionRef } from '@alfresco/adf-extensions';
@Component({
selector: 'aca-toolbar-action',
@ -47,7 +47,7 @@ export class ToolbarActionComponent {
constructor(
protected store: Store<AppStore>,
protected extensions: ExtensionService
protected extensions: AppExtensionService
) {}
trackByActionId(index: number, action: ContentActionRef) {

@ -24,8 +24,8 @@
*/
import { Component, Input } from '@angular/core';
import { ContentActionRef } from '../../action.extensions';
import { ExtensionService } from '../../extension.service';
import { ContentActionRef } from '@alfresco/adf-extensions';
import { AppExtensionService } from '../../extension.service';
import { Store } from '@ngrx/store';
import { AppStore } from '../../../store/states';
import { appSelection } from '../../../store/selectors/app.selectors';
@ -75,7 +75,7 @@ export class ToolbarButtonComponent {
constructor(
protected store: Store<AppStore>,
private extensions: ExtensionService
private extensions: AppExtensionService
) {}
runAction() {

@ -31,75 +31,32 @@ import { TrashcanComponent } from '../components/trashcan/trashcan.component';
import { ToolbarActionComponent } from './components/toolbar/toolbar-action.component';
import * as app from './evaluators/app.evaluators';
import * as nav from './evaluators/navigation.evaluators';
import { ExtensionService } from './extension.service';
import { CustomExtensionComponent } from './components/custom-component/custom.component';
import { AppExtensionService } from './extension.service';
import { ToggleInfoDrawerComponent } from '../components/toolbar/toggle-info-drawer/toggle-info-drawer.component';
import { ToggleFavoriteComponent } from '../components/toolbar/toggle-favorite/toggle-favorite.component';
import { ToolbarButtonComponent } from './components/toolbar/toolbar-button.component';
import { MetadataTabComponent } from '../components/info-drawer/metadata-tab/metadata-tab.component';
import { CommentsTabComponent } from '../components/info-drawer/comments-tab/comments-tab.component';
import { VersionsTabComponent } from '../components/info-drawer/versions-tab/versions-tab.component';
import { ExtensionLoaderService } from './extension-loader.service';
import { ExtensionsModule, ExtensionService } from '@alfresco/adf-extensions';
export function setupExtensions(extensions: ExtensionService): Function {
extensions.setComponents({
'app.layout.main': LayoutComponent,
'app.components.trashcan': TrashcanComponent,
'app.components.tabs.metadata': MetadataTabComponent,
'app.components.tabs.comments': CommentsTabComponent,
'app.components.tabs.versions': VersionsTabComponent,
'app.toolbar.toggleInfoDrawer': ToggleInfoDrawerComponent,
'app.toolbar.toggleFavorite': ToggleFavoriteComponent
});
extensions.setAuthGuards({
'app.auth': AuthGuardEcm
});
extensions.setEvaluators({
'app.selection.canDelete': app.canDeleteSelection,
'app.selection.canDownload': app.canDownloadSelection,
'app.selection.notEmpty': app.hasSelection,
'app.selection.canUnshare': app.canUnshareNodes,
'app.selection.canAddFavorite': app.canAddFavorite,
'app.selection.canRemoveFavorite': app.canRemoveFavorite,
'app.selection.first.canUpdate': app.canUpdateSelectedNode,
'app.selection.file': app.hasFileSelected,
'app.selection.file.canShare': app.canShareFile,
'app.selection.library': app.hasLibrarySelected,
'app.selection.folder': app.hasFolderSelected,
'app.selection.folder.canUpdate': app.canUpdateSelectedFolder,
'app.navigation.folder.canCreate': app.canCreateFolder,
'app.navigation.folder.canUpload': app.canUpload,
'app.navigation.isTrashcan': nav.isTrashcan,
'app.navigation.isNotTrashcan': nav.isNotTrashcan,
'app.navigation.isLibraries': nav.isLibraries,
'app.navigation.isNotLibraries': nav.isNotLibraries,
'app.navigation.isSharedFiles': nav.isSharedFiles,
'app.navigation.isNotSharedFiles': nav.isNotSharedFiles,
'app.navigation.isFavorites': nav.isFavorites,
'app.navigation.isNotFavorites': nav.isNotFavorites,
'app.navigation.isRecentFiles': nav.isRecentFiles,
'app.navigation.isNotRecentFiles': nav.isNotRecentFiles,
'app.navigation.isSearchResults': nav.isSearchResults,
'app.navigation.isNotSearchResults': nav.isNotSearchResults
});
return () => extensions.load();
export function setupExtensions(service: AppExtensionService): Function {
return () => service.load();
}
@NgModule({
imports: [CommonModule, CoreModule.forChild()],
imports: [
CommonModule,
CoreModule.forChild(),
ExtensionsModule.forChild()
],
declarations: [
ToolbarActionComponent,
ToolbarButtonComponent,
CustomExtensionComponent
ToolbarButtonComponent
],
exports: [
ToolbarActionComponent,
ToolbarButtonComponent,
CustomExtensionComponent
ToolbarButtonComponent
]
})
export class CoreExtensionsModule {
@ -107,12 +64,11 @@ export class CoreExtensionsModule {
return {
ngModule: CoreExtensionsModule,
providers: [
ExtensionLoaderService,
ExtensionService,
AppExtensionService,
{
provide: APP_INITIALIZER,
useFactory: setupExtensions,
deps: [ExtensionService],
deps: [AppExtensionService],
multi: true
}
]
@ -124,4 +80,50 @@ export class CoreExtensionsModule {
ngModule: CoreExtensionsModule
};
}
constructor(extensions: ExtensionService) {
extensions.setComponents({
'app.layout.main': LayoutComponent,
'app.components.trashcan': TrashcanComponent,
'app.components.tabs.metadata': MetadataTabComponent,
'app.components.tabs.comments': CommentsTabComponent,
'app.components.tabs.versions': VersionsTabComponent,
'app.toolbar.toggleInfoDrawer': ToggleInfoDrawerComponent,
'app.toolbar.toggleFavorite': ToggleFavoriteComponent
});
extensions.setAuthGuards({
'app.auth': AuthGuardEcm
});
extensions.setEvaluators({
'app.selection.canDelete': app.canDeleteSelection,
'app.selection.canDownload': app.canDownloadSelection,
'app.selection.notEmpty': app.hasSelection,
'app.selection.canUnshare': app.canUnshareNodes,
'app.selection.canAddFavorite': app.canAddFavorite,
'app.selection.canRemoveFavorite': app.canRemoveFavorite,
'app.selection.first.canUpdate': app.canUpdateSelectedNode,
'app.selection.file': app.hasFileSelected,
'app.selection.file.canShare': app.canShareFile,
'app.selection.library': app.hasLibrarySelected,
'app.selection.folder': app.hasFolderSelected,
'app.selection.folder.canUpdate': app.canUpdateSelectedFolder,
'app.navigation.folder.canCreate': app.canCreateFolder,
'app.navigation.folder.canUpload': app.canUpload,
'app.navigation.isTrashcan': nav.isTrashcan,
'app.navigation.isNotTrashcan': nav.isNotTrashcan,
'app.navigation.isLibraries': nav.isLibraries,
'app.navigation.isNotLibraries': nav.isNotLibraries,
'app.navigation.isSharedFiles': nav.isSharedFiles,
'app.navigation.isNotSharedFiles': nav.isNotSharedFiles,
'app.navigation.isFavorites': nav.isFavorites,
'app.navigation.isNotFavorites': nav.isNotFavorites,
'app.navigation.isRecentFiles': nav.isRecentFiles,
'app.navigation.isNotRecentFiles': nav.isNotRecentFiles,
'app.navigation.isSearchResults': nav.isSearchResults,
'app.navigation.isNotSearchResults': nav.isNotSearchResults
});
}
}

@ -23,7 +23,7 @@
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
import { RuleContext, RuleParameter } from '../rule.extensions';
import { RuleContext, RuleParameter } from '@alfresco/adf-extensions';
import {
isNotTrashcan,
isNotSharedFiles,

@ -1,71 +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 { RuleContext, RuleParameter } from '../rule.extensions';
export function not(context: RuleContext, ...args: RuleParameter[]): boolean {
if (!args || args.length === 0) {
return false;
}
return args
.every(arg => {
const evaluator = context.getEvaluator(arg.value);
if (!evaluator) {
console.warn('evaluator not found: ' + arg.value);
}
return !evaluator(context, ...arg.parameters);
});
}
export function every(context: RuleContext, ...args: RuleParameter[]): boolean {
if (!args || args.length === 0) {
return false;
}
return args
.every(arg => {
const evaluator = context.getEvaluator(arg.value);
if (!evaluator) {
console.warn('evaluator not found: ' + arg.value);
}
return evaluator(context, ...arg.parameters);
});
}
export function some(context: RuleContext, ...args: RuleParameter[]): boolean {
if (!args || args.length === 0) {
return false;
}
return args
.some(arg => {
const evaluator = context.getEvaluator(arg.value);
if (!evaluator) {
console.warn('evaluator not found: ' + arg.value);
}
return evaluator(context, ...arg.parameters);
});
}

@ -23,7 +23,7 @@
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
import { RuleContext, RuleParameter } from '../rule.extensions';
import { RuleContext, RuleParameter } from '@alfresco/adf-extensions';
export function isFavorites(
context: RuleContext,

@ -1,31 +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 ExtensionElement {
id: string;
order?: number;
disabled?: boolean;
}

@ -1,43 +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 { RouteRef } from './routing.extensions';
import { RuleRef } from './rule.extensions';
import { ActionRef } from './action.extensions';
export interface ExtensionConfig {
$name: string;
$version: string;
$description?: string;
$references?: Array<string>;
rules?: Array<RuleRef>;
routes?: Array<RouteRef>;
actions?: Array<ActionRef>;
features?: {
[key: string]: any;
};
}

@ -25,25 +25,34 @@
import { TestBed } from '@angular/core/testing';
import { AppTestingModule } from '../testing/app-testing.module';
import { ExtensionService } from './extension.service';
import { AppExtensionService } from './extension.service';
import { Store } from '@ngrx/store';
import { AppStore } from '../store/states';
import { ContentActionType } from './action.extensions';
import { mergeArrays, sortByOrder, filterEnabled, reduceSeparators, reduceEmptyMenus } from './extension-utils';
import { ContentActionType, mergeArrays,
sortByOrder, filterEnabled, reduceSeparators, reduceEmptyMenus, ExtensionService, ExtensionConfig } from '@alfresco/adf-extensions';
describe('ExtensionService', () => {
let extensions: ExtensionService;
describe('AppExtensionService', () => {
let service: AppExtensionService;
let store: Store<AppStore>;
let extensions: ExtensionService;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [AppTestingModule]
imports: [
AppTestingModule
]
});
store = TestBed.get(Store);
service = TestBed.get(AppExtensionService);
extensions = TestBed.get(ExtensionService);
});
const applyConfig = (config: ExtensionConfig) => {
extensions.setup(config);
service.setup(config);
};
describe('configs', () => {
it('should merge two arrays based on [id] keys', () => {
const left = [
@ -94,7 +103,7 @@ describe('ExtensionService', () => {
describe('actions', () => {
beforeEach(() => {
extensions.setup({
applyConfig({
$name: 'test',
$version: '1.0.0',
actions: [
@ -128,7 +137,7 @@ describe('ExtensionService', () => {
it('should run the action via store', () => {
spyOn(store, 'dispatch').and.stub();
extensions.runActionById('aca:actions/create-folder');
service.runActionById('aca:actions/create-folder');
expect(store.dispatch).toHaveBeenCalledWith({
type: 'CREATE_FOLDER',
payload: 'folder-name'
@ -138,7 +147,7 @@ describe('ExtensionService', () => {
it('should still invoke store if action is missing', () => {
spyOn(store, 'dispatch').and.stub();
extensions.runActionById('missing');
service.runActionById('missing');
expect(store.dispatch).toHaveBeenCalled();
});
});
@ -213,12 +222,12 @@ describe('ExtensionService', () => {
});
it('should fetch registered component', () => {
const component = extensions.getComponentById('component-1');
const component = service.getComponentById('component-1');
expect(component).toEqual(component1);
});
it('should not fetch registered component', () => {
const component = extensions.getComponentById('missing');
const component = service.getComponentById('missing');
expect(component).toBeFalsy();
});
});
@ -228,7 +237,7 @@ describe('ExtensionService', () => {
let guard1;
beforeEach(() => {
extensions.setup({
applyConfig({
$name: 'test',
$version: '1.0.0',
routes: [
@ -270,7 +279,7 @@ describe('ExtensionService', () => {
});
it('should build application routes', () => {
const routes = extensions.getApplicationRoutes();
const routes = service.getApplicationRoutes();
expect(routes.length).toBe(1);
@ -287,7 +296,7 @@ describe('ExtensionService', () => {
describe('content actions', () => {
it('should load content actions from the config', () => {
extensions.setup({
applyConfig({
$name: 'test',
$version: '1.0.0',
features: {
@ -308,11 +317,11 @@ describe('ExtensionService', () => {
}
});
expect(extensions.toolbarActions.length).toBe(2);
expect(service.toolbarActions.length).toBe(2);
});
it('should sort content actions by order', () => {
extensions.setup({
applyConfig({
$name: 'test',
$version: '1.0.0',
features: {
@ -333,11 +342,11 @@ describe('ExtensionService', () => {
}
});
expect(extensions.toolbarActions.length).toBe(2);
expect(extensions.toolbarActions[0].id).toBe(
expect(service.toolbarActions.length).toBe(2);
expect(service.toolbarActions[0].id).toBe(
'aca:toolbar/separator-1'
);
expect(extensions.toolbarActions[1].id).toBe(
expect(service.toolbarActions[1].id).toBe(
'aca:toolbar/separator-2'
);
});
@ -345,7 +354,7 @@ describe('ExtensionService', () => {
describe('open with', () => {
it('should load [open with] actions for the viewer', () => {
extensions.setup({
applyConfig({
$name: 'test',
$version: '1.0.0',
features: {
@ -367,11 +376,11 @@ describe('ExtensionService', () => {
}
});
expect(extensions.openWithActions.length).toBe(1);
expect(service.openWithActions.length).toBe(1);
});
it('should load only enabled [open with] actions for the viewer', () => {
extensions.setup({
applyConfig({
$name: 'test',
$version: '1.0.0',
features: {
@ -403,12 +412,12 @@ describe('ExtensionService', () => {
}
});
expect(extensions.openWithActions.length).toBe(1);
expect(extensions.openWithActions[0].id).toBe('aca:viewer/action2');
expect(service.openWithActions.length).toBe(1);
expect(service.openWithActions[0].id).toBe('aca:viewer/action2');
});
it('should sort [open with] actions by order', () => {
extensions.setup({
applyConfig({
$name: 'test',
$version: '1.0.0',
features: {
@ -439,15 +448,15 @@ describe('ExtensionService', () => {
}
});
expect(extensions.openWithActions.length).toBe(2);
expect(extensions.openWithActions[0].id).toBe('aca:viewer/action1');
expect(extensions.openWithActions[1].id).toBe('aca:viewer/action2');
expect(service.openWithActions.length).toBe(2);
expect(service.openWithActions[0].id).toBe('aca:viewer/action1');
expect(service.openWithActions[1].id).toBe('aca:viewer/action2');
});
});
describe('create', () => {
it('should load [create] actions from config', () => {
extensions.setup({
applyConfig({
$name: 'test',
$version: '1.0.0',
features: {
@ -463,11 +472,11 @@ describe('ExtensionService', () => {
}
});
expect(extensions.createActions.length).toBe(1);
expect(service.createActions.length).toBe(1);
});
it('should sort [create] actions by order', () => {
extensions.setup({
applyConfig({
$name: 'test',
$version: '1.0.0',
features: {
@ -490,9 +499,9 @@ describe('ExtensionService', () => {
}
});
expect(extensions.createActions.length).toBe(2);
expect(extensions.createActions[0].id).toBe('aca:create/folder-2');
expect(extensions.createActions[1].id).toBe('aca:create/folder');
expect(service.createActions.length).toBe(2);
expect(service.createActions[0].id).toBe('aca:create/folder-2');
expect(service.createActions[1].id).toBe('aca:create/folder');
});
});

@ -26,36 +26,28 @@
import { Injectable, Type } from '@angular/core';
import { Store } from '@ngrx/store';
import { Route } from '@angular/router';
import { ExtensionConfig } from './extension.config';
import { AppStore, SelectionState } from '../store/states';
import { NavigationState } from '../store/states/navigation.state';
import { selectionWithFolder } from '../store/selectors/app.selectors';
import { NavBarGroupRef } from './navbar.extensions';
import { RouteRef } from './routing.extensions';
import { RuleContext, RuleRef, RuleEvaluator, RuleParameter } from './rule.extensions';
import { ActionRef, ContentActionRef, ContentActionType } from './action.extensions';
import * as core from './evaluators/core.evaluators';
import { AppStore } from '../store/states';
import { ruleContext } from '../store/selectors/app.selectors';
import { NodePermissionService } from '../services/node-permission.service';
import { SidebarTabRef } from './sidebar.extensions';
import { ProfileResolver } from '../services/profile.resolver';
import { ViewerExtensionRef } from './viewer.extensions';
import { ExtensionLoaderService } from './extension-loader.service';
import { sortByOrder, filterEnabled, reduceSeparators, reduceEmptyMenus } from './extension-utils';
import {
SelectionState, NavigationState, ExtensionConfig,
RuleContext, RuleEvaluator, ViewerExtensionRef,
ContentActionRef, ContentActionType,
ExtensionLoaderService,
SidebarTabRef, NavBarGroupRef,
sortByOrder, reduceSeparators, reduceEmptyMenus,
ExtensionService,
ProfileState
} from '@alfresco/adf-extensions';
@Injectable()
export class ExtensionService implements RuleContext {
configPath = 'assets/app.extensions.json';
pluginsPath = 'assets/plugins';
export class AppExtensionService implements RuleContext {
defaults = {
layout: 'app.layout.main',
auth: ['app.auth']
};
rules: Array<RuleRef> = [];
routes: Array<RouteRef> = [];
actions: Array<ActionRef> = [];
toolbarActions: Array<ContentActionRef> = [];
viewerToolbarActions: Array<ContentActionRef> = [];
viewerContentExtensions: Array<ViewerExtensionRef> = [];
@ -65,32 +57,25 @@ export class ExtensionService implements RuleContext {
navbar: Array<NavBarGroupRef> = [];
sidebar: Array<SidebarTabRef> = [];
authGuards: { [key: string]: Type<{}> } = {};
components: { [key: string]: Type<{}> } = {};
evaluators: { [key: string]: RuleEvaluator } = {};
selection: SelectionState;
navigation: NavigationState;
profile: ProfileState;
constructor(
private store: Store<AppStore>,
private loader: ExtensionLoaderService,
private extensions: ExtensionService,
public permissions: NodePermissionService) {
this.evaluators = {
'core.every': core.every,
'core.some': core.some,
'core.not': core.not
};
this.store.select(selectionWithFolder).subscribe(result => {
this.store.select(ruleContext).subscribe(result => {
this.selection = result.selection;
this.navigation = result.navigation;
this.profile = result.profile;
});
}
async load() {
const config = await this.loader.load(this.configPath, this.pluginsPath);
const config = await this.extensions.load();
this.setup(config);
}
@ -100,9 +85,6 @@ export class ExtensionService implements RuleContext {
return;
}
this.rules = this.loader.getRules(config);
this.actions = this.loader.getActions(config);
this.routes = this.loader.getRoutes(config);
this.toolbarActions = this.loader.getContentActions(config, 'features.toolbar');
this.viewerToolbarActions = this.loader.getContentActions(config, 'features.viewer.toolbar');
this.viewerContentExtensions = this.loader.getElements<ViewerExtensionRef>(config, 'features.viewer.content');
@ -123,7 +105,7 @@ export class ExtensionService implements RuleContext {
.filter(item => !item.disabled)
.sort(sortByOrder)
.map(item => {
const routeRef = this.getRouteById(item.route);
const routeRef = this.extensions.getRouteById(item.route);
const url = `/${routeRef ? routeRef.path : item.route}`;
return {
...item,
@ -134,34 +116,6 @@ export class ExtensionService implements RuleContext {
});
}
setEvaluators(values: { [key: string]: RuleEvaluator }) {
if (values) {
this.evaluators = Object.assign({}, this.evaluators, values);
}
}
setAuthGuards(values: { [key: string]: Type<{}> }) {
if (values) {
this.authGuards = Object.assign({}, this.authGuards, values);
}
}
setComponents(values: { [key: string]: Type<{}> }) {
if (values) {
this.components = Object.assign({}, this.components, values);
}
}
getRouteById(id: string): RouteRef {
return this.routes.find(route => route.id === id);
}
getAuthGuards(ids: string[]): Array<Type<{}>> {
return (ids || [])
.map(id => this.authGuards[id])
.filter(guard => guard);
}
getNavigationGroups(): Array<NavBarGroupRef> {
return this.navbar;
}
@ -171,12 +125,12 @@ export class ExtensionService implements RuleContext {
}
getComponentById(id: string): Type<{}> {
return this.components[id];
return this.extensions.getComponentById(id);
}
getApplicationRoutes(): Array<Route> {
return this.routes.map(route => {
const guards = this.getAuthGuards(
return this.extensions.routes.map(route => {
const guards = this.extensions.getAuthGuards(
route.auth && route.auth.length > 0
? route.auth
: this.defaults.auth
@ -201,13 +155,12 @@ export class ExtensionService implements RuleContext {
getCreateActions(): Array<ContentActionRef> {
return this.createActions
.filter(filterEnabled)
.filter(action => this.filterByRules(action))
.map(action => {
let disabled = false;
if (action.rules && action.rules.enabled) {
disabled = !this.evaluateRule(action.rules.enabled);
disabled = !this.extensions.evaluateRule(action.rules.enabled, this);
}
return {
@ -220,7 +173,6 @@ export class ExtensionService implements RuleContext {
// evaluates content actions for the selection and parent folder node
getAllowedToolbarActions(): Array<ContentActionRef> {
return this.toolbarActions
.filter(filterEnabled)
.filter(action => this.filterByRules(action))
.map(action => {
if (action.type === ContentActionType.menu) {
@ -242,13 +194,11 @@ export class ExtensionService implements RuleContext {
getViewerToolbarActions(): Array<ContentActionRef> {
return this.viewerToolbarActions
.filter(filterEnabled)
.filter(action => this.filterByRules(action));
}
getAllowedContextMenuActions(): Array<ContentActionRef> {
return this.contextMenuActions
.filter(filterEnabled)
.filter(action => this.filterByRules(action));
}
@ -263,20 +213,16 @@ export class ExtensionService implements RuleContext {
filterByRules(action: ContentActionRef): boolean {
if (action && action.rules && action.rules.visible) {
return this.evaluateRule(action.rules.visible);
return this.extensions.evaluateRule(action.rules.visible, this);
}
return true;
}
getActionById(id: string): ActionRef {
return this.actions.find(action => action.id === id);
}
runActionById(id: string, context?: any) {
const action = this.getActionById(id);
const action = this.extensions.getActionById(id);
if (action) {
const { type, payload } = action;
const expression = this.runExpression(payload, context);
const expression = this.extensions.runExpression(payload, context);
this.store.dispatch({ type, payload: expression });
} else {
@ -284,45 +230,7 @@ export class ExtensionService implements RuleContext {
}
}
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;
}
getEvaluator(key: string): RuleEvaluator {
if (key && key.startsWith('!')) {
const fn = this.evaluators[key.substring(1)];
return (context: RuleContext, ...args: RuleParameter[]): boolean => {
return !fn(context, ...args);
};
}
return this.evaluators[key];
}
evaluateRule(ruleId: string): boolean {
const ruleRef = this.rules.find(ref => ref.id === ruleId);
if (ruleRef) {
const evaluator = this.getEvaluator(ruleRef.type);
if (evaluator) {
return evaluator(this, ...ruleRef.parameters);
}
} else {
const evaluator = this.getEvaluator(ruleId);
if (evaluator) {
return evaluator(this);
}
}
return false;
return this.extensions.getEvaluator(key);
}
}

@ -1,39 +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 { ExtensionElement } from './extension-element';
export interface NavBarGroupRef extends ExtensionElement {
items: Array<NavBarLinkRef>;
}
export interface NavBarLinkRef extends ExtensionElement {
icon: string;
title: string;
route: string;
url?: string; // evaluated at runtime based on route ref
description?: string;
}

@ -1,28 +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 NodePermissions {
check(source: any, permissions: string[], options?: any): boolean;
}

@ -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/>.
*/
export interface RouteRef {
id: string;
path: string;
component: string;
layout?: string;
auth?: string[];
data?: { [key: string]: string };
}

@ -1,50 +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 { SelectionState } from '../store/states';
import { NavigationState } from '../store/states/navigation.state';
import { NodePermissions } from './permission.extensions';
export type RuleEvaluator = (context: RuleContext, ...args: any[]) => boolean;
export interface RuleContext {
selection: SelectionState;
navigation: NavigationState;
permissions: NodePermissions;
getEvaluator(key: string): RuleEvaluator;
}
export class RuleRef {
type: string;
id?: string;
parameters?: Array<RuleParameter>;
}
export interface RuleParameter {
type: string;
value: any;
parameters?: Array<RuleParameter>;
}

@ -1,37 +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 { ExtensionElement } from './extension-element';
export interface SidebarTabRef extends ExtensionElement {
title: string;
component: string;
icon?: string;
rules?: {
visible?: string;
[key: string]: string;
};
}

@ -1,31 +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 { ExtensionElement } from './extension-element';
export interface ViewerExtensionRef extends ExtensionElement {
fileExtension: string;
component: string;
}

@ -24,7 +24,7 @@
*/
import { Injectable } from '@angular/core';
import { NodePermissions } from '../extensions/permission.extensions';
import { NodePermissions } from '@alfresco/adf-extensions';
@Injectable()
export class NodePermissionService implements NodePermissions {

@ -39,13 +39,15 @@ export const currentFolder = createSelector(selectApp, state => state.navigation
export const infoDrawerOpened = createSelector(selectApp, state => state.infoDrawerOpened);
export const documentDisplayMode = createSelector(selectApp, state => state.documentDisplayMode);
export const selectionWithFolder = createSelector(
export const ruleContext = createSelector(
appSelection,
appNavigation,
(selection, navigation) => {
selectUser,
(selection, navigation, profile) => {
return {
selection,
navigation
navigation,
profile
};
}
);

@ -24,5 +24,3 @@
*/
export * from './states/app.state';
export * from './states/profile.state';
export * from './states/selection.state';

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

@ -1,31 +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 { Node } from 'alfresco-js-api';
export interface NavigationState {
currentFolder?: Node;
url?: string;
}

@ -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 ProfileState {
id: string;
isAdmin: boolean;
firstName: string;
lastName: string;
userName?: string;
initials?: string;
}

@ -1,38 +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 { MinimalNodeEntity, SiteEntry } from 'alfresco-js-api';
export interface SelectionState {
count: number;
nodes: MinimalNodeEntity[];
libraries: SiteEntry[];
isEmpty: boolean;
first?: MinimalNodeEntity;
last?: MinimalNodeEntity;
folder?: MinimalNodeEntity;
file?: MinimalNodeEntity;
library?: SiteEntry;
}

@ -59,9 +59,9 @@ import { ContentManagementService } from '../services/content-management.service
import { NodeActionsService } from '../services/node-actions.service';
import { NodePermissionService } from '../services/node-permission.service';
import { ContentApiService } from '../services/content-api.service';
import { ExtensionService } from '../extensions/extension.service';
import { AppExtensionService } from '../extensions/extension.service';
import { ViewUtilService } from '../components/preview/view-util.service';
import { ExtensionLoaderService } from '../extensions/extension-loader.service';
import { ExtensionLoaderService, ExtensionService } from '@alfresco/adf-extensions';
@NgModule({
imports: [
@ -114,6 +114,7 @@ import { ExtensionLoaderService } from '../extensions/extension-loader.service';
NodeActionsService,
NodePermissionService,
ContentApiService,
AppExtensionService,
ExtensionService,
ExtensionLoaderService,
ViewUtilService

@ -24,6 +24,12 @@
],
"aca-dev-tools/*": [
"dist/aca-dev-tools/*"
],
"@alfresco/adf-extensions": [
"dist/@alfresco/adf-extensions"
],
"@alfresco/adf-extensions/*": [
"dist/@alfresco/adf-extensions/*"
]
}
},