shared library (#1080)

* shared project scaffold

* rules package

* move evaluators to shared lib

* add rxjs peer dependency

* use dedicated material namespaces

* create store package, move actions

* move selectors to shared library

* move generic effects to shared lib

* move routing extensions

* minor code reorg

* fix unit tests

* move content-api service

* move permission service

* update tests

* update plint config

* move page layout

* css variables

* use dedicated css property

* move generic error component to shared lib

* fix test
This commit is contained in:
Denys Vuika
2019-04-25 14:56:54 +01:00
committed by GitHub
parent f3c5ffb977
commit 9db1c2989f
175 changed files with 1552 additions and 1153 deletions

View File

@@ -0,0 +1,4 @@
<mat-icon>ic_error</mat-icon>
<p class="generic-error__title">
{{ text | translate }}
</p>

View File

@@ -0,0 +1,22 @@
.aca-generic-error {
color: var(--theme-text-color, rgba(0, 0, 0, 0.54));
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
width: 100%;
height: 100%;
&__title {
font-size: 16px;
}
mat-icon {
color: var(--theme-warn-color, #f44336);
direction: rtl;
font-size: 52px;
height: 52px;
width: 52px;
}
}

View File

@@ -0,0 +1,32 @@
/*!
* @license
* Alfresco Example Content Application
*
* Copyright (C) 2005 - 2019 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 { GenericErrorComponent } from './generic-error.component';
describe('GenericErrorComponent', () => {
it('should be defined', () => {
expect(GenericErrorComponent).toBeDefined();
});
});

View File

@@ -0,0 +1,44 @@
/*!
* @license
* Alfresco Example Content Application
*
* Copyright (C) 2005 - 2019 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,
ViewEncapsulation,
ChangeDetectionStrategy,
Input
} from '@angular/core';
@Component({
selector: 'aca-generic-error',
templateUrl: './generic-error.component.html',
styleUrls: ['./generic-error.component.scss'],
encapsulation: ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush,
host: { class: 'aca-generic-error' }
})
export class GenericErrorComponent {
@Input()
text = 'APP.MESSAGES.ERRORS.MISSING_CONTENT';
}

View File

@@ -0,0 +1,37 @@
/*!
* @license
* Alfresco Example Content Application
*
* Copyright (C) 2005 - 2019 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 { NgModule } from '@angular/core';
import { GenericErrorComponent } from './generic-error.component';
import { MatIconModule } from '@angular/material/icon';
import { CommonModule } from '@angular/common';
import { TranslateModule } from '@ngx-translate/core';
@NgModule({
imports: [CommonModule, MatIconModule, TranslateModule.forChild()],
declarations: [GenericErrorComponent],
exports: [GenericErrorComponent]
})
export class GenericErrorModule {}

View File

@@ -0,0 +1,47 @@
/*!
* @license
* Alfresco Example Content Application
*
* Copyright (C) 2005 - 2019 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,
ViewEncapsulation,
ChangeDetectionStrategy,
Input,
HostBinding
} from '@angular/core';
@Component({
selector: 'aca-page-layout-content',
template: `
<ng-content></ng-content>
`,
encapsulation: ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush,
host: { class: 'aca-page-layout-content' }
})
export class PageLayoutContentComponent {
@Input()
@HostBinding('class.scrollable')
scrollable = false;
}

View File

@@ -0,0 +1,41 @@
/*!
* @license
* Alfresco Example Content Application
*
* Copyright (C) 2005 - 2019 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,
ViewEncapsulation,
ChangeDetectionStrategy
} from '@angular/core';
@Component({
selector: 'aca-page-layout-error',
template: `
<ng-content></ng-content>
`,
encapsulation: ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush,
host: { class: 'aca-page-layout-error' }
})
export class PageLayoutErrorComponent {}

View File

@@ -0,0 +1,39 @@
/*!
* @license
* Alfresco Example Content Application
*
* Copyright (C) 2005 - 2019 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,
ViewEncapsulation,
ChangeDetectionStrategy
} from '@angular/core';
@Component({
selector: 'aca-page-layout-header',
template: '<ng-content></ng-content>',
encapsulation: ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush,
host: { class: 'aca-page-layout-header' }
})
export class PageLayoutHeaderComponent {}

View File

@@ -0,0 +1,3 @@
<ng-content select="aca-page-layout-header"></ng-content>
<ng-content select="aca-page-layout-error" *ngIf="hasError"></ng-content>
<ng-content select="aca-page-layout-content" *ngIf="!hasError"></ng-content>

View File

@@ -0,0 +1,44 @@
@import '../../styles/mixins.scss';
.aca-page-layout {
@include flex-column;
.aca-page-layout-header {
display: flex;
align-items: center;
flex: 0 0 65px;
flex-basis: 48px;
background: #fafafa;
border-bottom: 1px solid var(--theme-border-color, rgba(0, 0, 0, 0.07));
padding: 0 24px;
}
.aca-page-layout-content {
@include flex-row;
}
.aca-page-layout-error {
@include flex-row;
}
.main-content {
@include flex-column;
border-right: 1px solid var(--theme-border-color, rgba(0, 0, 0, 0.07));
}
.scrollable {
overflow: auto !important;
.main-content {
overflow: auto !important;
}
}
.sidebar {
display: block;
height: 100%;
overflow-y: scroll;
max-width: 350px;
width: 350px;
}
}

View File

@@ -0,0 +1,44 @@
/*!
* @license
* Alfresco Example Content Application
*
* Copyright (C) 2005 - 2019 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,
ViewEncapsulation,
ChangeDetectionStrategy,
Input
} from '@angular/core';
@Component({
selector: 'aca-page-layout',
templateUrl: 'page-layout.component.html',
styleUrls: ['./page-layout.component.scss'],
encapsulation: ViewEncapsulation.None,
host: { class: 'aca-page-layout' },
changeDetection: ChangeDetectionStrategy.OnPush
})
export class PageLayoutComponent {
@Input()
hasError = false;
}

View File

@@ -0,0 +1,48 @@
/*!
* @license
* Alfresco Example Content Application
*
* Copyright (C) 2005 - 2019 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 { NgModule } from '@angular/core';
import { PageLayoutContentComponent } from './page-layout-content.component';
import { PageLayoutErrorComponent } from './page-layout-error.component';
import { PageLayoutHeaderComponent } from './page-layout-header.component';
import { PageLayoutComponent } from './page-layout.component';
import { CommonModule } from '@angular/common';
@NgModule({
imports: [CommonModule],
declarations: [
PageLayoutContentComponent,
PageLayoutErrorComponent,
PageLayoutHeaderComponent,
PageLayoutComponent
],
exports: [
PageLayoutContentComponent,
PageLayoutErrorComponent,
PageLayoutHeaderComponent,
PageLayoutComponent
]
})
export class PageLayoutModule {}

View File

@@ -0,0 +1,86 @@
/*!
* @license
* Alfresco Example Content Application
*
* Copyright (C) 2005 - 2019 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 { AppRouteReuseStrategy } from './app.routes.strategy';
import { TestBed } from '@angular/core/testing';
describe('AppRouteReuseStrategy', () => {
let appRouteReuse: AppRouteReuseStrategy;
beforeEach(() => {
TestBed.configureTestingModule({
providers: [AppRouteReuseStrategy]
});
appRouteReuse = TestBed.get(AppRouteReuseStrategy);
});
it('should allow detach if route is configured to be reused', () => {
const route = <any>{
routeConfig: {
data: {
reuse: true
},
path: 'tested-path'
}
};
expect(appRouteReuse.shouldDetach(<any>route)).toBe(true);
});
it('should store on routeCache', () => {
const route = <any>{
url: [],
routeConfig: {
data: {
reuse: true
},
path: 'tested-path',
component: {}
},
firstChild: null,
children: []
};
appRouteReuse.store(route, { route: {} });
expect(appRouteReuse.shouldAttach(<any>route)).toBe(true);
});
it('should clear routeCache on resetCache', () => {
const route = <any>{
url: [],
routeConfig: {
data: {
reuse: true
},
path: 'tested-path',
component: {}
},
firstChild: null,
children: []
};
appRouteReuse.store(route, { route: {} });
appRouteReuse.resetCache();
expect(appRouteReuse.shouldAttach(<any>route)).toBe(false);
});
});

View File

@@ -0,0 +1,142 @@
/*!
* @license
* Alfresco Example Content Application
*
* Copyright (C) 2005 - 2019 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 {
RouteReuseStrategy,
DetachedRouteHandle,
ActivatedRouteSnapshot
} from '@angular/router';
import { ComponentRef } from '@angular/core';
interface RouteData {
reuse: boolean;
}
interface RouteInfo {
handle: DetachedRouteHandle;
data: RouteData;
}
export class AppRouteReuseStrategy implements RouteReuseStrategy {
private routeCache = new Map<string, RouteInfo>();
resetCache() {
this.routeCache.forEach(value => {
this.deactivateComponent(value.handle);
});
this.routeCache.clear();
}
private deactivateComponent(handle: DetachedRouteHandle): void {
if (!handle) {
return;
}
const componentRef: ComponentRef<any> = handle['componentRef'];
if (componentRef) {
componentRef.destroy();
}
}
shouldReuseRoute(
future: ActivatedRouteSnapshot,
curr: ActivatedRouteSnapshot
): boolean {
const ret = future.routeConfig === curr.routeConfig;
if (ret) {
this.addRedirectsRecursively(future); // update redirects
}
return ret;
}
shouldDetach(route: ActivatedRouteSnapshot): boolean {
const data = this.getRouteData(route);
return data && data.reuse;
}
store(route: ActivatedRouteSnapshot, handle: DetachedRouteHandle): void {
const url = this.getFullRouteUrl(route);
const data = this.getRouteData(route);
this.routeCache.set(url, { handle, data });
this.addRedirectsRecursively(route);
}
shouldAttach(route: ActivatedRouteSnapshot): boolean {
const url = this.getFullRouteUrl(route);
return this.routeCache.has(url);
}
retrieve(route: ActivatedRouteSnapshot): DetachedRouteHandle {
const url = this.getFullRouteUrl(route);
const data = this.getRouteData(route);
return data && data.reuse && this.routeCache.has(url)
? this.routeCache.get(url).handle
: null;
}
private addRedirectsRecursively(route: ActivatedRouteSnapshot): void {
const config = route.routeConfig;
if (config) {
if (!config.loadChildren) {
const routeFirstChild = route.firstChild;
const routeFirstChildUrl = routeFirstChild
? this.getRouteUrlPaths(routeFirstChild).join('/')
: '';
const childConfigs = config.children;
if (childConfigs) {
const childConfigWithRedirect = childConfigs.find(
c => c.path === '' && !!c.redirectTo
);
if (childConfigWithRedirect) {
childConfigWithRedirect.redirectTo = routeFirstChildUrl;
}
}
}
route.children.forEach(childRoute =>
this.addRedirectsRecursively(childRoute)
);
}
}
private getFullRouteUrl(route: ActivatedRouteSnapshot): string {
return this.getFullRouteUrlPaths(route)
.filter(Boolean)
.join('/');
}
private getFullRouteUrlPaths(route: ActivatedRouteSnapshot): string[] {
const paths = this.getRouteUrlPaths(route);
return route.parent
? [...this.getFullRouteUrlPaths(route.parent), ...paths]
: paths;
}
private getRouteUrlPaths(route: ActivatedRouteSnapshot): string[] {
return route.url.map(urlSegment => urlSegment.path);
}
private getRouteData(route: ActivatedRouteSnapshot): RouteData {
return route.routeConfig && (route.routeConfig.data as RouteData);
}
}

View File

@@ -0,0 +1,54 @@
/*!
* @license
* Alfresco Example Content Application
*
* Copyright (C) 2005 - 2019 Alfresco Software Limited
*
* This file is part of the Alfresco Example Content Application.
* If the software was purchased under a paid Alfresco license, the terms of
* the paid license agreement will prevail. Otherwise, the software is
* provided under the following open source license terms:
*
* The Alfresco Example Content Application is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* The Alfresco Example Content Application is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
import { Injectable } from '@angular/core';
import { CanActivate } from '@angular/router';
import { Observable } from 'rxjs';
import { ActivatedRouteSnapshot } from '@angular/router';
import { Store } from '@ngrx/store';
import { AppStore, isQuickShareEnabled } from '@alfresco/aca-shared/store';
@Injectable({
providedIn: 'root'
})
export class AppSharedRuleGuard implements CanActivate {
isQuickShareEnabled$: Observable<boolean>;
constructor(store: Store<AppStore>) {
this.isQuickShareEnabled$ = store.select(isQuickShareEnabled);
}
canActivate(
_: ActivatedRouteSnapshot
): Observable<boolean> | Promise<boolean> | boolean {
return this.isQuickShareEnabled$;
}
canActivateChild(
route: ActivatedRouteSnapshot
): Observable<boolean> | Promise<boolean> | boolean {
return this.canActivate(route);
}
}

View File

@@ -0,0 +1,96 @@
/*!
* @license
* Alfresco Example Content Application
*
* Copyright (C) 2005 - 2019 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 { AppService } from './app.service';
import { TestBed } from '@angular/core/testing';
import { AuthenticationService, AppConfigService } from '@alfresco/adf-core';
import { Subject } from 'rxjs';
import { HttpClientModule } from '@angular/common/http';
import { AppRouteReuseStrategy } from '../routing/app.routes.strategy';
describe('AppService', () => {
let service: AppService;
let auth: AuthenticationService;
let routeReuse: AppRouteReuseStrategy;
let appConfig: AppConfigService;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [HttpClientModule],
providers: [
AppRouteReuseStrategy,
{
provide: AuthenticationService,
useValue: {
onLogin: new Subject<any>(),
onLogout: new Subject<any>(),
isLoggedIn: () => false
}
}
]
});
routeReuse = TestBed.get(AppRouteReuseStrategy);
auth = TestBed.get(AuthenticationService);
appConfig = TestBed.get(AppConfigService);
spyOn(routeReuse, 'resetCache').and.stub();
service = new AppService(auth, appConfig, routeReuse);
});
it('should be ready if [withCredentials] mode is used', done => {
appConfig.config = {
auth: {
withCredentials: true
}
};
const instance = new AppService(auth, appConfig, routeReuse);
expect(instance.withCredentials).toBeTruthy();
instance.ready$.subscribe(() => {
done();
});
});
it('should reset route cache on login', async () => {
auth.onLogin.next();
await expect(routeReuse.resetCache).toHaveBeenCalled();
});
it('should reset route cache on logout', async () => {
auth.onLogout.next();
await expect(routeReuse.resetCache).toHaveBeenCalled();
});
it('should be ready after login', async () => {
let isReady = false;
service.ready$.subscribe(value => {
isReady = value;
});
auth.onLogin.next();
await expect(<any>isReady).toEqual(true);
});
});

View File

@@ -0,0 +1,64 @@
/*!
* @license
* Alfresco Example Content Application
*
* Copyright (C) 2005 - 2019 Alfresco Software Limited
*
* This file is part of the Alfresco Example Content Application.
* If the software was purchased under a paid Alfresco license, the terms of
* the paid license agreement will prevail. Otherwise, the software is
* provided under the following open source license terms:
*
* The Alfresco Example Content Application is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* The Alfresco Example Content Application is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
import { Injectable, Inject } from '@angular/core';
import { AuthenticationService, AppConfigService } from '@alfresco/adf-core';
import { Observable, BehaviorSubject } from 'rxjs';
import { RouteReuseStrategy } from '@angular/router';
import { AppRouteReuseStrategy } from '../routing/app.routes.strategy';
@Injectable({
providedIn: 'root'
})
export class AppService {
private ready: BehaviorSubject<boolean>;
ready$: Observable<boolean>;
/**
* Whether `withCredentials` mode is enabled.
* Usually means that `Kerberos` mode is used.
*/
get withCredentials(): boolean {
return this.config.get<boolean>('auth.withCredentials', false);
}
constructor(
auth: AuthenticationService,
private config: AppConfigService,
@Inject(RouteReuseStrategy) routeStrategy: AppRouteReuseStrategy
) {
this.ready = new BehaviorSubject(auth.isLoggedIn() || this.withCredentials);
this.ready$ = this.ready.asObservable();
auth.onLogin.subscribe(() => {
routeStrategy.resetCache();
this.ready.next(true);
});
auth.onLogout.subscribe(() => {
routeStrategy.resetCache();
});
}
}

View File

@@ -0,0 +1,32 @@
/*!
* @license
* Alfresco Example Content Application
*
* Copyright (C) 2005 - 2019 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 { ContentApiService } from './content-api.service';
describe('ContentApiService', () => {
it('should be defined', () => {
expect(ContentApiService).toBeDefined();
});
});

View File

@@ -0,0 +1,293 @@
/*!
* @license
* Alfresco Example Content Application
*
* Copyright (C) 2005 - 2019 Alfresco Software Limited
*
* This file is part of the Alfresco Example Content Application.
* If the software was purchased under a paid Alfresco license, the terms of
* the paid license agreement will prevail. Otherwise, the software is
* provided under the following open source license terms:
*
* The Alfresco Example Content Application is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* The Alfresco Example Content Application is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
import { Injectable } from '@angular/core';
import { AlfrescoApiService, UserPreferencesService } from '@alfresco/adf-core';
import { Observable, from } from 'rxjs';
import {
MinimalNodeEntity,
NodePaging,
Node,
DeletedNodesPaging,
PersonEntry,
NodeEntry,
DiscoveryEntry,
FavoritePaging,
SharedLinkPaging,
SearchRequest,
ResultSetPaging,
SiteBody,
SiteEntry,
FavoriteBody,
FavoriteEntry
} from '@alfresco/js-api';
import { map } from 'rxjs/operators';
@Injectable({
providedIn: 'root'
})
export class ContentApiService {
constructor(
private api: AlfrescoApiService,
private preferences: UserPreferencesService
) {}
/**
* Moves a node to the trashcan.
* @param nodeId ID of the target node
* @param options Optional parameters supported by JS-API
* @returns Empty result that notifies when the deletion is complete
*/
deleteNode(
nodeId: string,
options: { permanent?: boolean } = {}
): Observable<void> {
return from(this.api.nodesApi.deleteNode(nodeId, options));
}
/**
* Gets the stored information about a node.
* @param nodeId ID of the target node
* @param options Optional parameters supported by JS-API
* @returns Node information
*/
getNode(nodeId: string, options: any = {}): Observable<MinimalNodeEntity> {
const defaults = {
include: ['path', 'properties', 'allowableOperations', 'permissions']
};
const queryOptions = Object.assign(defaults, options);
return from(this.api.nodesApi.getNode(nodeId, queryOptions));
}
getNodeInfo(nodeId: string, options?: any): Observable<Node> {
const defaults = {
include: ['isFavorite', 'allowableOperations']
};
const queryOptions = Object.assign(defaults, options || {});
return from(this.api.nodesApi.getNodeInfo(nodeId, queryOptions));
}
/**
* Gets the items contained in a folder node.
* @param nodeId ID of the target node
* @param options Optional parameters supported by JS-API
* @returns List of child items from the folder
*/
getNodeChildren(nodeId: string, options: any = {}): Observable<NodePaging> {
const defaults = {
maxItems: this.preferences.paginationSize,
skipCount: 0,
include: [
'isLocked',
'path',
'properties',
'allowableOperations',
'permissions'
]
};
const queryOptions = Object.assign(defaults, options);
return from(this.api.nodesApi.getNodeChildren(nodeId, queryOptions));
}
deleteSharedLink(linkId: string): Observable<any> {
return from(this.api.sharedLinksApi.deleteSharedLink(linkId));
}
getDeletedNodes(options: any = {}): Observable<DeletedNodesPaging> {
const defaults = {
include: ['path']
};
const queryOptions = Object.assign(defaults, options);
return from(this.api.nodesApi.getDeletedNodes(queryOptions));
}
restoreNode(nodeId: string): Observable<MinimalNodeEntity> {
return from(this.api.nodesApi.restoreNode(nodeId));
}
purgeDeletedNode(nodeId: string): Observable<any> {
return from(this.api.nodesApi.purgeDeletedNode(nodeId));
}
/**
* Gets information about a user identified by their username.
* @param personId ID of the target user
* @returns User information
*/
getPerson(
personId: string,
options?: { fields?: Array<string> }
): Observable<PersonEntry> {
return from(this.api.peopleApi.getPerson(personId, options));
}
/**
* Copy a node to destination node
*
* @param nodeId The id of the node to be copied
* @param targetParentId The id of the folder-node where the node have to be copied to
* @param name The new name for the copy that would be added on the destination folder
*/
copyNode(
nodeId: string,
targetParentId: string,
name?: string,
opts?: { include?: Array<string>; fields?: Array<string> }
): Observable<NodeEntry> {
return from(
this.api.nodesApi.copyNode(nodeId, { targetParentId, name }, opts)
);
}
/**
* Gets product information for Content Services.
* @returns ProductVersionModel containing product details
*/
getRepositoryInformation(): Observable<DiscoveryEntry> {
return from(
this.api.getInstance().discovery.discoveryApi.getRepositoryInformation()
);
}
getFavorites(
personId: string,
opts?: {
skipCount?: number;
maxItems?: number;
where?: string;
fields?: Array<string>;
}
): Observable<FavoritePaging> {
return from(this.api.favoritesApi.getFavorites(personId, opts));
}
getFavoriteLibraries(
personId: string = '-me-',
opts?: any
): Observable<FavoritePaging> {
return this.getFavorites(personId, {
...opts,
where: '(EXISTS(target/site))'
}).pipe(
map((response: FavoritePaging) => {
return {
list: {
entries: response.list.entries.map(({ entry }: any) => {
entry.target.site.createdAt = entry.createdAt;
return {
entry: entry.target.site
};
}),
pagination: response.list.pagination
}
};
})
);
}
findSharedLinks(opts?: any): Observable<SharedLinkPaging> {
return from(this.api.sharedLinksApi.findSharedLinks(opts));
}
getSharedLinkContent(sharedId: string, attachment?: boolean): string {
return this.api.contentApi.getSharedLinkContentUrl(sharedId, attachment);
}
search(request: SearchRequest): Observable<ResultSetPaging> {
return from(this.api.searchApi.search(request));
}
getContentUrl(nodeId: string, attachment?: boolean): string {
return this.api.contentApi.getContentUrl(nodeId, attachment);
}
deleteSite(siteId?: string, opts?: { permanent?: boolean }): Observable<any> {
return from(this.api.sitesApi.deleteSite(siteId, opts));
}
leaveSite(siteId?: string): Observable<any> {
return from(this.api.sitesApi.removeSiteMember(siteId, '-me-'));
}
createSite(
siteBody: SiteBody,
opts?: {
fields?: Array<string>;
skipConfiguration?: boolean;
skipAddToFavorites?: boolean;
}
): Observable<SiteEntry> {
return from(this.api.sitesApi.createSite(siteBody, opts));
}
getSite(
siteId?: string,
opts?: { relations?: Array<string>; fields?: Array<string> }
): Observable<SiteEntry> {
return from(this.api.sitesApi.getSite(siteId, opts));
}
updateLibrary(siteId: string, siteBody: SiteBody): Observable<SiteEntry> {
return from(this.api.sitesApi.updateSite(siteId, siteBody));
}
addFavorite(nodes: Array<MinimalNodeEntity>): Observable<FavoriteEntry> {
const payload: FavoriteBody[] = nodes.map(node => {
const { isFolder, nodeId, id } = <any>node.entry;
const siteId = node.entry['guid'];
const type = siteId ? 'site' : isFolder ? 'folder' : 'file';
const guid = siteId || nodeId || id;
return {
target: {
[type]: {
guid
}
}
};
});
return from(this.api.favoritesApi.addFavorite('-me-', <any>payload));
}
removeFavorite(nodes: Array<MinimalNodeEntity>): Observable<any> {
return from(
Promise.all(
nodes.map((node: any) => {
const id = node.entry.nodeId || node.entry.id;
return this.api.favoritesApi.removeFavoriteSite('-me-', id);
})
)
);
}
unlockNode(nodeId: string, opts?: any) {
return this.api.nodesApi.unlockNode(nodeId, opts);
}
}

View File

@@ -0,0 +1,226 @@
/*!
* @license
* Alfresco Example Content Application
*
* Copyright (C) 2005 - 2019 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 { NodePermissionService } from './node-permission.service';
describe('NodePermissionService', () => {
let permission: NodePermissionService;
beforeEach(() => {
permission = new NodePermissionService();
});
it('should return false when source is null', () => {
const source = null;
expect(permission.check(source, ['update'])).toBe(false);
});
describe('Multiple source permission', () => {
it('should return true when source has allowableOperations permission', () => {
const source = [
{ entry: { allowableOperations: ['update'] } },
{ entry: { allowableOperations: ['update'] } },
{ entry: { allowableOperations: ['update'] } }
];
expect(permission.check(source, ['update'])).toBe(true);
});
it('should return true when source has allowableOperationsOnTarget permission', () => {
const source = [
{ entry: { allowableOperationsOnTarget: ['update'] } },
{ entry: { allowableOperationsOnTarget: ['update'] } },
{ entry: { allowableOperationsOnTarget: ['update'] } }
];
expect(
permission.check(source, ['update'], {
target: 'allowableOperationsOnTarget'
})
).toBe(true);
});
it('should return false when source does not have allowableOperations permission', () => {
const source = [
{ entry: { allowableOperations: ['update'] } },
{ entry: { allowableOperations: ['update'] } },
{ entry: { allowableOperations: ['delete'] } }
];
expect(
permission.check(source, ['update'], {
target: 'allowableOperationsOnTarget'
})
).toBe(false);
});
it('should return false when source does not have allowableOperationsOnTarget permission', () => {
const source = [
{ entry: { allowableOperationsOnTarget: ['update'] } },
{ entry: { allowableOperationsOnTarget: ['update'] } },
{ entry: { allowableOperationsOnTarget: ['delete'] } }
];
expect(
permission.check(source, ['update'], {
target: 'allowableOperationsOnTarget'
})
).toBe(false);
});
it('should return true when source has `OR` allowableOperations permission', () => {
const source = [
{ entry: { allowableOperations: ['update', 'delete'] } },
{ entry: { allowableOperations: ['update', 'create'] } },
{ entry: { allowableOperations: ['update', 'updatePermissions'] } }
];
expect(permission.check(source, ['update', 'create'])).toBe(true);
});
it('should return true when source has `AND` allowableOperations permission', () => {
const source = [
{ entry: { allowableOperations: ['update', 'delete', 'other'] } },
{ entry: { allowableOperations: ['update', 'create', 'other'] } },
{
entry: {
allowableOperations: ['update', 'updatePermissions', 'other']
}
}
];
expect(
permission.check(source, ['update', 'other'], { operation: 'AND' })
).toBe(true);
});
it('should return false when source has no `AND` allowableOperations permission', () => {
const source = [
{ entry: { allowableOperations: ['update', 'delete', 'other'] } },
{ entry: { allowableOperations: ['update', 'create', 'other'] } },
{
entry: {
allowableOperations: ['update', 'updatePermissions', 'other']
}
}
];
expect(
permission.check(source, ['update', 'bogus'], { operation: 'AND' })
).toBe(false);
});
it('should return false when source has no allowableOperations', () => {
const source = [
{ entry: { allowableOperations: [] } },
{ entry: { allowableOperations: [] } },
{ entry: { allowableOperations: ['update'] } }
];
expect(permission.check(source, ['update'])).toBe(false);
});
it('should return false when source has no allowableOperations property', () => {
const source = [
{ entry: {} },
{ entry: {} },
{ entry: { allowableOperations: ['update'] } }
];
expect(permission.check(source, ['update'])).toBe(false);
});
});
describe('Single source permission', () => {
it('should return true when source has allowableOperations permission', () => {
const source = { entry: { allowableOperations: ['update'] } };
expect(permission.check(source, ['update'])).toBe(true);
});
it('should return true when source has allowableOperationsOnTarget permission', () => {
const source = { entry: { allowableOperationsOnTarget: ['update'] } };
expect(
permission.check(source, ['update'], {
target: 'allowableOperationsOnTarget'
})
).toBe(true);
});
it('should return false when source does not have allowableOperations permission', () => {
const source = { entry: { allowableOperations: ['delete'] } };
expect(permission.check(source, ['update'])).toBe(false);
});
it('should return false when source does not have allowableOperationsOnTarget permission', () => {
const source = { entry: { allowableOperationsOnTarget: ['delete'] } };
expect(
permission.check(source, ['update'], {
target: 'allowableOperationsOnTarget'
})
).toBe(false);
});
it('should return true when source has `OR` allowableOperations permission', () => {
const source = { entry: { allowableOperations: ['update'] } };
expect(permission.check(source, ['update', 'create'])).toBe(true);
});
it('should return true when source has `AND` allowableOperations permission', () => {
const source = { entry: { allowableOperations: ['update', 'other'] } };
expect(
permission.check(source, ['update', 'other'], { operation: 'AND' })
).toBe(true);
});
it('should return false when source has no `AND` allowableOperations permission', () => {
const source = {
entry: { allowableOperations: ['update', 'updatePermissions', 'other'] }
};
expect(
permission.check(source, ['update', 'bogus'], { operation: 'AND' })
).toBe(false);
});
it('should return false when source has no allowableOperations', () => {
const source = { entry: { allowableOperations: [] } };
expect(permission.check(source, ['update'])).toBe(false);
});
it('should return false when source has no allowableOperations property', () => {
const source = { entry: {} };
expect(permission.check(source, ['update'])).toBe(false);
});
});
});

View File

@@ -0,0 +1,120 @@
/*!
* @license
* Alfresco Example Content Application
*
* Copyright (C) 2005 - 2019 Alfresco Software Limited
*
* This file is part of the Alfresco Example Content Application.
* If the software was purchased under a paid Alfresco license, the terms of
* the paid license agreement will prevail. Otherwise, the software is
* provided under the following open source license terms:
*
* The Alfresco Example Content Application is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* The Alfresco Example Content Application is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
import { Injectable } from '@angular/core';
import { NodePermissions } from '@alfresco/adf-extensions';
import { Node, SharedLink, SharedLinkEntry, NodeEntry } from '@alfresco/js-api';
export type PermissionSource = NodeEntry | SharedLinkEntry | Node;
export interface PermissionOptions {
target?: string;
operation?: string;
}
@Injectable({
providedIn: 'root'
})
export class NodePermissionService implements NodePermissions {
static DEFAULT_OPERATION = 'OR';
private defaultOptions: PermissionOptions = {
operation: NodePermissionService.DEFAULT_OPERATION,
target: null
};
check(
source: PermissionSource | PermissionSource[],
permissions: string[],
options?: PermissionOptions
): boolean {
const opts = Object.assign({}, this.defaultOptions, options || {});
if (!source) {
return false;
}
if (Array.isArray(source)) {
source = source.filter(item => item);
if (source.length > 0) {
return source.every(node =>
this.isOperationAllowed(node, permissions, opts)
);
}
return false;
} else {
return this.isOperationAllowed(source, permissions, opts);
}
}
private isOperationAllowed(
node: PermissionSource,
permissions: string[],
options: PermissionOptions
): boolean {
const allowableOperations = this.getAllowableOperations(
node,
options.target
);
if (allowableOperations.length) {
if (options.operation === NodePermissionService.DEFAULT_OPERATION) {
return permissions.some(permission =>
allowableOperations.includes(permission)
);
} else {
return permissions.every(permission =>
allowableOperations.includes(permission)
);
}
}
return false;
}
private getAllowableOperations(
node: PermissionSource,
property?: string
): string[] {
let entry: Node | SharedLink;
if ('entry' in node) {
entry = node.entry;
} else {
entry = node;
}
if (property) {
return entry[property] || [];
}
if ('allowableOperationsOnTarget' in entry) {
return entry.allowableOperationsOnTarget || [];
} else {
return entry.allowableOperations || [];
}
}
}

View File

@@ -0,0 +1,39 @@
/*!
* @license
* Alfresco Example Content Application
*
* Copyright (C) 2005 - 2019 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 { NgModule, ModuleWithProviders } from '@angular/core';
import { ContentApiService } from './services/content-api.service';
import { NodePermissionService } from './services/node-permission.service';
import { AppService } from './services/app.service';
@NgModule({})
export class SharedModule {
static forRoot(): ModuleWithProviders {
return {
ngModule: SharedModule,
providers: [ContentApiService, NodePermissionService, AppService]
};
}
}

View File

@@ -0,0 +1,16 @@
@mixin flex-column {
display: flex;
flex-direction: column;
flex: 1;
height: 100%;
overflow: hidden;
min-height: 0;
}
@mixin flex-row {
display: flex;
flex-direction: row;
flex: 1;
height: 100%;
overflow: hidden;
}

View File

@@ -0,0 +1,42 @@
/*!
* @license
* Alfresco Example Content Application
*
* Copyright (C) 2005 - 2019 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 * from './lib/components/page-layout/page-layout-content.component';
export * from './lib/components/page-layout/page-layout-error.component';
export * from './lib/components/page-layout/page-layout-header.component';
export * from './lib/components/page-layout/page-layout.component';
export * from './lib/components/page-layout/page-layout.module';
export * from './lib/routing/app.routes.strategy';
export * from './lib/routing/shared.guard';
export * from './lib/services/app.service';
export * from './lib/services/content-api.service';
export * from './lib/services/node-permission.service';
export * from './lib/components/generic-error/generic-error.component';
export * from './lib/components/generic-error/generic-error.module';
export * from './lib/shared.module';