mirror of
https://github.com/Alfresco/alfresco-content-app.git
synced 2025-05-12 17:04:46 +00:00
[ACA-1443] prettier formatting and checks (#629)
* intergrate prettier * update settings * integrate with travis * unified formatting across all files
This commit is contained in:
parent
06402a9c72
commit
883a1971c5
@ -4,7 +4,7 @@ root = true
|
||||
[*]
|
||||
charset = utf-8
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
indent_size = 2
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
|
2
.prettierignore
Normal file
2
.prettierignore
Normal file
@ -0,0 +1,2 @@
|
||||
node_modules
|
||||
src/assets/i18n
|
3
.prettierrc
Normal file
3
.prettierrc
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"singleQuote": true
|
||||
}
|
@ -9,7 +9,7 @@ addons:
|
||||
|
||||
language: node_js
|
||||
node_js:
|
||||
- "8"
|
||||
- '8'
|
||||
|
||||
before_script:
|
||||
# Disable services enabled by default
|
||||
@ -24,7 +24,10 @@ before_install:
|
||||
jobs:
|
||||
include:
|
||||
- stage: test
|
||||
script: npm run lint && npm run spellcheck
|
||||
script:
|
||||
- npm run lint
|
||||
- npm run spellcheck
|
||||
- npm run format:check
|
||||
- stage: test
|
||||
script:
|
||||
- npm run test:ci
|
||||
|
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@ -1,4 +1,5 @@
|
||||
{
|
||||
"javascript.preferences.quoteStyle": "single",
|
||||
"typescript.preferences.quoteStyle": "single"
|
||||
"typescript.preferences.quoteStyle": "single",
|
||||
"editor.formatOnSave": true
|
||||
}
|
||||
|
6
package-lock.json
generated
6
package-lock.json
generated
@ -11444,6 +11444,12 @@
|
||||
"integrity": "sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks=",
|
||||
"dev": true
|
||||
},
|
||||
"prettier": {
|
||||
"version": "1.14.2",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-1.14.2.tgz",
|
||||
"integrity": "sha512-McHPg0n1pIke+A/4VcaS2en+pTNjy4xF+Uuq86u/5dyDO59/TtFZtQ708QIRkEZ3qwKz3GVkVa6mpxK/CpB8Rg==",
|
||||
"dev": true
|
||||
},
|
||||
"pretty-error": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-2.1.1.tgz",
|
||||
|
@ -22,7 +22,8 @@
|
||||
"stop:docker": "docker-compose stop",
|
||||
"e2e:docker": "npm run start:docker && npm run e2e && npm run stop:docker",
|
||||
"spellcheck": "cspell 'src/**/*.ts' 'e2e/**/*.ts' 'projects/**/*.ts'",
|
||||
"inspect.bundle": "ng build app --prod --stats-json && npx webpack-bundle-analyzer dist/app/stats.json"
|
||||
"inspect.bundle": "ng build app --prod --stats-json && npx webpack-bundle-analyzer dist/app/stats.json",
|
||||
"format:check": "prettier --list-different \"src/{app,environments}/**/*{.ts,.js,.json,.css,.scss}\""
|
||||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
@ -89,6 +90,7 @@
|
||||
"karma-jasmine": "~1.1.0",
|
||||
"karma-jasmine-html-reporter": "^0.2.2",
|
||||
"ng-packagr": "^4.1.1",
|
||||
"prettier": "^1.14.2",
|
||||
"protractor": "^5.4.0",
|
||||
"rimraf": "2.6.2",
|
||||
"selenium-webdriver": "4.0.0-alpha.1",
|
||||
|
@ -24,112 +24,119 @@
|
||||
*/
|
||||
|
||||
import {
|
||||
AlfrescoApiService,
|
||||
AppConfigService,
|
||||
AuthenticationService,
|
||||
FileUploadErrorEvent,
|
||||
PageTitleService,
|
||||
UploadService
|
||||
AlfrescoApiService,
|
||||
AppConfigService,
|
||||
AuthenticationService,
|
||||
FileUploadErrorEvent,
|
||||
PageTitleService,
|
||||
UploadService
|
||||
} from '@alfresco/adf-core';
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { ActivatedRoute, NavigationEnd, Router } from '@angular/router';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { AppExtensionService } from './extensions/extension.service';
|
||||
import {
|
||||
SetLanguagePickerAction,
|
||||
SnackbarErrorAction,
|
||||
SetCurrentUrlAction,
|
||||
SetInitialStateAction
|
||||
SetLanguagePickerAction,
|
||||
SnackbarErrorAction,
|
||||
SetCurrentUrlAction,
|
||||
SetInitialStateAction
|
||||
} from './store/actions';
|
||||
import { AppStore, AppState, INITIAL_APP_STATE } from './store/states/app.state';
|
||||
import {
|
||||
AppStore,
|
||||
AppState,
|
||||
INITIAL_APP_STATE
|
||||
} from './store/states/app.state';
|
||||
import { filter } from 'rxjs/operators';
|
||||
import { MatDialog } from '@angular/material';
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
templateUrl: './app.component.html',
|
||||
styleUrls: ['./app.component.scss']
|
||||
selector: 'app-root',
|
||||
templateUrl: './app.component.html',
|
||||
styleUrls: ['./app.component.scss']
|
||||
})
|
||||
export class AppComponent implements OnInit {
|
||||
constructor(
|
||||
private route: ActivatedRoute,
|
||||
private router: Router,
|
||||
private pageTitle: PageTitleService,
|
||||
private store: Store<AppStore>,
|
||||
private config: AppConfigService,
|
||||
private alfrescoApiService: AlfrescoApiService,
|
||||
private authenticationService: AuthenticationService,
|
||||
private uploadService: UploadService,
|
||||
private extensions: AppExtensionService,
|
||||
private dialogRef: MatDialog
|
||||
) {}
|
||||
constructor(
|
||||
private route: ActivatedRoute,
|
||||
private router: Router,
|
||||
private pageTitle: PageTitleService,
|
||||
private store: Store<AppStore>,
|
||||
private config: AppConfigService,
|
||||
private alfrescoApiService: AlfrescoApiService,
|
||||
private authenticationService: AuthenticationService,
|
||||
private uploadService: UploadService,
|
||||
private extensions: AppExtensionService,
|
||||
private dialogRef: MatDialog
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.alfrescoApiService.getInstance().on('error', error => {
|
||||
if (error.status === 401) {
|
||||
if (!this.authenticationService.isLoggedIn()) {
|
||||
this.authenticationService.setRedirect({ provider: 'ECM', url: this.router.url });
|
||||
this.router.navigate(['/login']);
|
||||
ngOnInit() {
|
||||
this.alfrescoApiService.getInstance().on('error', error => {
|
||||
if (error.status === 401) {
|
||||
if (!this.authenticationService.isLoggedIn()) {
|
||||
this.authenticationService.setRedirect({
|
||||
provider: 'ECM',
|
||||
url: this.router.url
|
||||
});
|
||||
this.router.navigate(['/login']);
|
||||
|
||||
this.dialogRef.closeAll();
|
||||
}
|
||||
}
|
||||
});
|
||||
this.dialogRef.closeAll();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this.loadAppSettings();
|
||||
this.loadAppSettings();
|
||||
|
||||
const { router, pageTitle, route } = this;
|
||||
const { router, pageTitle, route } = this;
|
||||
|
||||
router.events
|
||||
.pipe(filter(event => event instanceof NavigationEnd))
|
||||
.subscribe(() => {
|
||||
let currentRoute = route.root;
|
||||
router.events
|
||||
.pipe(filter(event => event instanceof NavigationEnd))
|
||||
.subscribe(() => {
|
||||
let currentRoute = route.root;
|
||||
|
||||
while (currentRoute.firstChild) {
|
||||
currentRoute = currentRoute.firstChild;
|
||||
}
|
||||
|
||||
const snapshot: any = currentRoute.snapshot || {};
|
||||
const data: any = snapshot.data || {};
|
||||
|
||||
pageTitle.setTitle(data.title || '');
|
||||
|
||||
this.store.dispatch(new SetCurrentUrlAction(router.url));
|
||||
});
|
||||
|
||||
this.router.config.unshift(...this.extensions.getApplicationRoutes());
|
||||
|
||||
this.uploadService.fileUploadError.subscribe(error =>
|
||||
this.onFileUploadedError(error)
|
||||
);
|
||||
}
|
||||
|
||||
private loadAppSettings() {
|
||||
const languagePicker = this.config.get<boolean>('languagePicker');
|
||||
this.store.dispatch(new SetLanguagePickerAction(languagePicker));
|
||||
|
||||
const state: AppState = {
|
||||
... INITIAL_APP_STATE,
|
||||
appName: this.config.get<string>('application.name'),
|
||||
headerColor: this.config.get<string>('headerColor'),
|
||||
logoPath: this.config.get<string>('application.logo'),
|
||||
sharedUrl: this.config.get<string>('ecmHost') + '/#/preview/s/'
|
||||
};
|
||||
|
||||
this.store.dispatch(new SetInitialStateAction(state));
|
||||
}
|
||||
|
||||
onFileUploadedError(error: FileUploadErrorEvent) {
|
||||
let message = 'APP.MESSAGES.UPLOAD.ERROR.GENERIC';
|
||||
|
||||
if (error.error.status === 409) {
|
||||
message = 'APP.MESSAGES.UPLOAD.ERROR.CONFLICT';
|
||||
while (currentRoute.firstChild) {
|
||||
currentRoute = currentRoute.firstChild;
|
||||
}
|
||||
|
||||
if (error.error.status === 500) {
|
||||
message = 'APP.MESSAGES.UPLOAD.ERROR.500';
|
||||
}
|
||||
const snapshot: any = currentRoute.snapshot || {};
|
||||
const data: any = snapshot.data || {};
|
||||
|
||||
this.store.dispatch(new SnackbarErrorAction(message));
|
||||
pageTitle.setTitle(data.title || '');
|
||||
|
||||
this.store.dispatch(new SetCurrentUrlAction(router.url));
|
||||
});
|
||||
|
||||
this.router.config.unshift(...this.extensions.getApplicationRoutes());
|
||||
|
||||
this.uploadService.fileUploadError.subscribe(error =>
|
||||
this.onFileUploadedError(error)
|
||||
);
|
||||
}
|
||||
|
||||
private loadAppSettings() {
|
||||
const languagePicker = this.config.get<boolean>('languagePicker');
|
||||
this.store.dispatch(new SetLanguagePickerAction(languagePicker));
|
||||
|
||||
const state: AppState = {
|
||||
...INITIAL_APP_STATE,
|
||||
appName: this.config.get<string>('application.name'),
|
||||
headerColor: this.config.get<string>('headerColor'),
|
||||
logoPath: this.config.get<string>('application.logo'),
|
||||
sharedUrl: this.config.get<string>('ecmHost') + '/#/preview/s/'
|
||||
};
|
||||
|
||||
this.store.dispatch(new SetInitialStateAction(state));
|
||||
}
|
||||
|
||||
onFileUploadedError(error: FileUploadErrorEvent) {
|
||||
let message = 'APP.MESSAGES.UPLOAD.ERROR.GENERIC';
|
||||
|
||||
if (error.error.status === 409) {
|
||||
message = 'APP.MESSAGES.UPLOAD.ERROR.CONFLICT';
|
||||
}
|
||||
|
||||
if (error.error.status === 500) {
|
||||
message = 'APP.MESSAGES.UPLOAD.ERROR.500';
|
||||
}
|
||||
|
||||
this.store.dispatch(new SnackbarErrorAction(message));
|
||||
}
|
||||
}
|
||||
|
@ -28,7 +28,12 @@ import { NgModule } from '@angular/core';
|
||||
import { RouterModule, RouteReuseStrategy } from '@angular/router';
|
||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||
import { TRANSLATION_PROVIDER, CoreModule, AppConfigService, DebugAppConfigService } from '@alfresco/adf-core';
|
||||
import {
|
||||
TRANSLATION_PROVIDER,
|
||||
CoreModule,
|
||||
AppConfigService,
|
||||
DebugAppConfigService
|
||||
} from '@alfresco/adf-core';
|
||||
import { ContentModule } from '@alfresco/adf-content-services';
|
||||
|
||||
import { AppComponent } from './app.component';
|
||||
@ -75,76 +80,76 @@ import { ExtensionsModule } from '@alfresco/adf-extensions';
|
||||
import { AppToolbarModule } from './components/toolbar/toolbar.module';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
BrowserModule,
|
||||
BrowserAnimationsModule,
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
RouterModule.forRoot(APP_ROUTES, {
|
||||
useHash: true,
|
||||
enableTracing: false // enable for debug only
|
||||
}),
|
||||
MaterialModule,
|
||||
CoreModule.forRoot(),
|
||||
ContentModule.forRoot(),
|
||||
AppStoreModule,
|
||||
CoreExtensionsModule.forRoot(),
|
||||
ExtensionsModule.forRoot(),
|
||||
AppExtensionsModule,
|
||||
imports: [
|
||||
BrowserModule,
|
||||
BrowserAnimationsModule,
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
RouterModule.forRoot(APP_ROUTES, {
|
||||
useHash: true,
|
||||
enableTracing: false // enable for debug only
|
||||
}),
|
||||
MaterialModule,
|
||||
CoreModule.forRoot(),
|
||||
ContentModule.forRoot(),
|
||||
AppStoreModule,
|
||||
CoreExtensionsModule.forRoot(),
|
||||
ExtensionsModule.forRoot(),
|
||||
AppExtensionsModule,
|
||||
|
||||
DirectivesModule,
|
||||
ContextMenuModule.forRoot(),
|
||||
AppInfoDrawerModule,
|
||||
AppToolbarModule
|
||||
],
|
||||
declarations: [
|
||||
AppComponent,
|
||||
GenericErrorComponent,
|
||||
LoginComponent,
|
||||
LayoutComponent,
|
||||
SidenavViewsManagerDirective,
|
||||
CurrentUserComponent,
|
||||
SearchInputComponent,
|
||||
SearchInputControlComponent,
|
||||
SidenavComponent,
|
||||
FilesComponent,
|
||||
FavoritesComponent,
|
||||
LibrariesComponent,
|
||||
RecentFilesComponent,
|
||||
SharedFilesComponent,
|
||||
TrashcanComponent,
|
||||
LocationLinkComponent,
|
||||
SearchResultsRowComponent,
|
||||
NodeVersionsDialogComponent,
|
||||
LibraryDialogComponent,
|
||||
NodePermissionsDialogComponent,
|
||||
PermissionsManagerComponent,
|
||||
SearchResultsComponent,
|
||||
SharedLinkViewComponent
|
||||
],
|
||||
providers: [
|
||||
{ provide: RouteReuseStrategy, useClass: AppRouteReuseStrategy },
|
||||
{ provide: AppConfigService, useClass: DebugAppConfigService },
|
||||
{
|
||||
provide: TRANSLATION_PROVIDER,
|
||||
multi: true,
|
||||
useValue: {
|
||||
name: 'app',
|
||||
source: 'assets'
|
||||
}
|
||||
},
|
||||
ContentManagementService,
|
||||
NodeActionsService,
|
||||
NodePermissionService,
|
||||
ProfileResolver,
|
||||
ExperimentalGuard,
|
||||
ContentApiService
|
||||
],
|
||||
entryComponents: [
|
||||
LibraryDialogComponent,
|
||||
NodeVersionsDialogComponent,
|
||||
NodePermissionsDialogComponent
|
||||
],
|
||||
bootstrap: [AppComponent]
|
||||
DirectivesModule,
|
||||
ContextMenuModule.forRoot(),
|
||||
AppInfoDrawerModule,
|
||||
AppToolbarModule
|
||||
],
|
||||
declarations: [
|
||||
AppComponent,
|
||||
GenericErrorComponent,
|
||||
LoginComponent,
|
||||
LayoutComponent,
|
||||
SidenavViewsManagerDirective,
|
||||
CurrentUserComponent,
|
||||
SearchInputComponent,
|
||||
SearchInputControlComponent,
|
||||
SidenavComponent,
|
||||
FilesComponent,
|
||||
FavoritesComponent,
|
||||
LibrariesComponent,
|
||||
RecentFilesComponent,
|
||||
SharedFilesComponent,
|
||||
TrashcanComponent,
|
||||
LocationLinkComponent,
|
||||
SearchResultsRowComponent,
|
||||
NodeVersionsDialogComponent,
|
||||
LibraryDialogComponent,
|
||||
NodePermissionsDialogComponent,
|
||||
PermissionsManagerComponent,
|
||||
SearchResultsComponent,
|
||||
SharedLinkViewComponent
|
||||
],
|
||||
providers: [
|
||||
{ provide: RouteReuseStrategy, useClass: AppRouteReuseStrategy },
|
||||
{ provide: AppConfigService, useClass: DebugAppConfigService },
|
||||
{
|
||||
provide: TRANSLATION_PROVIDER,
|
||||
multi: true,
|
||||
useValue: {
|
||||
name: 'app',
|
||||
source: 'assets'
|
||||
}
|
||||
},
|
||||
ContentManagementService,
|
||||
NodeActionsService,
|
||||
NodePermissionService,
|
||||
ProfileResolver,
|
||||
ExperimentalGuard,
|
||||
ContentApiService
|
||||
],
|
||||
entryComponents: [
|
||||
LibraryDialogComponent,
|
||||
NodeVersionsDialogComponent,
|
||||
NodePermissionsDialogComponent
|
||||
],
|
||||
bootstrap: [AppComponent]
|
||||
})
|
||||
export class AppModule { }
|
||||
export class AppModule {}
|
||||
|
@ -24,103 +24,101 @@
|
||||
*/
|
||||
|
||||
import {
|
||||
RouteReuseStrategy,
|
||||
DetachedRouteHandle,
|
||||
ActivatedRouteSnapshot
|
||||
RouteReuseStrategy,
|
||||
DetachedRouteHandle,
|
||||
ActivatedRouteSnapshot
|
||||
} from '@angular/router';
|
||||
|
||||
interface RouteData {
|
||||
reuse: boolean;
|
||||
reuse: boolean;
|
||||
}
|
||||
|
||||
interface RouteInfo {
|
||||
handle: DetachedRouteHandle;
|
||||
data: RouteData;
|
||||
handle: DetachedRouteHandle;
|
||||
data: RouteData;
|
||||
}
|
||||
|
||||
export class AppRouteReuseStrategy implements RouteReuseStrategy {
|
||||
private routeCache = new Map<string, RouteInfo>();
|
||||
private routeCache = new Map<string, RouteInfo>();
|
||||
|
||||
shouldReuseRoute(
|
||||
future: ActivatedRouteSnapshot,
|
||||
curr: ActivatedRouteSnapshot
|
||||
): boolean {
|
||||
const ret = future.routeConfig === curr.routeConfig;
|
||||
if (ret) {
|
||||
this.addRedirectsRecursively(future); // update redirects
|
||||
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;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
route.children.forEach(childRoute =>
|
||||
this.addRedirectsRecursively(childRoute)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
shouldDetach(route: ActivatedRouteSnapshot): boolean {
|
||||
const data = this.getRouteData(route);
|
||||
return data && data.reuse;
|
||||
}
|
||||
private getFullRouteUrl(route: ActivatedRouteSnapshot): string {
|
||||
return this.getFullRouteUrlPaths(route)
|
||||
.filter(Boolean)
|
||||
.join('/');
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
private getFullRouteUrlPaths(route: ActivatedRouteSnapshot): string[] {
|
||||
const paths = this.getRouteUrlPaths(route);
|
||||
return route.parent
|
||||
? [...this.getFullRouteUrlPaths(route.parent), ...paths]
|
||||
: paths;
|
||||
}
|
||||
|
||||
shouldAttach(route: ActivatedRouteSnapshot): boolean {
|
||||
const url = this.getFullRouteUrl(route);
|
||||
return this.routeCache.has(url);
|
||||
}
|
||||
private getRouteUrlPaths(route: ActivatedRouteSnapshot): string[] {
|
||||
return route.url.map(urlSegment => urlSegment.path);
|
||||
}
|
||||
|
||||
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)
|
||||
);
|
||||
}
|
||||
private getRouteData(route: ActivatedRouteSnapshot): RouteData {
|
||||
return route.routeConfig && (route.routeConfig.data as RouteData);
|
||||
}
|
||||
}
|
||||
|
@ -43,224 +43,233 @@ import { SearchResultsComponent } from './components/search/search-results/searc
|
||||
import { ProfileResolver } from './services/profile.resolver';
|
||||
|
||||
export const APP_ROUTES: Routes = [
|
||||
{
|
||||
path: 'login',
|
||||
component: LoginComponent,
|
||||
data: {
|
||||
title: 'APP.SIGN_IN'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'settings',
|
||||
loadChildren: 'src/app/components/settings/settings.module#AppSettingsModule',
|
||||
data: {
|
||||
title: 'Settings'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'preview/s/:id',
|
||||
component: SharedLinkViewComponent,
|
||||
data: {
|
||||
title: 'APP.PREVIEW.TITLE',
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '',
|
||||
component: LayoutComponent,
|
||||
resolve: { profile: ProfileResolver },
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
redirectTo: `/personal-files`,
|
||||
pathMatch: 'full'
|
||||
},
|
||||
{
|
||||
path: 'favorites',
|
||||
data: {
|
||||
sortingPreferenceKey: 'favorites'
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
component: FavoritesComponent,
|
||||
data: {
|
||||
title: 'APP.BROWSE.FAVORITES.TITLE'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'preview/:nodeId',
|
||||
loadChildren: 'src/app/components/preview/preview.module#PreviewModule',
|
||||
data: {
|
||||
title: 'APP.PREVIEW.TITLE',
|
||||
navigateMultiple: true,
|
||||
navigateSource: 'favorites'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: 'libraries',
|
||||
data: {
|
||||
sortingPreferenceKey: 'libraries'
|
||||
},
|
||||
children: [{
|
||||
path: '',
|
||||
component: LibrariesComponent,
|
||||
data: {
|
||||
title: 'APP.BROWSE.LIBRARIES.TITLE'
|
||||
}
|
||||
}, {
|
||||
path: ':folderId',
|
||||
component: FilesComponent,
|
||||
data: {
|
||||
title: 'APP.BROWSE.LIBRARIES.TITLE',
|
||||
sortingPreferenceKey: 'libraries-files'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: ':folderId/preview/:nodeId',
|
||||
loadChildren: 'src/app/components/preview/preview.module#PreviewModule',
|
||||
data: {
|
||||
title: 'APP.PREVIEW.TITLE',
|
||||
navigateMultiple: true,
|
||||
navigateSource: 'libraries'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: 'personal-files',
|
||||
data: {
|
||||
sortingPreferenceKey: 'personal-files'
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
component: FilesComponent,
|
||||
data: {
|
||||
title: 'APP.BROWSE.PERSONAL.TITLE',
|
||||
defaultNodeId: '-my-'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: ':folderId',
|
||||
component: FilesComponent,
|
||||
data: {
|
||||
title: 'APP.BROWSE.PERSONAL.TITLE'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'preview/:nodeId',
|
||||
loadChildren: 'src/app/components/preview/preview.module#PreviewModule',
|
||||
data: {
|
||||
title: 'APP.PREVIEW.TITLE',
|
||||
navigateMultiple: true,
|
||||
navigateSource: 'personal-files'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: ':folderId/preview/:nodeId',
|
||||
loadChildren: 'src/app/components/preview/preview.module#PreviewModule',
|
||||
data: {
|
||||
title: 'APP.PREVIEW.TITLE',
|
||||
navigateMultiple: true,
|
||||
navigateSource: 'personal-files'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: 'recent-files',
|
||||
data: {
|
||||
sortingPreferenceKey: 'recent-files'
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
component: RecentFilesComponent,
|
||||
data: {
|
||||
title: 'APP.BROWSE.RECENT.TITLE'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'preview/:nodeId',
|
||||
loadChildren: 'src/app/components/preview/preview.module#PreviewModule',
|
||||
data: {
|
||||
title: 'APP.PREVIEW.TITLE',
|
||||
navigateMultiple: true,
|
||||
navigateSource: 'recent-files'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: 'shared',
|
||||
data: {
|
||||
sortingPreferenceKey: 'shared-files'
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
component: SharedFilesComponent,
|
||||
data: {
|
||||
title: 'APP.BROWSE.SHARED.TITLE'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'preview/:nodeId',
|
||||
loadChildren: 'src/app/components/preview/preview.module#PreviewModule',
|
||||
data: {
|
||||
title: 'APP.PREVIEW.TITLE',
|
||||
navigateMultiple: true,
|
||||
navigateSource: 'shared'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: 'trashcan',
|
||||
component: TrashcanComponent,
|
||||
data: {
|
||||
title: 'APP.BROWSE.TRASHCAN.TITLE',
|
||||
sortingPreferenceKey: 'trashcan'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'about',
|
||||
loadChildren: 'src/app/components/about/about.module#AboutModule',
|
||||
data: {
|
||||
title: 'APP.BROWSE.ABOUT.TITLE'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'search',
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
component: SearchResultsComponent,
|
||||
data: {
|
||||
title: 'APP.BROWSE.SEARCH.TITLE',
|
||||
reuse: true
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'preview/:nodeId',
|
||||
loadChildren: 'src/app/components/preview/preview.module#PreviewModule',
|
||||
data: {
|
||||
title: 'APP.PREVIEW.TITLE',
|
||||
navigateMultiple: true,
|
||||
navigateSource: 'search'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '**',
|
||||
component: GenericErrorComponent
|
||||
}
|
||||
],
|
||||
canActivateChild: [ AuthGuardEcm ],
|
||||
canActivate: [ AuthGuardEcm ]
|
||||
{
|
||||
path: 'login',
|
||||
component: LoginComponent,
|
||||
data: {
|
||||
title: 'APP.SIGN_IN'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'settings',
|
||||
loadChildren:
|
||||
'src/app/components/settings/settings.module#AppSettingsModule',
|
||||
data: {
|
||||
title: 'Settings'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'preview/s/:id',
|
||||
component: SharedLinkViewComponent,
|
||||
data: {
|
||||
title: 'APP.PREVIEW.TITLE'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '',
|
||||
component: LayoutComponent,
|
||||
resolve: { profile: ProfileResolver },
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
redirectTo: `/personal-files`,
|
||||
pathMatch: 'full'
|
||||
},
|
||||
{
|
||||
path: 'favorites',
|
||||
data: {
|
||||
sortingPreferenceKey: 'favorites'
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
component: FavoritesComponent,
|
||||
data: {
|
||||
title: 'APP.BROWSE.FAVORITES.TITLE'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'preview/:nodeId',
|
||||
loadChildren:
|
||||
'src/app/components/preview/preview.module#PreviewModule',
|
||||
data: {
|
||||
title: 'APP.PREVIEW.TITLE',
|
||||
navigateMultiple: true,
|
||||
navigateSource: 'favorites'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: 'libraries',
|
||||
data: {
|
||||
sortingPreferenceKey: 'libraries'
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
component: LibrariesComponent,
|
||||
data: {
|
||||
title: 'APP.BROWSE.LIBRARIES.TITLE'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: ':folderId',
|
||||
component: FilesComponent,
|
||||
data: {
|
||||
title: 'APP.BROWSE.LIBRARIES.TITLE',
|
||||
sortingPreferenceKey: 'libraries-files'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: ':folderId/preview/:nodeId',
|
||||
loadChildren:
|
||||
'src/app/components/preview/preview.module#PreviewModule',
|
||||
data: {
|
||||
title: 'APP.PREVIEW.TITLE',
|
||||
navigateMultiple: true,
|
||||
navigateSource: 'libraries'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: 'personal-files',
|
||||
data: {
|
||||
sortingPreferenceKey: 'personal-files'
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
component: FilesComponent,
|
||||
data: {
|
||||
title: 'APP.BROWSE.PERSONAL.TITLE',
|
||||
defaultNodeId: '-my-'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: ':folderId',
|
||||
component: FilesComponent,
|
||||
data: {
|
||||
title: 'APP.BROWSE.PERSONAL.TITLE'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'preview/:nodeId',
|
||||
loadChildren:
|
||||
'src/app/components/preview/preview.module#PreviewModule',
|
||||
data: {
|
||||
title: 'APP.PREVIEW.TITLE',
|
||||
navigateMultiple: true,
|
||||
navigateSource: 'personal-files'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: ':folderId/preview/:nodeId',
|
||||
loadChildren:
|
||||
'src/app/components/preview/preview.module#PreviewModule',
|
||||
data: {
|
||||
title: 'APP.PREVIEW.TITLE',
|
||||
navigateMultiple: true,
|
||||
navigateSource: 'personal-files'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: 'recent-files',
|
||||
data: {
|
||||
sortingPreferenceKey: 'recent-files'
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
component: RecentFilesComponent,
|
||||
data: {
|
||||
title: 'APP.BROWSE.RECENT.TITLE'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'preview/:nodeId',
|
||||
loadChildren:
|
||||
'src/app/components/preview/preview.module#PreviewModule',
|
||||
data: {
|
||||
title: 'APP.PREVIEW.TITLE',
|
||||
navigateMultiple: true,
|
||||
navigateSource: 'recent-files'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: 'shared',
|
||||
data: {
|
||||
sortingPreferenceKey: 'shared-files'
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
component: SharedFilesComponent,
|
||||
data: {
|
||||
title: 'APP.BROWSE.SHARED.TITLE'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'preview/:nodeId',
|
||||
loadChildren:
|
||||
'src/app/components/preview/preview.module#PreviewModule',
|
||||
data: {
|
||||
title: 'APP.PREVIEW.TITLE',
|
||||
navigateMultiple: true,
|
||||
navigateSource: 'shared'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: 'trashcan',
|
||||
component: TrashcanComponent,
|
||||
data: {
|
||||
title: 'APP.BROWSE.TRASHCAN.TITLE',
|
||||
sortingPreferenceKey: 'trashcan'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'about',
|
||||
loadChildren: 'src/app/components/about/about.module#AboutModule',
|
||||
data: {
|
||||
title: 'APP.BROWSE.ABOUT.TITLE'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'search',
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
component: SearchResultsComponent,
|
||||
data: {
|
||||
title: 'APP.BROWSE.SEARCH.TITLE',
|
||||
reuse: true
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'preview/:nodeId',
|
||||
loadChildren:
|
||||
'src/app/components/preview/preview.module#PreviewModule',
|
||||
data: {
|
||||
title: 'APP.PREVIEW.TITLE',
|
||||
navigateMultiple: true,
|
||||
navigateSource: 'search'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '**',
|
||||
component: GenericErrorComponent
|
||||
}
|
||||
],
|
||||
canActivateChild: [AuthGuardEcm],
|
||||
canActivate: [AuthGuardEcm]
|
||||
}
|
||||
];
|
||||
|
||||
|
@ -1,39 +1,39 @@
|
||||
@mixin aca-about-component-theme($theme) {
|
||||
$foreground: map-get($theme, foreground);
|
||||
$foreground: map-get($theme, foreground);
|
||||
|
||||
article {
|
||||
color: mat-color($foreground, text, 0.54);
|
||||
}
|
||||
article {
|
||||
color: mat-color($foreground, text, 0.54);
|
||||
}
|
||||
|
||||
article:first-of-type {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
article:first-of-type {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
article:last-of-type {
|
||||
margin-bottom: 50px;
|
||||
}
|
||||
article:last-of-type {
|
||||
margin-bottom: 50px;
|
||||
}
|
||||
|
||||
header {
|
||||
line-height: 24px;
|
||||
font-size: 14px;
|
||||
font-weight: 800;
|
||||
letter-spacing: -0.2px;
|
||||
}
|
||||
header {
|
||||
line-height: 24px;
|
||||
font-size: 14px;
|
||||
font-weight: 800;
|
||||
letter-spacing: -0.2px;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: mat-color($foreground, text, 0.87);
|
||||
}
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: mat-color($foreground, text, 0.87);
|
||||
}
|
||||
|
||||
.padding {
|
||||
padding: 25px;
|
||||
}
|
||||
.padding {
|
||||
padding: 25px;
|
||||
}
|
||||
|
||||
.padding-top-bottom {
|
||||
padding: 25px 0 25px 0;
|
||||
}
|
||||
.padding-top-bottom {
|
||||
padding: 25px 0 25px 0;
|
||||
}
|
||||
|
||||
.padding-left-right {
|
||||
padding: 0 25px 0 25px;
|
||||
}
|
||||
.padding-left-right {
|
||||
padding: 0 25px 0 25px;
|
||||
}
|
||||
}
|
||||
|
@ -25,83 +25,164 @@
|
||||
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { ObjectDataTableAdapter } from '@alfresco/adf-core';
|
||||
import { ObjectDataTableAdapter } from '@alfresco/adf-core';
|
||||
import { ContentApiService } from '../../services/content-api.service';
|
||||
import { RepositoryInfo } from 'alfresco-js-api';
|
||||
import { map } from 'rxjs/operators';
|
||||
|
||||
@Component({
|
||||
selector: 'app-about',
|
||||
templateUrl: './about.component.html'
|
||||
selector: 'app-about',
|
||||
templateUrl: './about.component.html'
|
||||
})
|
||||
export class AboutComponent implements OnInit {
|
||||
repository: RepositoryInfo;
|
||||
data: ObjectDataTableAdapter;
|
||||
status: ObjectDataTableAdapter;
|
||||
license: ObjectDataTableAdapter;
|
||||
modules: ObjectDataTableAdapter;
|
||||
githubUrlCommitAlpha = 'https://github.com/Alfresco/alfresco-content-app/commits';
|
||||
releaseVersion = '';
|
||||
repository: RepositoryInfo;
|
||||
data: ObjectDataTableAdapter;
|
||||
status: ObjectDataTableAdapter;
|
||||
license: ObjectDataTableAdapter;
|
||||
modules: ObjectDataTableAdapter;
|
||||
githubUrlCommitAlpha =
|
||||
'https://github.com/Alfresco/alfresco-content-app/commits';
|
||||
releaseVersion = '';
|
||||
|
||||
constructor(
|
||||
private contentApi: ContentApiService,
|
||||
private http: HttpClient
|
||||
) {}
|
||||
constructor(
|
||||
private contentApi: ContentApiService,
|
||||
private http: HttpClient
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.contentApi.getRepositoryInformation()
|
||||
.pipe(map(node => node.entry.repository))
|
||||
.subscribe(repository => {
|
||||
this.repository = repository;
|
||||
ngOnInit() {
|
||||
this.contentApi
|
||||
.getRepositoryInformation()
|
||||
.pipe(map(node => node.entry.repository))
|
||||
.subscribe(repository => {
|
||||
this.repository = repository;
|
||||
|
||||
this.modules = new ObjectDataTableAdapter(repository.modules, [
|
||||
{type: 'text', key: 'id', title: 'ID', sortable: true},
|
||||
{type: 'text', key: 'title', title: 'Title', sortable: true},
|
||||
{type: 'text', key: 'version', title: 'Description', sortable: true},
|
||||
{type: 'date', key: 'installDate', title: 'Install Date', sortable: true},
|
||||
{type: 'text', key: 'installState', title: 'Install State', sortable: true},
|
||||
{type: 'text', key: 'versionMin', title: 'Version Minor', sortable: true},
|
||||
{type: 'text', key: 'versionMax', title: 'Version Max', sortable: true}
|
||||
]);
|
||||
this.modules = new ObjectDataTableAdapter(repository.modules, [
|
||||
{ type: 'text', key: 'id', title: 'ID', sortable: true },
|
||||
{ type: 'text', key: 'title', title: 'Title', sortable: true },
|
||||
{
|
||||
type: 'text',
|
||||
key: 'version',
|
||||
title: 'Description',
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
type: 'date',
|
||||
key: 'installDate',
|
||||
title: 'Install Date',
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
key: 'installState',
|
||||
title: 'Install State',
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
key: 'versionMin',
|
||||
title: 'Version Minor',
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
key: 'versionMax',
|
||||
title: 'Version Max',
|
||||
sortable: true
|
||||
}
|
||||
]);
|
||||
|
||||
this.status = new ObjectDataTableAdapter([repository.status], [
|
||||
{type: 'text', key: 'isReadOnly', title: 'Read Only', sortable: true},
|
||||
{type: 'text', key: 'isAuditEnabled', title: 'Audit Enable', sortable: true},
|
||||
{type: 'text', key: 'isQuickShareEnabled', title: 'Quick Shared Enable', sortable: true},
|
||||
{type: 'text', key: 'isThumbnailGenerationEnabled', title: 'Thumbnail Generation', sortable: true}
|
||||
]);
|
||||
|
||||
|
||||
if (repository.license) {
|
||||
this.license = new ObjectDataTableAdapter([repository.license], [
|
||||
{type: 'date', key: 'issuedAt', title: 'Issued At', sortable: true},
|
||||
{type: 'date', key: 'expiresAt', title: 'Expires At', sortable: true},
|
||||
{type: 'text', key: 'remainingDays', title: 'Remaining Days', sortable: true},
|
||||
{type: 'text', key: 'holder', title: 'Holder', sortable: true},
|
||||
{type: 'text', key: 'mode', title: 'Type', sortable: true},
|
||||
{type: 'text', key: 'isClusterEnabled', title: 'Cluster Enabled', sortable: true},
|
||||
{type: 'text', key: 'isCryptodocEnabled', title: 'Cryptodoc Enable', sortable: true}
|
||||
]);
|
||||
this.status = new ObjectDataTableAdapter(
|
||||
[repository.status],
|
||||
[
|
||||
{
|
||||
type: 'text',
|
||||
key: 'isReadOnly',
|
||||
title: 'Read Only',
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
key: 'isAuditEnabled',
|
||||
title: 'Audit Enable',
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
key: 'isQuickShareEnabled',
|
||||
title: 'Quick Shared Enable',
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
key: 'isThumbnailGenerationEnabled',
|
||||
title: 'Thumbnail Generation',
|
||||
sortable: true
|
||||
}
|
||||
});
|
||||
]
|
||||
);
|
||||
|
||||
this.http.get('/versions.json')
|
||||
.subscribe((response: any) => {
|
||||
const regexp = new RegExp('^(@alfresco|alfresco-)');
|
||||
if (repository.license) {
|
||||
this.license = new ObjectDataTableAdapter(
|
||||
[repository.license],
|
||||
[
|
||||
{
|
||||
type: 'date',
|
||||
key: 'issuedAt',
|
||||
title: 'Issued At',
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
type: 'date',
|
||||
key: 'expiresAt',
|
||||
title: 'Expires At',
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
key: 'remainingDays',
|
||||
title: 'Remaining Days',
|
||||
sortable: true
|
||||
},
|
||||
{ type: 'text', key: 'holder', title: 'Holder', sortable: true },
|
||||
{ type: 'text', key: 'mode', title: 'Type', sortable: true },
|
||||
{
|
||||
type: 'text',
|
||||
key: 'isClusterEnabled',
|
||||
title: 'Cluster Enabled',
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
key: 'isCryptodocEnabled',
|
||||
title: 'Cryptodoc Enable',
|
||||
sortable: true
|
||||
}
|
||||
]
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
const alfrescoPackagesTableRepresentation = Object.keys(response.dependencies)
|
||||
.filter((val) => regexp.test(val))
|
||||
.map((val) => ({
|
||||
name: val,
|
||||
version: response.dependencies[val].version
|
||||
}));
|
||||
this.http.get('/versions.json').subscribe((response: any) => {
|
||||
const regexp = new RegExp('^(@alfresco|alfresco-)');
|
||||
|
||||
this.data = new ObjectDataTableAdapter(alfrescoPackagesTableRepresentation, [
|
||||
{type: 'text', key: 'name', title: 'Name', sortable: true},
|
||||
{type: 'text', key: 'version', title: 'Version', sortable: true}
|
||||
]);
|
||||
const alfrescoPackagesTableRepresentation = Object.keys(
|
||||
response.dependencies
|
||||
)
|
||||
.filter(val => regexp.test(val))
|
||||
.map(val => ({
|
||||
name: val,
|
||||
version: response.dependencies[val].version
|
||||
}));
|
||||
|
||||
this.releaseVersion = response.version;
|
||||
});
|
||||
}
|
||||
this.data = new ObjectDataTableAdapter(
|
||||
alfrescoPackagesTableRepresentation,
|
||||
[
|
||||
{ type: 'text', key: 'name', title: 'Name', sortable: true },
|
||||
{ type: 'text', key: 'version', title: 'Version', sortable: true }
|
||||
]
|
||||
);
|
||||
|
||||
this.releaseVersion = response.version;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -30,19 +30,14 @@ import { CommonModule } from '@angular/common';
|
||||
import { CoreModule } from '@alfresco/adf-core';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: '',
|
||||
component: AboutComponent
|
||||
}
|
||||
{
|
||||
path: '',
|
||||
component: AboutComponent
|
||||
}
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
CommonModule,
|
||||
CoreModule.forChild(),
|
||||
RouterModule.forChild(routes)
|
||||
],
|
||||
declarations: [AboutComponent]
|
||||
imports: [CommonModule, CoreModule.forChild(), RouterModule.forChild(routes)],
|
||||
declarations: [AboutComponent]
|
||||
})
|
||||
export class AboutModule {
|
||||
}
|
||||
export class AboutModule {}
|
||||
|
@ -24,29 +24,45 @@
|
||||
*/
|
||||
|
||||
import {
|
||||
state,
|
||||
style,
|
||||
animate,
|
||||
transition,
|
||||
query,
|
||||
group,
|
||||
sequence
|
||||
state,
|
||||
style,
|
||||
animate,
|
||||
transition,
|
||||
query,
|
||||
group,
|
||||
sequence
|
||||
} from '@angular/animations';
|
||||
|
||||
export const contextMenuAnimation = [
|
||||
state('void', style({
|
||||
opacity: 0,
|
||||
transform: 'scale(0.01, 0.01)'
|
||||
})),
|
||||
transition('void => *', sequence([
|
||||
query('.mat-menu-content', style({ opacity: 0 })),
|
||||
animate('100ms linear', style({ opacity: 1, transform: 'scale(1, 0.5)' })),
|
||||
group([
|
||||
query('.mat-menu-content', animate('400ms cubic-bezier(0.55, 0, 0.55, 0.2)',
|
||||
style({ opacity: 1 })
|
||||
)),
|
||||
animate('300ms cubic-bezier(0.25, 0.8, 0.25, 1)', style({ transform: 'scale(1, 1)' })),
|
||||
])
|
||||
])),
|
||||
transition('* => void', animate('150ms 50ms linear', style({ opacity: 0 })))
|
||||
state(
|
||||
'void',
|
||||
style({
|
||||
opacity: 0,
|
||||
transform: 'scale(0.01, 0.01)'
|
||||
})
|
||||
),
|
||||
transition(
|
||||
'void => *',
|
||||
sequence([
|
||||
query('.mat-menu-content', style({ opacity: 0 })),
|
||||
animate(
|
||||
'100ms linear',
|
||||
style({ opacity: 1, transform: 'scale(1, 0.5)' })
|
||||
),
|
||||
group([
|
||||
query(
|
||||
'.mat-menu-content',
|
||||
animate(
|
||||
'400ms cubic-bezier(0.55, 0, 0.55, 0.2)',
|
||||
style({ opacity: 1 })
|
||||
)
|
||||
),
|
||||
animate(
|
||||
'300ms cubic-bezier(0.25, 0.8, 0.25, 1)',
|
||||
style({ transform: 'scale(1, 1)' })
|
||||
)
|
||||
])
|
||||
])
|
||||
),
|
||||
transition('* => void', animate('150ms 50ms linear', style({ opacity: 0 })))
|
||||
];
|
||||
|
@ -27,25 +27,25 @@ import { Directive, ElementRef, OnDestroy } from '@angular/core';
|
||||
import { FocusableOption, FocusMonitor, FocusOrigin } from '@angular/cdk/a11y';
|
||||
|
||||
@Directive({
|
||||
selector: '[acaContextMenuItem]',
|
||||
selector: '[acaContextMenuItem]'
|
||||
})
|
||||
export class ContextMenuItemDirective implements OnDestroy, FocusableOption {
|
||||
constructor(
|
||||
private elementRef: ElementRef,
|
||||
private focusMonitor: FocusMonitor) {
|
||||
constructor(
|
||||
private elementRef: ElementRef,
|
||||
private focusMonitor: FocusMonitor
|
||||
) {
|
||||
focusMonitor.monitor(this.getHostElement(), false);
|
||||
}
|
||||
|
||||
focusMonitor.monitor(this.getHostElement(), false);
|
||||
}
|
||||
ngOnDestroy() {
|
||||
this.focusMonitor.stopMonitoring(this.getHostElement());
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.focusMonitor.stopMonitoring(this.getHostElement());
|
||||
}
|
||||
focus(origin: FocusOrigin = 'keyboard'): void {
|
||||
this.focusMonitor.focusVia(this.getHostElement(), origin);
|
||||
}
|
||||
|
||||
focus(origin: FocusOrigin = 'keyboard'): void {
|
||||
this.focusMonitor.focusVia(this.getHostElement(), origin);
|
||||
}
|
||||
|
||||
private getHostElement(): HTMLElement {
|
||||
return this.elementRef.nativeElement;
|
||||
}
|
||||
private getHostElement(): HTMLElement {
|
||||
return this.elementRef.nativeElement;
|
||||
}
|
||||
}
|
||||
|
@ -1,28 +1,34 @@
|
||||
import { Directive, Output, EventEmitter, OnInit, OnDestroy } from '@angular/core';
|
||||
import {
|
||||
Directive,
|
||||
Output,
|
||||
EventEmitter,
|
||||
OnInit,
|
||||
OnDestroy
|
||||
} from '@angular/core';
|
||||
import { fromEvent, Subscription } from 'rxjs';
|
||||
import { delay } from 'rxjs/operators';
|
||||
|
||||
@Directive({
|
||||
selector: '[acaContextMenuOutsideEvent]'
|
||||
})
|
||||
|
||||
export class OutsideEventDirective implements OnInit, OnDestroy {
|
||||
private subscriptions: Subscription[] = [];
|
||||
private subscriptions: Subscription[] = [];
|
||||
|
||||
@Output() clickOutside: EventEmitter<null> = new EventEmitter();
|
||||
@Output()
|
||||
clickOutside: EventEmitter<null> = new EventEmitter();
|
||||
|
||||
constructor() {}
|
||||
constructor() {}
|
||||
|
||||
ngOnInit() {
|
||||
this.subscriptions = this.subscriptions.concat([
|
||||
fromEvent(document.body, 'click')
|
||||
.pipe(delay(1))
|
||||
.subscribe(() => this.clickOutside.next())
|
||||
]);
|
||||
}
|
||||
ngOnInit() {
|
||||
this.subscriptions = this.subscriptions.concat([
|
||||
fromEvent(document.body, 'click')
|
||||
.pipe(delay(1))
|
||||
.subscribe(() => this.clickOutside.next())
|
||||
]);
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.subscriptions.forEach(subscription => subscription.unsubscribe());
|
||||
this.subscriptions = [];
|
||||
}
|
||||
ngOnDestroy() {
|
||||
this.subscriptions.forEach(subscription => subscription.unsubscribe());
|
||||
this.subscriptions = [];
|
||||
}
|
||||
}
|
||||
|
@ -26,10 +26,9 @@
|
||||
import { OverlayRef } from '@angular/cdk/overlay';
|
||||
|
||||
export class ContextMenuOverlayRef {
|
||||
constructor(private overlayRef: OverlayRef) {}
|
||||
|
||||
constructor(private overlayRef: OverlayRef) { }
|
||||
|
||||
close(): void {
|
||||
this.overlayRef.dispose();
|
||||
}
|
||||
close(): void {
|
||||
this.overlayRef.dispose();
|
||||
}
|
||||
}
|
||||
|
@ -1,19 +1,19 @@
|
||||
.aca-context-menu {
|
||||
&__more-actions::after {
|
||||
margin-left: 34px;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-style: solid;
|
||||
border-width: 5px 0 5px 5px;
|
||||
content: '';
|
||||
display: inline-block;
|
||||
}
|
||||
&__more-actions::after {
|
||||
margin-left: 34px;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-style: solid;
|
||||
border-width: 5px 0 5px 5px;
|
||||
content: '';
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
&__separator {
|
||||
display: block;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border-top-width: 1px;
|
||||
border-top-style: solid;
|
||||
}
|
||||
}
|
||||
&__separator {
|
||||
display: block;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border-top-width: 1px;
|
||||
border-top-style: solid;
|
||||
}
|
||||
}
|
||||
|
@ -1,17 +1,17 @@
|
||||
@mixin aca-context-menu-theme($theme) {
|
||||
$foreground: map-get($theme, foreground);
|
||||
$primary: map-get($theme, primary);
|
||||
$foreground: map-get($theme, foreground);
|
||||
$primary: map-get($theme, primary);
|
||||
|
||||
.aca-context-menu {
|
||||
@include angular-material-theme($theme);
|
||||
.aca-context-menu {
|
||||
@include angular-material-theme($theme);
|
||||
|
||||
&__separator {
|
||||
border-top-color: mat-color($foreground, divider);
|
||||
}
|
||||
|
||||
&__more-actions::after {
|
||||
border-color: transparent;
|
||||
border-left-color: mat-color($primary);
|
||||
}
|
||||
&__separator {
|
||||
border-top-color: mat-color($foreground, divider);
|
||||
}
|
||||
|
||||
&__more-actions::after {
|
||||
border-color: transparent;
|
||||
border-left-color: mat-color($primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -24,8 +24,14 @@
|
||||
*/
|
||||
|
||||
import {
|
||||
Component, ViewEncapsulation, OnInit, OnDestroy, HostListener,
|
||||
ViewChildren, QueryList, AfterViewInit
|
||||
Component,
|
||||
ViewEncapsulation,
|
||||
OnInit,
|
||||
OnDestroy,
|
||||
HostListener,
|
||||
ViewChildren,
|
||||
QueryList,
|
||||
AfterViewInit
|
||||
} from '@angular/core';
|
||||
import { trigger } from '@angular/animations';
|
||||
import { FocusKeyManager } from '@angular/cdk/a11y';
|
||||
@ -44,99 +50,99 @@ import { contextMenuAnimation } from './animations';
|
||||
import { ContextMenuItemDirective } from './context-menu-item.directive';
|
||||
|
||||
@Component({
|
||||
selector: 'aca-context-menu',
|
||||
templateUrl: './context-menu.component.html',
|
||||
styleUrls: [
|
||||
'./context-menu.component.scss',
|
||||
'./context-menu.component.theme.scss'
|
||||
],
|
||||
host: {
|
||||
role: 'menu',
|
||||
class: 'aca-context-menu'
|
||||
},
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
animations: [
|
||||
trigger('panelAnimation', contextMenuAnimation)
|
||||
]
|
||||
selector: 'aca-context-menu',
|
||||
templateUrl: './context-menu.component.html',
|
||||
styleUrls: [
|
||||
'./context-menu.component.scss',
|
||||
'./context-menu.component.theme.scss'
|
||||
],
|
||||
host: {
|
||||
role: 'menu',
|
||||
class: 'aca-context-menu'
|
||||
},
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
animations: [trigger('panelAnimation', contextMenuAnimation)]
|
||||
})
|
||||
export class ContextMenuComponent implements OnInit, OnDestroy, AfterViewInit {
|
||||
private onDestroy$: Subject<boolean> = new Subject<boolean>();
|
||||
private selection: SelectionState;
|
||||
private _keyManager: FocusKeyManager<ContextMenuItemDirective>;
|
||||
actions: Array<ContentActionRef> = [];
|
||||
private onDestroy$: Subject<boolean> = new Subject<boolean>();
|
||||
private selection: SelectionState;
|
||||
private _keyManager: FocusKeyManager<ContextMenuItemDirective>;
|
||||
actions: Array<ContentActionRef> = [];
|
||||
|
||||
@ViewChildren(ContextMenuItemDirective)
|
||||
private contextMenuItems: QueryList<ContextMenuItemDirective>;
|
||||
@ViewChildren(ContextMenuItemDirective)
|
||||
private contextMenuItems: QueryList<ContextMenuItemDirective>;
|
||||
|
||||
@HostListener('contextmenu', ['$event'])
|
||||
handleContextMenu(event: MouseEvent) {
|
||||
if (event) {
|
||||
event.preventDefault();
|
||||
if (this.contextMenuOverlayRef) {
|
||||
this.contextMenuOverlayRef.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@HostListener('document:keydown.Escape', ['$event'])
|
||||
handleKeydownEscape(event: KeyboardEvent) {
|
||||
if (event) {
|
||||
if (this.contextMenuOverlayRef) {
|
||||
this.contextMenuOverlayRef.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@HostListener('document:keydown', ['$event'])
|
||||
handleKeydownEvent(event: KeyboardEvent) {
|
||||
if (event) {
|
||||
const keyCode = event.keyCode;
|
||||
if (keyCode === UP_ARROW || keyCode === DOWN_ARROW) {
|
||||
this._keyManager.onKeydown(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
constructor(
|
||||
private contextMenuOverlayRef: ContextMenuOverlayRef,
|
||||
private extensions: AppExtensionService,
|
||||
private store: Store<AppStore>,
|
||||
) { }
|
||||
|
||||
onClickOutsideEvent() {
|
||||
if (this.contextMenuOverlayRef) {
|
||||
this.contextMenuOverlayRef.close();
|
||||
}
|
||||
}
|
||||
|
||||
runAction(actionId: string) {
|
||||
const context = {
|
||||
selection: this.selection
|
||||
};
|
||||
|
||||
this.extensions.runActionById(actionId, context);
|
||||
@HostListener('contextmenu', ['$event'])
|
||||
handleContextMenu(event: MouseEvent) {
|
||||
if (event) {
|
||||
event.preventDefault();
|
||||
if (this.contextMenuOverlayRef) {
|
||||
this.contextMenuOverlayRef.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.onDestroy$.next(true);
|
||||
this.onDestroy$.complete();
|
||||
@HostListener('document:keydown.Escape', ['$event'])
|
||||
handleKeydownEscape(event: KeyboardEvent) {
|
||||
if (event) {
|
||||
if (this.contextMenuOverlayRef) {
|
||||
this.contextMenuOverlayRef.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.store
|
||||
.select(appSelection)
|
||||
.pipe(takeUntil(this.onDestroy$))
|
||||
.subscribe(selection => {
|
||||
if (selection.count) {
|
||||
this.selection = selection;
|
||||
this.actions = this.extensions.getAllowedContextMenuActions();
|
||||
}
|
||||
});
|
||||
@HostListener('document:keydown', ['$event'])
|
||||
handleKeydownEvent(event: KeyboardEvent) {
|
||||
if (event) {
|
||||
const keyCode = event.keyCode;
|
||||
if (keyCode === UP_ARROW || keyCode === DOWN_ARROW) {
|
||||
this._keyManager.onKeydown(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ngAfterViewInit() {
|
||||
this._keyManager = new FocusKeyManager<ContextMenuItemDirective>(this.contextMenuItems);
|
||||
this._keyManager.setFirstItemActive();
|
||||
constructor(
|
||||
private contextMenuOverlayRef: ContextMenuOverlayRef,
|
||||
private extensions: AppExtensionService,
|
||||
private store: Store<AppStore>
|
||||
) {}
|
||||
|
||||
onClickOutsideEvent() {
|
||||
if (this.contextMenuOverlayRef) {
|
||||
this.contextMenuOverlayRef.close();
|
||||
}
|
||||
}
|
||||
|
||||
runAction(actionId: string) {
|
||||
const context = {
|
||||
selection: this.selection
|
||||
};
|
||||
|
||||
this.extensions.runActionById(actionId, context);
|
||||
this.contextMenuOverlayRef.close();
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.onDestroy$.next(true);
|
||||
this.onDestroy$.complete();
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.store
|
||||
.select(appSelection)
|
||||
.pipe(takeUntil(this.onDestroy$))
|
||||
.subscribe(selection => {
|
||||
if (selection.count) {
|
||||
this.selection = selection;
|
||||
this.actions = this.extensions.getAllowedContextMenuActions();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ngAfterViewInit() {
|
||||
this._keyManager = new FocusKeyManager<ContextMenuItemDirective>(
|
||||
this.contextMenuItems
|
||||
);
|
||||
this._keyManager.setFirstItemActive();
|
||||
}
|
||||
}
|
||||
|
@ -34,106 +34,112 @@ import { DataRow } from '@alfresco/adf-core';
|
||||
import { MinimalNodeEntity } from 'alfresco-js-api';
|
||||
|
||||
@Directive({
|
||||
selector: '[acaContextActions]'
|
||||
selector: '[acaContextActions]'
|
||||
})
|
||||
export class ContextActionsDirective {
|
||||
private overlayRef: ContextMenuOverlayRef = null;
|
||||
private overlayRef: ContextMenuOverlayRef = null;
|
||||
|
||||
// tslint:disable-next-line:no-input-rename
|
||||
@Input('acaContextEnable') enabled = true;
|
||||
// tslint:disable-next-line:no-input-rename
|
||||
@Input('acaContextEnable')
|
||||
enabled = true;
|
||||
|
||||
@HostListener('window:resize', ['$event'])
|
||||
onResize(event) {
|
||||
if (event && this.overlayRef) {
|
||||
this.clearSelection();
|
||||
this.overlayRef.close();
|
||||
}
|
||||
@HostListener('window:resize', ['$event'])
|
||||
onResize(event) {
|
||||
if (event && this.overlayRef) {
|
||||
this.clearSelection();
|
||||
this.overlayRef.close();
|
||||
}
|
||||
}
|
||||
|
||||
@HostListener('contextmenu', ['$event'])
|
||||
onContextMenuEvent(event: MouseEvent) {
|
||||
if (event) {
|
||||
event.preventDefault();
|
||||
|
||||
if (this.enabled) {
|
||||
this.execute(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
constructor(
|
||||
private documentList: DocumentListComponent,
|
||||
private store: Store<AppStore>,
|
||||
private contextMenuService: ContextMenuService
|
||||
) {}
|
||||
|
||||
private execute(event: MouseEvent) {
|
||||
const selected = this.getSelectedRow(event);
|
||||
|
||||
if (selected) {
|
||||
if (!this.isInSelection(selected)) {
|
||||
this.clearSelection();
|
||||
|
||||
this.documentList.dataTable.selectRow(selected, true);
|
||||
this.documentList.selection.push((<any>selected).node);
|
||||
|
||||
this.updateSelection();
|
||||
}
|
||||
|
||||
this.render(event);
|
||||
}
|
||||
}
|
||||
|
||||
private render(event: MouseEvent) {
|
||||
if (this.overlayRef) {
|
||||
this.overlayRef.close();
|
||||
}
|
||||
|
||||
@HostListener('contextmenu', ['$event'])
|
||||
onContextMenuEvent(event: MouseEvent) {
|
||||
if (event) {
|
||||
event.preventDefault();
|
||||
this.overlayRef = this.contextMenuService.open({
|
||||
source: event,
|
||||
hasBackdrop: false,
|
||||
backdropClass: 'cdk-overlay-transparent-backdrop',
|
||||
panelClass: 'cdk-overlay-pane'
|
||||
});
|
||||
}
|
||||
|
||||
if (this.enabled) {
|
||||
this.execute(event);
|
||||
}
|
||||
}
|
||||
private updateSelection() {
|
||||
this.store.dispatch(
|
||||
new SetSelectedNodesAction(this.documentList.selection)
|
||||
);
|
||||
}
|
||||
|
||||
private isInSelection(row: DataRow): MinimalNodeEntity {
|
||||
return this.documentList.selection.find(
|
||||
selected => row.getValue('name') === selected.entry.name
|
||||
);
|
||||
}
|
||||
|
||||
private getSelectedRow(event): DataRow {
|
||||
const rowElement = this.findAncestor(
|
||||
<HTMLElement>event.target,
|
||||
'adf-datatable-row'
|
||||
);
|
||||
|
||||
if (!rowElement) {
|
||||
return null;
|
||||
}
|
||||
|
||||
constructor(
|
||||
private documentList: DocumentListComponent,
|
||||
private store: Store<AppStore>,
|
||||
private contextMenuService: ContextMenuService
|
||||
) { }
|
||||
const rowName = rowElement
|
||||
.querySelector('.adf-data-table-cell--text .adf-datatable-cell')
|
||||
.textContent.trim();
|
||||
|
||||
private execute(event: MouseEvent) {
|
||||
const selected = this.getSelectedRow(event);
|
||||
return this.documentList.data
|
||||
.getRows()
|
||||
.find((row: DataRow) => row.getValue('name') === rowName);
|
||||
}
|
||||
|
||||
if (selected) {
|
||||
if (!this.isInSelection(selected)) {
|
||||
this.clearSelection();
|
||||
private clearSelection() {
|
||||
this.documentList.data.getRows().map((row: DataRow) => {
|
||||
return this.documentList.dataTable.selectRow(row, false);
|
||||
});
|
||||
|
||||
this.documentList.dataTable.selectRow(selected, true);
|
||||
this.documentList.selection.push((<any>selected).node);
|
||||
this.documentList.selection = [];
|
||||
}
|
||||
|
||||
this.updateSelection();
|
||||
}
|
||||
|
||||
this.render(event);
|
||||
}
|
||||
}
|
||||
|
||||
private render(event: MouseEvent) {
|
||||
if (this.overlayRef) {
|
||||
this.overlayRef.close();
|
||||
}
|
||||
|
||||
this.overlayRef = this.contextMenuService.open({
|
||||
source: event,
|
||||
hasBackdrop: false,
|
||||
backdropClass: 'cdk-overlay-transparent-backdrop',
|
||||
panelClass: 'cdk-overlay-pane',
|
||||
});
|
||||
}
|
||||
|
||||
private updateSelection() {
|
||||
this.store.dispatch(
|
||||
new SetSelectedNodesAction(this.documentList.selection)
|
||||
);
|
||||
}
|
||||
|
||||
private isInSelection(row: DataRow): MinimalNodeEntity {
|
||||
return this.documentList.selection.find((selected) =>
|
||||
row.getValue('name') === selected.entry.name);
|
||||
}
|
||||
|
||||
private getSelectedRow(event): DataRow {
|
||||
const rowElement = this.findAncestor(<HTMLElement>event.target, 'adf-datatable-row');
|
||||
|
||||
if (!rowElement) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const rowName = rowElement.querySelector('.adf-data-table-cell--text .adf-datatable-cell')
|
||||
.textContent
|
||||
.trim();
|
||||
|
||||
return this.documentList.data.getRows()
|
||||
.find((row: DataRow) => row.getValue('name') === rowName);
|
||||
}
|
||||
|
||||
private clearSelection() {
|
||||
this.documentList.data.getRows().map((row: DataRow) => {
|
||||
return this.documentList.dataTable.selectRow(row, false);
|
||||
});
|
||||
|
||||
this.documentList.selection = [];
|
||||
}
|
||||
|
||||
private findAncestor (el: Element, className: string): Element {
|
||||
// tslint:disable-next-line:curly
|
||||
while ((el = el.parentElement) && !el.classList.contains(className));
|
||||
return el;
|
||||
}
|
||||
private findAncestor(el: Element, className: string): Element {
|
||||
// tslint:disable-next-line:curly
|
||||
while ((el = el.parentElement) && !el.classList.contains(className));
|
||||
return el;
|
||||
}
|
||||
}
|
||||
|
@ -24,7 +24,12 @@
|
||||
*/
|
||||
|
||||
import { NgModule, ModuleWithProviders } from '@angular/core';
|
||||
import { MatMenuModule, MatListModule, MatIconModule, MatButtonModule } from '@angular/material';
|
||||
import {
|
||||
MatMenuModule,
|
||||
MatListModule,
|
||||
MatIconModule,
|
||||
MatButtonModule
|
||||
} from '@angular/material';
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { CoreModule } from '@alfresco/adf-core';
|
||||
import { CoreExtensionsModule } from '../../extensions/core.extensions.module';
|
||||
@ -37,44 +42,40 @@ import { ExtensionsModule } from '@alfresco/adf-extensions';
|
||||
import { OutsideEventDirective } from './context-menu-outside-event.directive';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
MatMenuModule,
|
||||
MatListModule,
|
||||
MatIconModule,
|
||||
MatButtonModule,
|
||||
BrowserModule,
|
||||
CoreExtensionsModule.forChild(),
|
||||
CoreModule.forChild(),
|
||||
ExtensionsModule.forChild()
|
||||
],
|
||||
declarations: [
|
||||
ContextActionsDirective,
|
||||
ContextMenuComponent,
|
||||
ContextMenuItemDirective,
|
||||
OutsideEventDirective
|
||||
],
|
||||
exports: [
|
||||
OutsideEventDirective,
|
||||
ContextActionsDirective,
|
||||
ContextMenuComponent
|
||||
],
|
||||
entryComponents: [
|
||||
ContextMenuComponent
|
||||
]
|
||||
imports: [
|
||||
MatMenuModule,
|
||||
MatListModule,
|
||||
MatIconModule,
|
||||
MatButtonModule,
|
||||
BrowserModule,
|
||||
CoreExtensionsModule.forChild(),
|
||||
CoreModule.forChild(),
|
||||
ExtensionsModule.forChild()
|
||||
],
|
||||
declarations: [
|
||||
ContextActionsDirective,
|
||||
ContextMenuComponent,
|
||||
ContextMenuItemDirective,
|
||||
OutsideEventDirective
|
||||
],
|
||||
exports: [
|
||||
OutsideEventDirective,
|
||||
ContextActionsDirective,
|
||||
ContextMenuComponent
|
||||
],
|
||||
entryComponents: [ContextMenuComponent]
|
||||
})
|
||||
export class ContextMenuModule {
|
||||
static forRoot(): ModuleWithProviders {
|
||||
return {
|
||||
ngModule: ContextMenuModule,
|
||||
providers: [
|
||||
ContextMenuService
|
||||
]
|
||||
};
|
||||
}
|
||||
static forRoot(): ModuleWithProviders {
|
||||
return {
|
||||
ngModule: ContextMenuModule,
|
||||
providers: [ContextMenuService]
|
||||
};
|
||||
}
|
||||
|
||||
static forChild(): ModuleWithProviders {
|
||||
return {
|
||||
ngModule: ContextMenuModule
|
||||
};
|
||||
}
|
||||
static forChild(): ModuleWithProviders {
|
||||
return {
|
||||
ngModule: ContextMenuModule
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -7,96 +7,115 @@ import { ContextmenuOverlayConfig } from './interfaces';
|
||||
|
||||
@Injectable()
|
||||
export class ContextMenuService {
|
||||
constructor(
|
||||
private injector: Injector,
|
||||
private overlay: Overlay) { }
|
||||
constructor(private injector: Injector, private overlay: Overlay) {}
|
||||
|
||||
open(config: ContextmenuOverlayConfig) {
|
||||
open(config: ContextmenuOverlayConfig) {
|
||||
const overlay = this.createOverlay(config);
|
||||
|
||||
const overlay = this.createOverlay(config);
|
||||
const overlayRef = new ContextMenuOverlayRef(overlay);
|
||||
|
||||
const overlayRef = new ContextMenuOverlayRef(overlay);
|
||||
this.attachDialogContainer(overlay, config, overlayRef);
|
||||
|
||||
this.attachDialogContainer(overlay, config, overlayRef);
|
||||
overlay.backdropClick().subscribe(() => overlayRef.close());
|
||||
|
||||
overlay.backdropClick().subscribe(() => overlayRef.close());
|
||||
|
||||
// prevent native contextmenu on overlay element if config.hasBackdrop is true
|
||||
if (config.hasBackdrop) {
|
||||
(<any>overlay)._backdropElement
|
||||
.addEventListener('contextmenu', () => {
|
||||
event.preventDefault();
|
||||
(<any>overlay)._backdropClick.next(null);
|
||||
}, true);
|
||||
}
|
||||
|
||||
return overlayRef;
|
||||
// prevent native contextmenu on overlay element if config.hasBackdrop is true
|
||||
if (config.hasBackdrop) {
|
||||
(<any>overlay)._backdropElement.addEventListener(
|
||||
'contextmenu',
|
||||
() => {
|
||||
event.preventDefault();
|
||||
(<any>overlay)._backdropClick.next(null);
|
||||
},
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
private createOverlay(config: ContextmenuOverlayConfig) {
|
||||
const overlayConfig = this.getOverlayConfig(config);
|
||||
return this.overlay.create(overlayConfig);
|
||||
}
|
||||
return overlayRef;
|
||||
}
|
||||
|
||||
private attachDialogContainer(overlay: OverlayRef, config: ContextmenuOverlayConfig, contextmenuOverlayRef: ContextMenuOverlayRef) {
|
||||
const injector = this.createInjector(config, contextmenuOverlayRef);
|
||||
private createOverlay(config: ContextmenuOverlayConfig) {
|
||||
const overlayConfig = this.getOverlayConfig(config);
|
||||
return this.overlay.create(overlayConfig);
|
||||
}
|
||||
|
||||
const containerPortal = new ComponentPortal(ContextMenuComponent, null, injector);
|
||||
const containerRef: ComponentRef<ContextMenuComponent> = overlay.attach(containerPortal);
|
||||
private attachDialogContainer(
|
||||
overlay: OverlayRef,
|
||||
config: ContextmenuOverlayConfig,
|
||||
contextmenuOverlayRef: ContextMenuOverlayRef
|
||||
) {
|
||||
const injector = this.createInjector(config, contextmenuOverlayRef);
|
||||
|
||||
return containerRef.instance;
|
||||
}
|
||||
const containerPortal = new ComponentPortal(
|
||||
ContextMenuComponent,
|
||||
null,
|
||||
injector
|
||||
);
|
||||
const containerRef: ComponentRef<ContextMenuComponent> = overlay.attach(
|
||||
containerPortal
|
||||
);
|
||||
|
||||
private createInjector(config: ContextmenuOverlayConfig, contextmenuOverlayRef: ContextMenuOverlayRef): PortalInjector {
|
||||
const injectionTokens = new WeakMap();
|
||||
return containerRef.instance;
|
||||
}
|
||||
|
||||
injectionTokens.set(ContextMenuOverlayRef, contextmenuOverlayRef);
|
||||
private createInjector(
|
||||
config: ContextmenuOverlayConfig,
|
||||
contextmenuOverlayRef: ContextMenuOverlayRef
|
||||
): PortalInjector {
|
||||
const injectionTokens = new WeakMap();
|
||||
|
||||
return new PortalInjector(this.injector, injectionTokens);
|
||||
}
|
||||
injectionTokens.set(ContextMenuOverlayRef, contextmenuOverlayRef);
|
||||
|
||||
private getOverlayConfig(config: ContextmenuOverlayConfig): OverlayConfig {
|
||||
const fakeElement: any = {
|
||||
getBoundingClientRect: (): ClientRect => ({
|
||||
bottom: config.source.clientY,
|
||||
height: 0,
|
||||
left: config.source.clientX,
|
||||
right: config.source.clientX,
|
||||
top: config.source.clientY,
|
||||
width: 0
|
||||
})
|
||||
};
|
||||
return new PortalInjector(this.injector, injectionTokens);
|
||||
}
|
||||
|
||||
const positionStrategy = this.overlay.position()
|
||||
.connectedTo(
|
||||
new ElementRef(fakeElement),
|
||||
{ originX: 'start', originY: 'bottom' },
|
||||
{ overlayX: 'start', overlayY: 'top' })
|
||||
.withFallbackPosition(
|
||||
{ originX: 'start', originY: 'top' },
|
||||
{ overlayX: 'start', overlayY: 'bottom' })
|
||||
.withFallbackPosition(
|
||||
{ originX: 'end', originY: 'top' },
|
||||
{ overlayX: 'start', overlayY: 'top' })
|
||||
.withFallbackPosition(
|
||||
{ originX: 'start', originY: 'top' },
|
||||
{ overlayX: 'end', overlayY: 'top' })
|
||||
.withFallbackPosition(
|
||||
{ originX: 'end', originY: 'center' },
|
||||
{ overlayX: 'start', overlayY: 'center' })
|
||||
.withFallbackPosition(
|
||||
{ originX: 'start', originY: 'center' },
|
||||
{ overlayX: 'end', overlayY: 'center' }
|
||||
);
|
||||
private getOverlayConfig(config: ContextmenuOverlayConfig): OverlayConfig {
|
||||
const fakeElement: any = {
|
||||
getBoundingClientRect: (): ClientRect => ({
|
||||
bottom: config.source.clientY,
|
||||
height: 0,
|
||||
left: config.source.clientX,
|
||||
right: config.source.clientX,
|
||||
top: config.source.clientY,
|
||||
width: 0
|
||||
})
|
||||
};
|
||||
|
||||
const overlayConfig = new OverlayConfig({
|
||||
hasBackdrop: config.hasBackdrop,
|
||||
backdropClass: config.backdropClass,
|
||||
panelClass: config.panelClass,
|
||||
scrollStrategy: this.overlay.scrollStrategies.close(),
|
||||
positionStrategy
|
||||
});
|
||||
const positionStrategy = this.overlay
|
||||
.position()
|
||||
.connectedTo(
|
||||
new ElementRef(fakeElement),
|
||||
{ originX: 'start', originY: 'bottom' },
|
||||
{ overlayX: 'start', overlayY: 'top' }
|
||||
)
|
||||
.withFallbackPosition(
|
||||
{ originX: 'start', originY: 'top' },
|
||||
{ overlayX: 'start', overlayY: 'bottom' }
|
||||
)
|
||||
.withFallbackPosition(
|
||||
{ originX: 'end', originY: 'top' },
|
||||
{ overlayX: 'start', overlayY: 'top' }
|
||||
)
|
||||
.withFallbackPosition(
|
||||
{ originX: 'start', originY: 'top' },
|
||||
{ overlayX: 'end', overlayY: 'top' }
|
||||
)
|
||||
.withFallbackPosition(
|
||||
{ originX: 'end', originY: 'center' },
|
||||
{ overlayX: 'start', overlayY: 'center' }
|
||||
)
|
||||
.withFallbackPosition(
|
||||
{ originX: 'start', originY: 'center' },
|
||||
{ overlayX: 'end', overlayY: 'center' }
|
||||
);
|
||||
|
||||
return overlayConfig;
|
||||
}
|
||||
const overlayConfig = new OverlayConfig({
|
||||
hasBackdrop: config.hasBackdrop,
|
||||
backdropClass: config.backdropClass,
|
||||
panelClass: config.panelClass,
|
||||
scrollStrategy: this.overlay.scrollStrategies.close(),
|
||||
positionStrategy
|
||||
});
|
||||
|
||||
return overlayConfig;
|
||||
}
|
||||
}
|
||||
|
@ -24,9 +24,9 @@
|
||||
*/
|
||||
|
||||
export interface ContextmenuOverlayConfig {
|
||||
panelClass?: string;
|
||||
hasBackdrop?: boolean;
|
||||
backdropClass?: string;
|
||||
source?: MouseEvent;
|
||||
data?: any;
|
||||
panelClass?: string;
|
||||
hasBackdrop?: boolean;
|
||||
backdropClass?: string;
|
||||
source?: MouseEvent;
|
||||
data?: any;
|
||||
}
|
||||
|
@ -1,44 +1,44 @@
|
||||
@mixin aca-current-user-theme($theme) {
|
||||
$background: map-get($theme, background);
|
||||
$am-avatar-size: 35px;
|
||||
$background: map-get($theme, background);
|
||||
$am-avatar-size: 35px;
|
||||
|
||||
.aca-current-user {
|
||||
position: relative;
|
||||
color: mat-color($background, card);
|
||||
line-height: 20px;
|
||||
.aca-current-user {
|
||||
position: relative;
|
||||
color: mat-color($background, card);
|
||||
line-height: 20px;
|
||||
|
||||
.am-avatar {
|
||||
margin-left: 9px;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
width: $am-avatar-size;
|
||||
height: $am-avatar-size;
|
||||
line-height: $am-avatar-size;
|
||||
font-size: 1em;
|
||||
text-align: center;
|
||||
color: inherit;
|
||||
border-radius: 100%;
|
||||
background-color: mat-color($background, card, .15);
|
||||
}
|
||||
|
||||
.current-user__full-name {
|
||||
width: 100px;
|
||||
text-align: right;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
display:inline-block;
|
||||
height: 18px;
|
||||
font-size: 14px;
|
||||
line-height: 1.43;
|
||||
letter-spacing: -0.3px;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
@media screen and ($mat-small) {
|
||||
.current-user__full-name {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
.am-avatar {
|
||||
margin-left: 9px;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
width: $am-avatar-size;
|
||||
height: $am-avatar-size;
|
||||
line-height: $am-avatar-size;
|
||||
font-size: 1em;
|
||||
text-align: center;
|
||||
color: inherit;
|
||||
border-radius: 100%;
|
||||
background-color: mat-color($background, card, 0.15);
|
||||
}
|
||||
|
||||
.current-user__full-name {
|
||||
width: 100px;
|
||||
text-align: right;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
display: inline-block;
|
||||
height: 18px;
|
||||
font-size: 14px;
|
||||
line-height: 1.43;
|
||||
letter-spacing: -0.3px;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
@media screen and ($mat-small) {
|
||||
.current-user__full-name {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -26,27 +26,30 @@
|
||||
import { Component, ViewEncapsulation } from '@angular/core';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { Observable } from 'rxjs';
|
||||
import { selectUser, appLanguagePicker } from '../../store/selectors/app.selectors';
|
||||
import {
|
||||
selectUser,
|
||||
appLanguagePicker
|
||||
} from '../../store/selectors/app.selectors';
|
||||
import { AppStore } from '../../store/states';
|
||||
import { ProfileState } from '@alfresco/adf-extensions';
|
||||
import { SetSelectedNodesAction } from '../../store/actions';
|
||||
|
||||
@Component({
|
||||
selector: 'aca-current-user',
|
||||
templateUrl: './current-user.component.html',
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
host: { class: 'aca-current-user' }
|
||||
selector: 'aca-current-user',
|
||||
templateUrl: './current-user.component.html',
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
host: { class: 'aca-current-user' }
|
||||
})
|
||||
export class CurrentUserComponent {
|
||||
profile$: Observable<ProfileState>;
|
||||
languagePicker$: Observable<boolean>;
|
||||
profile$: Observable<ProfileState>;
|
||||
languagePicker$: Observable<boolean>;
|
||||
|
||||
constructor(private store: Store<AppStore>) {
|
||||
this.profile$ = this.store.select(selectUser);
|
||||
this.languagePicker$ = store.select(appLanguagePicker);
|
||||
}
|
||||
constructor(private store: Store<AppStore>) {
|
||||
this.profile$ = this.store.select(selectUser);
|
||||
this.languagePicker$ = store.select(appLanguagePicker);
|
||||
}
|
||||
|
||||
onLogoutEvent() {
|
||||
this.store.dispatch(new SetSelectedNodesAction([]));
|
||||
}
|
||||
onLogoutEvent() {
|
||||
this.store.dispatch(new SetSelectedNodesAction([]));
|
||||
}
|
||||
}
|
||||
|
@ -27,9 +27,12 @@ import { NO_ERRORS_SCHEMA } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { TestBed, ComponentFixture } from '@angular/core/testing';
|
||||
import {
|
||||
AlfrescoApiService,
|
||||
TimeAgoPipe, NodeNameTooltipPipe,
|
||||
NodeFavoriteDirective, DataTableComponent, AppConfigPipe
|
||||
AlfrescoApiService,
|
||||
TimeAgoPipe,
|
||||
NodeNameTooltipPipe,
|
||||
NodeFavoriteDirective,
|
||||
DataTableComponent,
|
||||
AppConfigPipe
|
||||
} from '@alfresco/adf-core';
|
||||
import { DocumentListComponent } from '@alfresco/adf-content-services';
|
||||
import { ContentManagementService } from '../../services/content-management.service';
|
||||
@ -40,136 +43,144 @@ import { ContentApiService } from '../../services/content-api.service';
|
||||
import { ExperimentalDirective } from '../../directives/experimental.directive';
|
||||
|
||||
describe('FavoritesComponent', () => {
|
||||
let fixture: ComponentFixture<FavoritesComponent>;
|
||||
let component: FavoritesComponent;
|
||||
let alfrescoApi: AlfrescoApiService;
|
||||
let contentService: ContentManagementService;
|
||||
let contentApi: ContentApiService;
|
||||
let router: Router;
|
||||
let page;
|
||||
let node;
|
||||
let fixture: ComponentFixture<FavoritesComponent>;
|
||||
let component: FavoritesComponent;
|
||||
let alfrescoApi: AlfrescoApiService;
|
||||
let contentService: ContentManagementService;
|
||||
let contentApi: ContentApiService;
|
||||
let router: Router;
|
||||
let page;
|
||||
let node;
|
||||
|
||||
beforeEach(() => {
|
||||
page = {
|
||||
list: {
|
||||
entries: [
|
||||
{ entry: { id: 1, target: { file: {} } } },
|
||||
{ entry: { id: 2, target: { folder: {} } } }
|
||||
],
|
||||
pagination: { data: 'data' }
|
||||
}
|
||||
};
|
||||
|
||||
node = <any>{
|
||||
id: 'folder-node',
|
||||
isFolder: true,
|
||||
isFile: false,
|
||||
path: {
|
||||
elements: []
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [AppTestingModule],
|
||||
declarations: [
|
||||
DataTableComponent,
|
||||
TimeAgoPipe,
|
||||
NodeNameTooltipPipe,
|
||||
NodeFavoriteDirective,
|
||||
DocumentListComponent,
|
||||
FavoritesComponent,
|
||||
AppConfigPipe,
|
||||
ExperimentalDirective
|
||||
],
|
||||
schemas: [NO_ERRORS_SCHEMA]
|
||||
});
|
||||
|
||||
fixture = TestBed.createComponent(FavoritesComponent);
|
||||
component = fixture.componentInstance;
|
||||
|
||||
alfrescoApi = TestBed.get(AlfrescoApiService);
|
||||
alfrescoApi.reset();
|
||||
spyOn(alfrescoApi.favoritesApi, 'getFavorites').and.returnValue(
|
||||
Promise.resolve(page)
|
||||
);
|
||||
|
||||
contentApi = TestBed.get(ContentApiService);
|
||||
|
||||
contentService = TestBed.get(ContentManagementService);
|
||||
router = TestBed.get(Router);
|
||||
});
|
||||
|
||||
describe('Events', () => {
|
||||
beforeEach(() => {
|
||||
page = {
|
||||
list: {
|
||||
entries: [
|
||||
{ entry: { id: 1, target: { file: {} } } },
|
||||
{ entry: { id: 2, target: { folder: {} } } }
|
||||
],
|
||||
pagination: { data: 'data'}
|
||||
}
|
||||
};
|
||||
|
||||
node = <any> {
|
||||
id: 'folder-node',
|
||||
isFolder: true,
|
||||
isFile: false,
|
||||
path: {
|
||||
elements: []
|
||||
}
|
||||
};
|
||||
spyOn(component, 'reload');
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should refresh on editing folder event', () => {
|
||||
contentService.folderEdited.next(null);
|
||||
|
||||
expect(component.reload).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should refresh on move node event', () => {
|
||||
contentService.nodesMoved.next(null);
|
||||
|
||||
expect(component.reload).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should refresh on node deleted event', () => {
|
||||
contentService.nodesDeleted.next(null);
|
||||
|
||||
expect(component.reload).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should refresh on node restore event', () => {
|
||||
contentService.nodesRestored.next(null);
|
||||
|
||||
expect(component.reload).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Node navigation', () => {
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [ AppTestingModule ],
|
||||
declarations: [
|
||||
DataTableComponent,
|
||||
TimeAgoPipe,
|
||||
NodeNameTooltipPipe,
|
||||
NodeFavoriteDirective,
|
||||
DocumentListComponent,
|
||||
FavoritesComponent,
|
||||
AppConfigPipe,
|
||||
ExperimentalDirective
|
||||
],
|
||||
schemas: [ NO_ERRORS_SCHEMA ]
|
||||
});
|
||||
|
||||
fixture = TestBed.createComponent(FavoritesComponent);
|
||||
component = fixture.componentInstance;
|
||||
|
||||
alfrescoApi = TestBed.get(AlfrescoApiService);
|
||||
alfrescoApi.reset();
|
||||
spyOn(alfrescoApi.favoritesApi, 'getFavorites').and.returnValue(Promise.resolve(page));
|
||||
|
||||
contentApi = TestBed.get(ContentApiService);
|
||||
|
||||
contentService = TestBed.get(ContentManagementService);
|
||||
router = TestBed.get(Router);
|
||||
spyOn(contentApi, 'getNode').and.returnValue(of({ entry: node }));
|
||||
spyOn(router, 'navigate');
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
describe('Events', () => {
|
||||
beforeEach(() => {
|
||||
spyOn(component, 'reload');
|
||||
fixture.detectChanges();
|
||||
});
|
||||
it('navigates to `/libraries` if node path has `Sites`', () => {
|
||||
node.path.elements = [{ name: 'Sites' }];
|
||||
|
||||
it('should refresh on editing folder event', () => {
|
||||
contentService.folderEdited.next(null);
|
||||
component.navigate(node);
|
||||
|
||||
expect(component.reload).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should refresh on move node event', () => {
|
||||
contentService.nodesMoved.next(null);
|
||||
|
||||
expect(component.reload).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should refresh on node deleted event', () => {
|
||||
contentService.nodesDeleted.next(null);
|
||||
|
||||
expect(component.reload).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should refresh on node restore event', () => {
|
||||
contentService.nodesRestored.next(null);
|
||||
|
||||
expect(component.reload).toHaveBeenCalled();
|
||||
});
|
||||
expect(router.navigate).toHaveBeenCalledWith([
|
||||
'/libraries',
|
||||
'folder-node'
|
||||
]);
|
||||
});
|
||||
|
||||
describe('Node navigation', () => {
|
||||
beforeEach(() => {
|
||||
spyOn(contentApi, 'getNode').and.returnValue(of({ entry: node}));
|
||||
spyOn(router, 'navigate');
|
||||
fixture.detectChanges();
|
||||
});
|
||||
it('navigates to `/personal-files` if node path has no `Sites`', () => {
|
||||
node.path.elements = [{ name: 'something else' }];
|
||||
|
||||
it('navigates to `/libraries` if node path has `Sites`', () => {
|
||||
node.path.elements = [{ name: 'Sites' }];
|
||||
component.navigate(node);
|
||||
|
||||
component.navigate(node);
|
||||
|
||||
expect(router.navigate).toHaveBeenCalledWith([ '/libraries', 'folder-node' ]);
|
||||
});
|
||||
|
||||
it('navigates to `/personal-files` if node path has no `Sites`', () => {
|
||||
node.path.elements = [{ name: 'something else' }];
|
||||
|
||||
component.navigate(node);
|
||||
|
||||
expect(router.navigate).toHaveBeenCalledWith([ '/personal-files', 'folder-node' ]);
|
||||
});
|
||||
|
||||
it('does not navigate when node is not folder', () => {
|
||||
node.isFolder = false;
|
||||
|
||||
component.navigate(node);
|
||||
|
||||
expect(router.navigate).not.toHaveBeenCalled();
|
||||
});
|
||||
expect(router.navigate).toHaveBeenCalledWith([
|
||||
'/personal-files',
|
||||
'folder-node'
|
||||
]);
|
||||
});
|
||||
|
||||
describe('refresh', () => {
|
||||
it('should call document list reload', () => {
|
||||
spyOn(component.documentList, 'reload');
|
||||
fixture.detectChanges();
|
||||
it('does not navigate when node is not folder', () => {
|
||||
node.isFolder = false;
|
||||
|
||||
component.reload();
|
||||
component.navigate(node);
|
||||
|
||||
expect(component.documentList.reload).toHaveBeenCalled();
|
||||
});
|
||||
expect(router.navigate).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('refresh', () => {
|
||||
it('should call document list reload', () => {
|
||||
spyOn(component.documentList, 'reload');
|
||||
fixture.detectChanges();
|
||||
|
||||
component.reload();
|
||||
|
||||
expect(component.documentList.reload).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -28,10 +28,10 @@ import { Router } from '@angular/router';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { BreakpointObserver, Breakpoints } from '@angular/cdk/layout';
|
||||
import {
|
||||
MinimalNodeEntity,
|
||||
MinimalNodeEntryEntity,
|
||||
PathElementEntity,
|
||||
PathInfo
|
||||
MinimalNodeEntity,
|
||||
MinimalNodeEntryEntity,
|
||||
PathElementEntity,
|
||||
PathInfo
|
||||
} from 'alfresco-js-api';
|
||||
import { ContentManagementService } from '../../services/content-management.service';
|
||||
import { AppStore } from '../../store/states/app.state';
|
||||
@ -41,76 +41,71 @@ import { AppExtensionService } from '../../extensions/extension.service';
|
||||
import { map } from 'rxjs/operators';
|
||||
|
||||
@Component({
|
||||
templateUrl: './favorites.component.html'
|
||||
templateUrl: './favorites.component.html'
|
||||
})
|
||||
export class FavoritesComponent extends PageComponent implements OnInit {
|
||||
isSmallScreen = false;
|
||||
isSmallScreen = false;
|
||||
|
||||
constructor(
|
||||
private router: Router,
|
||||
store: Store<AppStore>,
|
||||
extensions: AppExtensionService,
|
||||
private contentApi: ContentApiService,
|
||||
content: ContentManagementService,
|
||||
private breakpointObserver: BreakpointObserver
|
||||
) {
|
||||
super(store, extensions, content);
|
||||
constructor(
|
||||
private router: Router,
|
||||
store: Store<AppStore>,
|
||||
extensions: AppExtensionService,
|
||||
private contentApi: ContentApiService,
|
||||
content: ContentManagementService,
|
||||
private breakpointObserver: BreakpointObserver
|
||||
) {
|
||||
super(store, extensions, content);
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
super.ngOnInit();
|
||||
|
||||
this.subscriptions = this.subscriptions.concat([
|
||||
this.content.nodesDeleted.subscribe(() => this.reload()),
|
||||
this.content.nodesRestored.subscribe(() => this.reload()),
|
||||
this.content.folderEdited.subscribe(() => this.reload()),
|
||||
this.content.nodesMoved.subscribe(() => this.reload()),
|
||||
this.content.favoriteRemoved.subscribe(() => this.reload()),
|
||||
this.content.favoriteToggle.subscribe(() => this.reload()),
|
||||
|
||||
this.breakpointObserver
|
||||
.observe([Breakpoints.HandsetPortrait, Breakpoints.HandsetLandscape])
|
||||
.subscribe(result => {
|
||||
this.isSmallScreen = result.matches;
|
||||
})
|
||||
]);
|
||||
}
|
||||
|
||||
navigate(favorite: MinimalNodeEntryEntity) {
|
||||
const { isFolder, id } = favorite;
|
||||
|
||||
// TODO: rework as it will fail on non-English setups
|
||||
const isSitePath = (path: PathInfo): boolean => {
|
||||
return path.elements.some(
|
||||
({ name }: PathElementEntity) => name === 'Sites'
|
||||
);
|
||||
};
|
||||
|
||||
if (isFolder) {
|
||||
this.contentApi
|
||||
.getNode(id)
|
||||
.pipe(map(node => node.entry))
|
||||
.subscribe(({ path }: MinimalNodeEntryEntity) => {
|
||||
const routeUrl = isSitePath(path) ? '/libraries' : '/personal-files';
|
||||
this.router.navigate([routeUrl, id]);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
super.ngOnInit();
|
||||
onNodeDoubleClick(node: MinimalNodeEntity) {
|
||||
if (node && node.entry) {
|
||||
if (node.entry.isFolder) {
|
||||
this.navigate(node.entry);
|
||||
}
|
||||
|
||||
this.subscriptions = this.subscriptions.concat([
|
||||
this.content.nodesDeleted.subscribe(() => this.reload()),
|
||||
this.content.nodesRestored.subscribe(() => this.reload()),
|
||||
this.content.folderEdited.subscribe(() => this.reload()),
|
||||
this.content.nodesMoved.subscribe(() => this.reload()),
|
||||
this.content.favoriteRemoved.subscribe(() => this.reload()),
|
||||
this.content.favoriteToggle.subscribe(() => this.reload()),
|
||||
|
||||
this.breakpointObserver
|
||||
.observe([
|
||||
Breakpoints.HandsetPortrait,
|
||||
Breakpoints.HandsetLandscape
|
||||
])
|
||||
.subscribe(result => {
|
||||
this.isSmallScreen = result.matches;
|
||||
})
|
||||
]);
|
||||
}
|
||||
|
||||
navigate(favorite: MinimalNodeEntryEntity) {
|
||||
const { isFolder, id } = favorite;
|
||||
|
||||
// TODO: rework as it will fail on non-English setups
|
||||
const isSitePath = (path: PathInfo): boolean => {
|
||||
return path.elements.some(
|
||||
({ name }: PathElementEntity) => name === 'Sites'
|
||||
);
|
||||
};
|
||||
|
||||
if (isFolder) {
|
||||
this.contentApi
|
||||
.getNode(id)
|
||||
.pipe(map(node => node.entry))
|
||||
.subscribe(({ path }: MinimalNodeEntryEntity) => {
|
||||
const routeUrl = isSitePath(path)
|
||||
? '/libraries'
|
||||
: '/personal-files';
|
||||
this.router.navigate([routeUrl, id]);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
onNodeDoubleClick(node: MinimalNodeEntity) {
|
||||
if (node && node.entry) {
|
||||
if (node.entry.isFolder) {
|
||||
this.navigate(node.entry);
|
||||
}
|
||||
|
||||
if (node.entry.isFile) {
|
||||
this.showPreview(node);
|
||||
}
|
||||
}
|
||||
if (node.entry.isFile) {
|
||||
this.showPreview(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -23,14 +23,22 @@
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { TestBed, fakeAsync, tick, ComponentFixture } from '@angular/core/testing';
|
||||
import {
|
||||
TestBed,
|
||||
fakeAsync,
|
||||
tick,
|
||||
ComponentFixture
|
||||
} from '@angular/core/testing';
|
||||
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
||||
import { Router, ActivatedRoute } from '@angular/router';
|
||||
import {
|
||||
TimeAgoPipe, NodeNameTooltipPipe, FileSizePipe, NodeFavoriteDirective,
|
||||
DataTableComponent,
|
||||
UploadService,
|
||||
AppConfigPipe
|
||||
TimeAgoPipe,
|
||||
NodeNameTooltipPipe,
|
||||
FileSizePipe,
|
||||
NodeFavoriteDirective,
|
||||
DataTableComponent,
|
||||
UploadService,
|
||||
AppConfigPipe
|
||||
} from '@alfresco/adf-core';
|
||||
import { DocumentListComponent } from '@alfresco/adf-content-services';
|
||||
import { ContentManagementService } from '../../services/content-management.service';
|
||||
@ -42,270 +50,278 @@ import { ExperimentalDirective } from '../../directives/experimental.directive';
|
||||
import { of, throwError } from 'rxjs';
|
||||
|
||||
describe('FilesComponent', () => {
|
||||
let node;
|
||||
let fixture: ComponentFixture<FilesComponent>;
|
||||
let component: FilesComponent;
|
||||
let contentManagementService: ContentManagementService;
|
||||
let uploadService: UploadService;
|
||||
let router: Router;
|
||||
let nodeActionsService: NodeActionsService;
|
||||
let contentApi: ContentApiService;
|
||||
let node;
|
||||
let fixture: ComponentFixture<FilesComponent>;
|
||||
let component: FilesComponent;
|
||||
let contentManagementService: ContentManagementService;
|
||||
let uploadService: UploadService;
|
||||
let router: Router;
|
||||
let nodeActionsService: NodeActionsService;
|
||||
let contentApi: ContentApiService;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [AppTestingModule],
|
||||
declarations: [
|
||||
FilesComponent,
|
||||
DataTableComponent,
|
||||
TimeAgoPipe,
|
||||
NodeNameTooltipPipe,
|
||||
NodeFavoriteDirective,
|
||||
DocumentListComponent,
|
||||
FileSizePipe,
|
||||
AppConfigPipe,
|
||||
ExperimentalDirective
|
||||
],
|
||||
providers: [
|
||||
{
|
||||
provide: ActivatedRoute,
|
||||
useValue: {
|
||||
snapshot: { data: { preferencePrefix: 'prefix' } },
|
||||
params: of({ folderId: 'someId' })
|
||||
}
|
||||
}
|
||||
],
|
||||
schemas: [NO_ERRORS_SCHEMA]
|
||||
});
|
||||
|
||||
fixture = TestBed.createComponent(FilesComponent);
|
||||
component = fixture.componentInstance;
|
||||
|
||||
contentManagementService = TestBed.get(ContentManagementService);
|
||||
uploadService = TestBed.get(UploadService);
|
||||
router = TestBed.get(Router);
|
||||
nodeActionsService = TestBed.get(NodeActionsService);
|
||||
contentApi = TestBed.get(ContentApiService);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
node = { id: 'node-id', isFolder: true };
|
||||
spyOn(component.documentList, 'loadFolder').and.callFake(() => {});
|
||||
});
|
||||
|
||||
describe('Current page is valid', () => {
|
||||
it('should be a valid current page', fakeAsync(() => {
|
||||
spyOn(contentApi, 'getNode').and.returnValue(throwError(null));
|
||||
|
||||
component.ngOnInit();
|
||||
fixture.detectChanges();
|
||||
tick();
|
||||
|
||||
expect(component.isValidPath).toBe(false);
|
||||
}));
|
||||
|
||||
it('should set current page as invalid path', fakeAsync(() => {
|
||||
spyOn(contentApi, 'getNode').and.returnValue(of({ entry: node }));
|
||||
|
||||
component.ngOnInit();
|
||||
tick();
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(component.isValidPath).toBe(true);
|
||||
}));
|
||||
});
|
||||
|
||||
describe('OnInit', () => {
|
||||
it('should set current node', () => {
|
||||
spyOn(contentApi, 'getNode').and.returnValue(of({ entry: node }));
|
||||
fixture.detectChanges();
|
||||
expect(component.node).toBe(node);
|
||||
});
|
||||
|
||||
it('if should navigate to parent if node is not a folder', () => {
|
||||
node.isFolder = false;
|
||||
node.parentId = 'parent-id';
|
||||
spyOn(contentApi, 'getNode').and.returnValue(of({ entry: node }));
|
||||
spyOn(router, 'navigate');
|
||||
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(router.navigate['calls'].argsFor(0)[0]).toEqual([
|
||||
'/personal-files',
|
||||
'parent-id'
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('refresh on events', () => {
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [ AppTestingModule ],
|
||||
declarations: [
|
||||
FilesComponent,
|
||||
DataTableComponent,
|
||||
TimeAgoPipe,
|
||||
NodeNameTooltipPipe,
|
||||
NodeFavoriteDirective,
|
||||
DocumentListComponent,
|
||||
FileSizePipe,
|
||||
AppConfigPipe,
|
||||
ExperimentalDirective
|
||||
],
|
||||
providers: [
|
||||
{ provide: ActivatedRoute, useValue: {
|
||||
snapshot: { data: { preferencePrefix: 'prefix' } },
|
||||
params: of({ folderId: 'someId' })
|
||||
} }
|
||||
],
|
||||
schemas: [ NO_ERRORS_SCHEMA ]
|
||||
});
|
||||
spyOn(contentApi, 'getNode').and.returnValue(of({ entry: node }));
|
||||
spyOn(component.documentList, 'reload');
|
||||
|
||||
fixture = TestBed.createComponent(FilesComponent);
|
||||
component = fixture.componentInstance;
|
||||
|
||||
contentManagementService = TestBed.get(ContentManagementService);
|
||||
uploadService = TestBed.get(UploadService);
|
||||
router = TestBed.get(Router);
|
||||
nodeActionsService = TestBed.get(NodeActionsService);
|
||||
contentApi = TestBed.get(ContentApiService);
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should call refresh onContentCopied event if parent is the same', () => {
|
||||
const nodes = [
|
||||
{ entry: { parentId: '1' } },
|
||||
{ entry: { parentId: '2' } }
|
||||
];
|
||||
|
||||
component.node = { id: '1' };
|
||||
|
||||
nodeActionsService.contentCopied.next(nodes);
|
||||
|
||||
expect(component.documentList.reload).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should not call refresh onContentCopied event when parent mismatch', () => {
|
||||
const nodes = [
|
||||
{ entry: { parentId: '1' } },
|
||||
{ entry: { parentId: '2' } }
|
||||
];
|
||||
|
||||
component.node = { id: '3' };
|
||||
|
||||
nodeActionsService.contentCopied.next(nodes);
|
||||
|
||||
expect(component.documentList.reload).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should call refresh onCreateFolder event', () => {
|
||||
contentManagementService.folderCreated.next();
|
||||
|
||||
expect(component.documentList.reload).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should call refresh editFolder event', () => {
|
||||
contentManagementService.folderEdited.next();
|
||||
|
||||
expect(component.documentList.reload).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should call refresh deleteNode event', () => {
|
||||
contentManagementService.nodesDeleted.next();
|
||||
|
||||
expect(component.documentList.reload).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should call refresh moveNode event', () => {
|
||||
contentManagementService.nodesMoved.next();
|
||||
|
||||
expect(component.documentList.reload).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should call refresh restoreNode event', () => {
|
||||
contentManagementService.nodesRestored.next();
|
||||
|
||||
expect(component.documentList.reload).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should call refresh on fileUploadComplete event if parent node match', fakeAsync(() => {
|
||||
const file = { file: { options: { parentId: 'parentId' } } };
|
||||
component.node = { id: 'parentId' };
|
||||
|
||||
uploadService.fileUploadComplete.next(<any>file);
|
||||
|
||||
tick(500);
|
||||
|
||||
expect(component.documentList.reload).toHaveBeenCalled();
|
||||
}));
|
||||
|
||||
it('should not call refresh on fileUploadComplete event if parent mismatch', fakeAsync(() => {
|
||||
const file = { file: { options: { parentId: 'otherId' } } };
|
||||
component.node = { id: 'parentId' };
|
||||
|
||||
uploadService.fileUploadComplete.next(<any>file);
|
||||
|
||||
tick(500);
|
||||
|
||||
expect(component.documentList.reload).not.toHaveBeenCalled();
|
||||
}));
|
||||
|
||||
it('should call refresh on fileUploadDeleted event if parent node match', fakeAsync(() => {
|
||||
const file = { file: { options: { parentId: 'parentId' } } };
|
||||
component.node = { id: 'parentId' };
|
||||
|
||||
uploadService.fileUploadDeleted.next(<any>file);
|
||||
|
||||
tick(500);
|
||||
|
||||
expect(component.documentList.reload).toHaveBeenCalled();
|
||||
}));
|
||||
|
||||
it('should not call refresh on fileUploadDeleted event if parent mismatch', fakeAsync(() => {
|
||||
const file: any = { file: { options: { parentId: 'otherId' } } };
|
||||
component.node = { id: 'parentId' };
|
||||
|
||||
uploadService.fileUploadDeleted.next(file);
|
||||
|
||||
tick(500);
|
||||
|
||||
expect(component.documentList.reload).not.toHaveBeenCalled();
|
||||
}));
|
||||
});
|
||||
|
||||
describe('onBreadcrumbNavigate()', () => {
|
||||
beforeEach(() => {
|
||||
node = { id: 'node-id', isFolder: true };
|
||||
spyOn(component.documentList, 'loadFolder').and.callFake(() => {});
|
||||
spyOn(contentApi, 'getNode').and.returnValue(of({ entry: node }));
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
describe('Current page is valid', () => {
|
||||
it('should be a valid current page', fakeAsync(() => {
|
||||
spyOn(contentApi, 'getNode').and.returnValue(throwError(null));
|
||||
it('should navigates to node id', () => {
|
||||
const routeData: any = { id: 'some-where-over-the-rainbow' };
|
||||
spyOn(component, 'navigate');
|
||||
|
||||
component.ngOnInit();
|
||||
fixture.detectChanges();
|
||||
tick();
|
||||
component.onBreadcrumbNavigate(routeData);
|
||||
|
||||
expect(component.isValidPath).toBe(false);
|
||||
}));
|
||||
expect(component.navigate).toHaveBeenCalledWith(routeData.id);
|
||||
});
|
||||
});
|
||||
|
||||
it('should set current page as invalid path', fakeAsync(() => {
|
||||
spyOn(contentApi, 'getNode').and.returnValue(of({ entry: node }));
|
||||
describe('Node navigation', () => {
|
||||
beforeEach(() => {
|
||||
spyOn(contentApi, 'getNode').and.returnValue(of({ entry: node }));
|
||||
spyOn(router, 'navigate');
|
||||
|
||||
component.ngOnInit();
|
||||
tick();
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(component.isValidPath).toBe(true);
|
||||
}));
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
describe('OnInit', () => {
|
||||
it('should set current node', () => {
|
||||
spyOn(contentApi, 'getNode').and.returnValue(of({ entry: node }));
|
||||
fixture.detectChanges();
|
||||
expect(component.node).toBe(node);
|
||||
});
|
||||
it('should navigates to node when id provided', () => {
|
||||
component.navigate(node.id);
|
||||
|
||||
it('if should navigate to parent if node is not a folder', () => {
|
||||
node.isFolder = false;
|
||||
node.parentId = 'parent-id';
|
||||
spyOn(contentApi, 'getNode').and.returnValue(of({ entry: node }));
|
||||
spyOn(router, 'navigate');
|
||||
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(router.navigate['calls'].argsFor(0)[0]).toEqual(['/personal-files', 'parent-id']);
|
||||
});
|
||||
expect(router.navigate).toHaveBeenCalledWith(
|
||||
['./', node.id],
|
||||
jasmine.any(Object)
|
||||
);
|
||||
});
|
||||
|
||||
describe('refresh on events', () => {
|
||||
beforeEach(() => {
|
||||
spyOn(contentApi, 'getNode').and.returnValue(of({ entry: node }));
|
||||
spyOn(component.documentList, 'reload');
|
||||
it('should navigates to home when id not provided', () => {
|
||||
component.navigate();
|
||||
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should call refresh onContentCopied event if parent is the same', () => {
|
||||
const nodes = [
|
||||
{ entry: { parentId: '1' } },
|
||||
{ entry: { parentId: '2' } }
|
||||
];
|
||||
|
||||
component.node = { id: '1' };
|
||||
|
||||
nodeActionsService.contentCopied.next(nodes);
|
||||
|
||||
expect(component.documentList.reload).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should not call refresh onContentCopied event when parent mismatch', () => {
|
||||
const nodes = [
|
||||
{ entry: { parentId: '1' } },
|
||||
{ entry: { parentId: '2' } }
|
||||
];
|
||||
|
||||
component.node = { id: '3' };
|
||||
|
||||
nodeActionsService.contentCopied.next(nodes);
|
||||
|
||||
expect(component.documentList.reload).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should call refresh onCreateFolder event', () => {
|
||||
contentManagementService.folderCreated.next();
|
||||
|
||||
expect(component.documentList.reload).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should call refresh editFolder event', () => {
|
||||
contentManagementService.folderEdited.next();
|
||||
|
||||
expect(component.documentList.reload).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should call refresh deleteNode event', () => {
|
||||
contentManagementService.nodesDeleted.next();
|
||||
|
||||
expect(component.documentList.reload).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should call refresh moveNode event', () => {
|
||||
contentManagementService.nodesMoved.next();
|
||||
|
||||
expect(component.documentList.reload).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should call refresh restoreNode event', () => {
|
||||
contentManagementService.nodesRestored.next();
|
||||
|
||||
expect(component.documentList.reload).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should call refresh on fileUploadComplete event if parent node match', fakeAsync(() => {
|
||||
const file = { file: { options: { parentId: 'parentId' } } };
|
||||
component.node = { id: 'parentId' };
|
||||
|
||||
uploadService.fileUploadComplete.next(<any>file);
|
||||
|
||||
tick(500);
|
||||
|
||||
expect(component.documentList.reload).toHaveBeenCalled();
|
||||
}));
|
||||
|
||||
it('should not call refresh on fileUploadComplete event if parent mismatch', fakeAsync(() => {
|
||||
const file = { file: { options: { parentId: 'otherId' } } };
|
||||
component.node = { id: 'parentId' };
|
||||
|
||||
uploadService.fileUploadComplete.next(<any>file);
|
||||
|
||||
tick(500);
|
||||
|
||||
expect(component.documentList.reload).not.toHaveBeenCalled();
|
||||
}));
|
||||
|
||||
it('should call refresh on fileUploadDeleted event if parent node match', fakeAsync(() => {
|
||||
const file = { file: { options: { parentId: 'parentId' } } };
|
||||
component.node = { id: 'parentId' };
|
||||
|
||||
uploadService.fileUploadDeleted.next(<any>file);
|
||||
|
||||
tick(500);
|
||||
|
||||
expect(component.documentList.reload).toHaveBeenCalled();
|
||||
}));
|
||||
|
||||
it('should not call refresh on fileUploadDeleted event if parent mismatch', fakeAsync(() => {
|
||||
const file: any = { file: { options: { parentId: 'otherId' } } };
|
||||
component.node = { id: 'parentId' };
|
||||
|
||||
uploadService.fileUploadDeleted.next(file);
|
||||
|
||||
tick(500);
|
||||
|
||||
expect(component.documentList.reload).not.toHaveBeenCalled();
|
||||
}));
|
||||
expect(router.navigate).toHaveBeenCalledWith(['./'], jasmine.any(Object));
|
||||
});
|
||||
|
||||
it('should navigate home if node is root', () => {
|
||||
component.node = {
|
||||
path: {
|
||||
elements: [{ id: 'node-id' }]
|
||||
}
|
||||
};
|
||||
|
||||
describe('onBreadcrumbNavigate()', () => {
|
||||
beforeEach(() => {
|
||||
spyOn(contentApi, 'getNode').and.returnValue(of({ entry: node }));
|
||||
fixture.detectChanges();
|
||||
});
|
||||
component.navigate(node.id);
|
||||
|
||||
it('should navigates to node id', () => {
|
||||
const routeData: any = { id: 'some-where-over-the-rainbow' };
|
||||
spyOn(component, 'navigate');
|
||||
expect(router.navigate).toHaveBeenCalledWith(['./'], jasmine.any(Object));
|
||||
});
|
||||
});
|
||||
|
||||
component.onBreadcrumbNavigate(routeData);
|
||||
describe('isSiteContainer', () => {
|
||||
it('should return false if node has no aspectNames', () => {
|
||||
const mock = { aspectNames: [] };
|
||||
|
||||
expect(component.navigate).toHaveBeenCalledWith(routeData.id);
|
||||
});
|
||||
expect(component.isSiteContainer(mock)).toBe(false);
|
||||
});
|
||||
|
||||
describe('Node navigation', () => {
|
||||
beforeEach(() => {
|
||||
spyOn(contentApi, 'getNode').and.returnValue(of({ entry: node }));
|
||||
spyOn(router, 'navigate');
|
||||
it('should return false if node is not site container', () => {
|
||||
const mock = { aspectNames: ['something-else'] };
|
||||
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should navigates to node when id provided', () => {
|
||||
component.navigate(node.id);
|
||||
|
||||
expect(router.navigate).toHaveBeenCalledWith(['./', node.id], jasmine.any(Object));
|
||||
});
|
||||
|
||||
it('should navigates to home when id not provided', () => {
|
||||
component.navigate();
|
||||
|
||||
expect(router.navigate).toHaveBeenCalledWith(['./'], jasmine.any(Object));
|
||||
});
|
||||
|
||||
it('should navigate home if node is root', () => {
|
||||
component.node = {
|
||||
path: {
|
||||
elements: [ {id: 'node-id'} ]
|
||||
}
|
||||
};
|
||||
|
||||
component.navigate(node.id);
|
||||
|
||||
expect(router.navigate).toHaveBeenCalledWith(['./'], jasmine.any(Object));
|
||||
});
|
||||
expect(component.isSiteContainer(mock)).toBe(false);
|
||||
});
|
||||
|
||||
describe('isSiteContainer', () => {
|
||||
it('should return false if node has no aspectNames', () => {
|
||||
const mock = { aspectNames: [] };
|
||||
it('should return true if node is a site container', () => {
|
||||
const mock = { aspectNames: ['st:siteContainer'] };
|
||||
|
||||
expect(component.isSiteContainer(mock)).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false if node is not site container', () => {
|
||||
const mock = { aspectNames: ['something-else'] };
|
||||
|
||||
expect(component.isSiteContainer(mock)).toBe(false);
|
||||
});
|
||||
|
||||
it('should return true if node is a site container', () => {
|
||||
const mock = { aspectNames: [ 'st:siteContainer' ] };
|
||||
|
||||
expect(component.isSiteContainer(mock)).toBe(true);
|
||||
});
|
||||
expect(component.isSiteContainer(mock)).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -27,7 +27,12 @@ import { FileUploadEvent, UploadService } from '@alfresco/adf-core';
|
||||
import { Component, OnDestroy, OnInit } from '@angular/core';
|
||||
import { ActivatedRoute, Params, Router } from '@angular/router';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { MinimalNodeEntity, MinimalNodeEntryEntity, PathElement, PathElementEntity } from 'alfresco-js-api';
|
||||
import {
|
||||
MinimalNodeEntity,
|
||||
MinimalNodeEntryEntity,
|
||||
PathElement,
|
||||
PathElementEntity
|
||||
} from 'alfresco-js-api';
|
||||
import { ContentManagementService } from '../../services/content-management.service';
|
||||
import { NodeActionsService } from '../../services/node-actions.service';
|
||||
import { AppStore } from '../../store/states/app.state';
|
||||
@ -39,238 +44,256 @@ import { BreakpointObserver, Breakpoints } from '@angular/cdk/layout';
|
||||
import { debounceTime } from 'rxjs/operators';
|
||||
|
||||
@Component({
|
||||
templateUrl: './files.component.html'
|
||||
templateUrl: './files.component.html'
|
||||
})
|
||||
export class FilesComponent extends PageComponent implements OnInit, OnDestroy {
|
||||
isValidPath = true;
|
||||
isSmallScreen = false;
|
||||
|
||||
isValidPath = true;
|
||||
isSmallScreen = false;
|
||||
private nodePath: PathElement[];
|
||||
|
||||
private nodePath: PathElement[];
|
||||
constructor(
|
||||
private router: Router,
|
||||
private route: ActivatedRoute,
|
||||
private contentApi: ContentApiService,
|
||||
store: Store<AppStore>,
|
||||
private nodeActionsService: NodeActionsService,
|
||||
private uploadService: UploadService,
|
||||
content: ContentManagementService,
|
||||
extensions: AppExtensionService,
|
||||
private breakpointObserver: BreakpointObserver
|
||||
) {
|
||||
super(store, extensions, content);
|
||||
}
|
||||
|
||||
constructor(private router: Router,
|
||||
private route: ActivatedRoute,
|
||||
private contentApi: ContentApiService,
|
||||
store: Store<AppStore>,
|
||||
private nodeActionsService: NodeActionsService,
|
||||
private uploadService: UploadService,
|
||||
content: ContentManagementService,
|
||||
extensions: AppExtensionService,
|
||||
private breakpointObserver: BreakpointObserver) {
|
||||
super(store, extensions, content);
|
||||
}
|
||||
ngOnInit() {
|
||||
super.ngOnInit();
|
||||
|
||||
ngOnInit() {
|
||||
super.ngOnInit();
|
||||
const { route, content, nodeActionsService, uploadService } = this;
|
||||
const { data } = route.snapshot;
|
||||
|
||||
const { route, content, nodeActionsService, uploadService } = this;
|
||||
const { data } = route.snapshot;
|
||||
this.title = data.title;
|
||||
|
||||
this.title = data.title;
|
||||
route.params.subscribe(({ folderId }: Params) => {
|
||||
const nodeId = folderId || data.defaultNodeId;
|
||||
|
||||
route.params.subscribe(({ folderId }: Params) => {
|
||||
const nodeId = folderId || data.defaultNodeId;
|
||||
this.contentApi.getNode(nodeId).subscribe(
|
||||
node => {
|
||||
this.isValidPath = true;
|
||||
|
||||
this.contentApi
|
||||
.getNode(nodeId)
|
||||
.subscribe(
|
||||
node => {
|
||||
this.isValidPath = true;
|
||||
|
||||
if (node.entry && node.entry.isFolder) {
|
||||
this.updateCurrentNode(node.entry);
|
||||
} else {
|
||||
this.router.navigate(
|
||||
['/personal-files', node.entry.parentId],
|
||||
{ replaceUrl: true }
|
||||
);
|
||||
}
|
||||
},
|
||||
() => this.isValidPath = false
|
||||
);
|
||||
});
|
||||
|
||||
this.subscriptions = this.subscriptions.concat([
|
||||
nodeActionsService.contentCopied.subscribe((nodes) => this.onContentCopied(nodes)),
|
||||
content.folderCreated.subscribe(() => this.documentList.reload()),
|
||||
content.folderEdited.subscribe(() => this.documentList.reload()),
|
||||
content.nodesDeleted.subscribe(() => this.documentList.reload()),
|
||||
content.nodesMoved.subscribe(() => this.documentList.reload()),
|
||||
content.nodesRestored.subscribe(() => this.documentList.reload()),
|
||||
uploadService.fileUploadComplete.pipe(debounceTime(300)).subscribe(file => this.onFileUploadedEvent(file)),
|
||||
uploadService.fileUploadDeleted.pipe(debounceTime(300)).subscribe((file) => this.onFileUploadedEvent(file)),
|
||||
|
||||
this.breakpointObserver
|
||||
.observe([
|
||||
Breakpoints.HandsetPortrait,
|
||||
Breakpoints.HandsetLandscape
|
||||
])
|
||||
.subscribe(result => {
|
||||
this.isSmallScreen = result.matches;
|
||||
})
|
||||
]);
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
super.ngOnDestroy();
|
||||
this.store.dispatch(new SetCurrentFolderAction(null));
|
||||
}
|
||||
|
||||
navigate(nodeId: string = null) {
|
||||
const commands = [ './' ];
|
||||
|
||||
if (nodeId && !this.isRootNode(nodeId)) {
|
||||
commands.push(nodeId);
|
||||
}
|
||||
|
||||
this.router.navigate(commands, {
|
||||
relativeTo: this.route.parent
|
||||
});
|
||||
}
|
||||
|
||||
navigateTo(node: MinimalNodeEntity) {
|
||||
if (node && node.entry) {
|
||||
const { id, isFolder } = node.entry;
|
||||
|
||||
if (isFolder) {
|
||||
this.navigate(id);
|
||||
return;
|
||||
}
|
||||
|
||||
if (PageComponent.isLockedNode(node.entry)) {
|
||||
event.preventDefault();
|
||||
return;
|
||||
}
|
||||
|
||||
this.showPreview(node);
|
||||
}
|
||||
}
|
||||
|
||||
onBreadcrumbNavigate(route: PathElementEntity) {
|
||||
// todo: review this approach once 5.2.3 is out
|
||||
if (this.nodePath && this.nodePath.length > 2) {
|
||||
if (this.nodePath[1].name === 'Sites' && this.nodePath[2].id === route.id) {
|
||||
return this.navigate(this.nodePath[3].id);
|
||||
}
|
||||
}
|
||||
this.navigate(route.id);
|
||||
}
|
||||
|
||||
onFileUploadedEvent(event: FileUploadEvent) {
|
||||
const node: MinimalNodeEntity = event.file.data;
|
||||
|
||||
// check root and child nodes
|
||||
if (node && node.entry && node.entry.parentId === this.getParentNodeId()) {
|
||||
this.documentList.reload();
|
||||
return;
|
||||
}
|
||||
|
||||
// check the child nodes to show dropped folder
|
||||
if (event && event.file.options.parentId === this.getParentNodeId()) {
|
||||
this.displayFolderParent(event.file.options.path, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
if (event && event.file.options.parentId) {
|
||||
if (this.nodePath) {
|
||||
const correspondingNodePath = this.nodePath.find(pathItem => pathItem.id === event.file.options.parentId);
|
||||
|
||||
// check if the current folder has the 'trigger-upload-folder' as one of its parents
|
||||
if (correspondingNodePath) {
|
||||
const correspondingIndex = this.nodePath.length - this.nodePath.indexOf(correspondingNodePath);
|
||||
this.displayFolderParent(event.file.options.path, correspondingIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
displayFolderParent(filePath = '', index) {
|
||||
const parentName = filePath.split('/')[index];
|
||||
const currentFoldersDisplayed: any = this.documentList.data.getRows() || [];
|
||||
|
||||
const alreadyDisplayedParentFolder = currentFoldersDisplayed.find(
|
||||
row => row.node.entry.isFolder && row.node.entry.name === parentName);
|
||||
|
||||
if (alreadyDisplayedParentFolder) {
|
||||
return;
|
||||
}
|
||||
this.documentList.reload();
|
||||
}
|
||||
|
||||
onContentCopied(nodes: MinimalNodeEntity[]) {
|
||||
const newNode = nodes
|
||||
.find((node) => {
|
||||
return node && node.entry && node.entry.parentId === this.getParentNodeId();
|
||||
if (node.entry && node.entry.isFolder) {
|
||||
this.updateCurrentNode(node.entry);
|
||||
} else {
|
||||
this.router.navigate(['/personal-files', node.entry.parentId], {
|
||||
replaceUrl: true
|
||||
});
|
||||
if (newNode) {
|
||||
this.documentList.reload();
|
||||
}
|
||||
}
|
||||
},
|
||||
() => (this.isValidPath = false)
|
||||
);
|
||||
});
|
||||
|
||||
this.subscriptions = this.subscriptions.concat([
|
||||
nodeActionsService.contentCopied.subscribe(nodes =>
|
||||
this.onContentCopied(nodes)
|
||||
),
|
||||
content.folderCreated.subscribe(() => this.documentList.reload()),
|
||||
content.folderEdited.subscribe(() => this.documentList.reload()),
|
||||
content.nodesDeleted.subscribe(() => this.documentList.reload()),
|
||||
content.nodesMoved.subscribe(() => this.documentList.reload()),
|
||||
content.nodesRestored.subscribe(() => this.documentList.reload()),
|
||||
uploadService.fileUploadComplete
|
||||
.pipe(debounceTime(300))
|
||||
.subscribe(file => this.onFileUploadedEvent(file)),
|
||||
uploadService.fileUploadDeleted
|
||||
.pipe(debounceTime(300))
|
||||
.subscribe(file => this.onFileUploadedEvent(file)),
|
||||
|
||||
this.breakpointObserver
|
||||
.observe([Breakpoints.HandsetPortrait, Breakpoints.HandsetLandscape])
|
||||
.subscribe(result => {
|
||||
this.isSmallScreen = result.matches;
|
||||
})
|
||||
]);
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
super.ngOnDestroy();
|
||||
this.store.dispatch(new SetCurrentFolderAction(null));
|
||||
}
|
||||
|
||||
navigate(nodeId: string = null) {
|
||||
const commands = ['./'];
|
||||
|
||||
if (nodeId && !this.isRootNode(nodeId)) {
|
||||
commands.push(nodeId);
|
||||
}
|
||||
|
||||
this.router.navigate(commands, {
|
||||
relativeTo: this.route.parent
|
||||
});
|
||||
}
|
||||
|
||||
navigateTo(node: MinimalNodeEntity) {
|
||||
if (node && node.entry) {
|
||||
const { id, isFolder } = node.entry;
|
||||
|
||||
if (isFolder) {
|
||||
this.navigate(id);
|
||||
return;
|
||||
}
|
||||
|
||||
if (PageComponent.isLockedNode(node.entry)) {
|
||||
event.preventDefault();
|
||||
return;
|
||||
}
|
||||
|
||||
this.showPreview(node);
|
||||
}
|
||||
}
|
||||
|
||||
onBreadcrumbNavigate(route: PathElementEntity) {
|
||||
// todo: review this approach once 5.2.3 is out
|
||||
private async updateCurrentNode(node: MinimalNodeEntryEntity) {
|
||||
this.nodePath = null;
|
||||
if (this.nodePath && this.nodePath.length > 2) {
|
||||
if (
|
||||
this.nodePath[1].name === 'Sites' &&
|
||||
this.nodePath[2].id === route.id
|
||||
) {
|
||||
return this.navigate(this.nodePath[3].id);
|
||||
}
|
||||
}
|
||||
this.navigate(route.id);
|
||||
}
|
||||
|
||||
if (node && node.path && node.path.elements) {
|
||||
const elements = node.path.elements;
|
||||
onFileUploadedEvent(event: FileUploadEvent) {
|
||||
const node: MinimalNodeEntity = event.file.data;
|
||||
|
||||
this.nodePath = elements.map(pathElement => {
|
||||
return Object.assign({}, pathElement);
|
||||
});
|
||||
|
||||
if (elements.length > 1) {
|
||||
if (elements[1].name === 'User Homes') {
|
||||
elements.splice(0, 2);
|
||||
} else if (elements[1].name === 'Sites') {
|
||||
await this.normalizeSitePath(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.node = node;
|
||||
this.store.dispatch(new SetCurrentFolderAction(node));
|
||||
// check root and child nodes
|
||||
if (node && node.entry && node.entry.parentId === this.getParentNodeId()) {
|
||||
this.documentList.reload();
|
||||
return;
|
||||
}
|
||||
|
||||
// todo: review this approach once 5.2.3 is out
|
||||
private async normalizeSitePath(node: MinimalNodeEntryEntity) {
|
||||
const elements = node.path.elements;
|
||||
|
||||
// remove 'Sites'
|
||||
elements.splice(1, 1);
|
||||
|
||||
if (this.isSiteContainer(node)) {
|
||||
// rename 'documentLibrary' entry to the target site display name
|
||||
// clicking on the breadcrumb entry loads the site content
|
||||
const parentNode = await this.contentApi.getNodeInfo(node.parentId).toPromise();
|
||||
node.name = parentNode.properties['cm:title'] || parentNode.name;
|
||||
|
||||
// remove the site entry
|
||||
elements.splice(1, 1);
|
||||
} else {
|
||||
// remove 'documentLibrary' in the middle of the path
|
||||
const docLib = elements.findIndex(el => el.name === 'documentLibrary');
|
||||
if (docLib > -1) {
|
||||
const siteFragment = elements[docLib - 1];
|
||||
const siteNode = await this.contentApi.getNodeInfo(siteFragment.id).toPromise();
|
||||
|
||||
// apply Site Name to the parent fragment
|
||||
siteFragment.name = siteNode.properties['cm:title'] || siteNode.name;
|
||||
elements.splice(docLib, 1);
|
||||
}
|
||||
}
|
||||
// check the child nodes to show dropped folder
|
||||
if (event && event.file.options.parentId === this.getParentNodeId()) {
|
||||
this.displayFolderParent(event.file.options.path, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
isSiteContainer(node: MinimalNodeEntryEntity): boolean {
|
||||
if (node && node.aspectNames && node.aspectNames.length > 0) {
|
||||
return node.aspectNames.indexOf('st:siteContainer') >= 0;
|
||||
if (event && event.file.options.parentId) {
|
||||
if (this.nodePath) {
|
||||
const correspondingNodePath = this.nodePath.find(
|
||||
pathItem => pathItem.id === event.file.options.parentId
|
||||
);
|
||||
|
||||
// check if the current folder has the 'trigger-upload-folder' as one of its parents
|
||||
if (correspondingNodePath) {
|
||||
const correspondingIndex =
|
||||
this.nodePath.length - this.nodePath.indexOf(correspondingNodePath);
|
||||
this.displayFolderParent(event.file.options.path, correspondingIndex);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
displayFolderParent(filePath = '', index) {
|
||||
const parentName = filePath.split('/')[index];
|
||||
const currentFoldersDisplayed: any = this.documentList.data.getRows() || [];
|
||||
|
||||
const alreadyDisplayedParentFolder = currentFoldersDisplayed.find(
|
||||
row => row.node.entry.isFolder && row.node.entry.name === parentName
|
||||
);
|
||||
|
||||
if (alreadyDisplayedParentFolder) {
|
||||
return;
|
||||
}
|
||||
this.documentList.reload();
|
||||
}
|
||||
|
||||
onContentCopied(nodes: MinimalNodeEntity[]) {
|
||||
const newNode = nodes.find(node => {
|
||||
return (
|
||||
node && node.entry && node.entry.parentId === this.getParentNodeId()
|
||||
);
|
||||
});
|
||||
if (newNode) {
|
||||
this.documentList.reload();
|
||||
}
|
||||
}
|
||||
|
||||
// todo: review this approach once 5.2.3 is out
|
||||
private async updateCurrentNode(node: MinimalNodeEntryEntity) {
|
||||
this.nodePath = null;
|
||||
|
||||
if (node && node.path && node.path.elements) {
|
||||
const elements = node.path.elements;
|
||||
|
||||
this.nodePath = elements.map(pathElement => {
|
||||
return Object.assign({}, pathElement);
|
||||
});
|
||||
|
||||
if (elements.length > 1) {
|
||||
if (elements[1].name === 'User Homes') {
|
||||
elements.splice(0, 2);
|
||||
} else if (elements[1].name === 'Sites') {
|
||||
await this.normalizeSitePath(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
isRootNode(nodeId: string): boolean {
|
||||
if (this.node && this.node.path && this.node.path.elements && this.node.path.elements.length > 0) {
|
||||
return this.node.path.elements[0].id === nodeId;
|
||||
}
|
||||
return false;
|
||||
this.node = node;
|
||||
this.store.dispatch(new SetCurrentFolderAction(node));
|
||||
}
|
||||
|
||||
// todo: review this approach once 5.2.3 is out
|
||||
private async normalizeSitePath(node: MinimalNodeEntryEntity) {
|
||||
const elements = node.path.elements;
|
||||
|
||||
// remove 'Sites'
|
||||
elements.splice(1, 1);
|
||||
|
||||
if (this.isSiteContainer(node)) {
|
||||
// rename 'documentLibrary' entry to the target site display name
|
||||
// clicking on the breadcrumb entry loads the site content
|
||||
const parentNode = await this.contentApi
|
||||
.getNodeInfo(node.parentId)
|
||||
.toPromise();
|
||||
node.name = parentNode.properties['cm:title'] || parentNode.name;
|
||||
|
||||
// remove the site entry
|
||||
elements.splice(1, 1);
|
||||
} else {
|
||||
// remove 'documentLibrary' in the middle of the path
|
||||
const docLib = elements.findIndex(el => el.name === 'documentLibrary');
|
||||
if (docLib > -1) {
|
||||
const siteFragment = elements[docLib - 1];
|
||||
const siteNode = await this.contentApi
|
||||
.getNodeInfo(siteFragment.id)
|
||||
.toPromise();
|
||||
|
||||
// apply Site Name to the parent fragment
|
||||
siteFragment.name = siteNode.properties['cm:title'] || siteNode.name;
|
||||
elements.splice(docLib, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
isSiteContainer(node: MinimalNodeEntryEntity): boolean {
|
||||
if (node && node.aspectNames && node.aspectNames.length > 0) {
|
||||
return node.aspectNames.indexOf('st:siteContainer') >= 0;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
isRootNode(nodeId: string): boolean {
|
||||
if (
|
||||
this.node &&
|
||||
this.node.path &&
|
||||
this.node.path.elements &&
|
||||
this.node.path.elements.length > 0
|
||||
) {
|
||||
return this.node.path.elements[0].id === nodeId;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -1,27 +1,27 @@
|
||||
@mixin aca-generic-error-theme($theme) {
|
||||
$warn: map-get($theme, warn);
|
||||
$foreground: map-get($theme, foreground);
|
||||
$warn: map-get($theme, warn);
|
||||
$foreground: map-get($theme, foreground);
|
||||
|
||||
.aca-generic-error {
|
||||
color: mat-color($foreground, text, 0.54);
|
||||
.aca-generic-error {
|
||||
color: mat-color($foreground, text, 0.54);
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
&__title {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
mat-icon {
|
||||
color: mat-color($warn);
|
||||
direction: rtl;
|
||||
font-size: 52px;
|
||||
height: 52px;
|
||||
width: 52px;
|
||||
}
|
||||
&__title {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
mat-icon {
|
||||
color: mat-color($warn);
|
||||
direction: rtl;
|
||||
font-size: 52px;
|
||||
height: 52px;
|
||||
width: 52px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -23,14 +23,17 @@
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Component, ViewEncapsulation, ChangeDetectionStrategy } from '@angular/core';
|
||||
import {
|
||||
Component,
|
||||
ViewEncapsulation,
|
||||
ChangeDetectionStrategy
|
||||
} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'aca-generic-error',
|
||||
templateUrl: './generic-error.component.html',
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
host: { class: 'aca-generic-error' }
|
||||
selector: 'aca-generic-error',
|
||||
templateUrl: './generic-error.component.html',
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
host: { class: 'aca-generic-error' }
|
||||
})
|
||||
export class GenericErrorComponent {}
|
||||
|
||||
|
@ -28,18 +28,18 @@ import { MinimalNodeEntryEntity } from 'alfresco-js-api';
|
||||
import { NodePermissionService } from '../../../services/node-permission.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-comments-tab',
|
||||
template: `
|
||||
selector: 'app-comments-tab',
|
||||
template: `
|
||||
<adf-comments [readOnly]="!canUpdateNode" [nodeId]="node?.id"></adf-comments>
|
||||
`
|
||||
})
|
||||
export class CommentsTabComponent {
|
||||
@Input()
|
||||
node: MinimalNodeEntryEntity;
|
||||
@Input()
|
||||
node: MinimalNodeEntryEntity;
|
||||
|
||||
constructor(private permission: NodePermissionService) {}
|
||||
constructor(private permission: NodePermissionService) {}
|
||||
|
||||
get canUpdateNode() {
|
||||
return this.node && this.permission.check(this.node, ['update']);
|
||||
}
|
||||
get canUpdateNode() {
|
||||
return this.node && this.permission.check(this.node, ['update']);
|
||||
}
|
||||
}
|
||||
|
@ -30,71 +30,73 @@ import { AppExtensionService } from '../../extensions/extension.service';
|
||||
import { SidebarTabRef } from '@alfresco/adf-extensions';
|
||||
|
||||
@Component({
|
||||
selector: 'aca-info-drawer',
|
||||
templateUrl: './info-drawer.component.html'
|
||||
selector: 'aca-info-drawer',
|
||||
templateUrl: './info-drawer.component.html'
|
||||
})
|
||||
export class InfoDrawerComponent implements OnChanges, OnInit {
|
||||
@Input() nodeId: string;
|
||||
@Input() node: MinimalNodeEntity;
|
||||
@Input()
|
||||
nodeId: string;
|
||||
@Input()
|
||||
node: MinimalNodeEntity;
|
||||
|
||||
isLoading = false;
|
||||
displayNode: MinimalNodeEntryEntity;
|
||||
tabs: Array<SidebarTabRef> = [];
|
||||
isLoading = false;
|
||||
displayNode: MinimalNodeEntryEntity;
|
||||
tabs: Array<SidebarTabRef> = [];
|
||||
|
||||
constructor(
|
||||
private contentApi: ContentApiService,
|
||||
private extensions: AppExtensionService
|
||||
) {}
|
||||
constructor(
|
||||
private contentApi: ContentApiService,
|
||||
private extensions: AppExtensionService
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.tabs = this.extensions.getSidebarTabs();
|
||||
}
|
||||
ngOnInit() {
|
||||
this.tabs = this.extensions.getSidebarTabs();
|
||||
}
|
||||
|
||||
ngOnChanges() {
|
||||
if (this.node) {
|
||||
const entry = this.node.entry;
|
||||
if (entry.nodeId) {
|
||||
this.loadNodeInfo(entry.nodeId);
|
||||
} else if ((<any>entry).guid) {
|
||||
// workaround for Favorite files
|
||||
this.loadNodeInfo(entry.id);
|
||||
} else {
|
||||
// workaround Recent
|
||||
if (this.isTypeImage(entry) && !this.hasAspectNames(entry)) {
|
||||
this.loadNodeInfo(this.node.entry.id);
|
||||
} else {
|
||||
this.setDisplayNode(this.node.entry);
|
||||
}
|
||||
}
|
||||
ngOnChanges() {
|
||||
if (this.node) {
|
||||
const entry = this.node.entry;
|
||||
if (entry.nodeId) {
|
||||
this.loadNodeInfo(entry.nodeId);
|
||||
} else if ((<any>entry).guid) {
|
||||
// workaround for Favorite files
|
||||
this.loadNodeInfo(entry.id);
|
||||
} else {
|
||||
// workaround Recent
|
||||
if (this.isTypeImage(entry) && !this.hasAspectNames(entry)) {
|
||||
this.loadNodeInfo(this.node.entry.id);
|
||||
} else {
|
||||
this.setDisplayNode(this.node.entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private hasAspectNames(entry: MinimalNodeEntryEntity): boolean {
|
||||
return entry.aspectNames && entry.aspectNames.includes('exif:exif');
|
||||
private hasAspectNames(entry: MinimalNodeEntryEntity): boolean {
|
||||
return entry.aspectNames && entry.aspectNames.includes('exif:exif');
|
||||
}
|
||||
|
||||
private isTypeImage(entry: MinimalNodeEntryEntity): boolean {
|
||||
if (entry && entry.content && entry.content.mimeType) {
|
||||
return entry.content.mimeType.includes('image/');
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private isTypeImage(entry: MinimalNodeEntryEntity): boolean {
|
||||
if (entry && entry.content && entry.content.mimeType) {
|
||||
return entry.content.mimeType.includes('image/');
|
||||
}
|
||||
return false;
|
||||
private loadNodeInfo(nodeId: string) {
|
||||
if (nodeId) {
|
||||
this.isLoading = true;
|
||||
|
||||
this.contentApi.getNodeInfo(nodeId).subscribe(
|
||||
entity => {
|
||||
this.setDisplayNode(entity);
|
||||
this.isLoading = false;
|
||||
},
|
||||
() => (this.isLoading = false)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private loadNodeInfo(nodeId: string) {
|
||||
if (nodeId) {
|
||||
this.isLoading = true;
|
||||
|
||||
this.contentApi.getNodeInfo(nodeId).subscribe(
|
||||
entity => {
|
||||
this.setDisplayNode(entity);
|
||||
this.isLoading = false;
|
||||
},
|
||||
() => this.isLoading = false
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private setDisplayNode(node: MinimalNodeEntryEntity) {
|
||||
this.displayNode = node;
|
||||
}
|
||||
private setDisplayNode(node: MinimalNodeEntryEntity) {
|
||||
this.displayNode = node;
|
||||
}
|
||||
}
|
||||
|
@ -24,8 +24,8 @@
|
||||
*/
|
||||
|
||||
import {
|
||||
ContentMetadataModule,
|
||||
VersionManagerModule
|
||||
ContentMetadataModule,
|
||||
VersionManagerModule
|
||||
} from '@alfresco/adf-content-services';
|
||||
import { CoreModule } from '@alfresco/adf-core';
|
||||
import { ExtensionsModule } from '@alfresco/adf-extensions';
|
||||
@ -39,26 +39,26 @@ import { MetadataTabComponent } from './metadata-tab/metadata-tab.component';
|
||||
import { VersionsTabComponent } from './versions-tab/versions-tab.component';
|
||||
|
||||
export function components() {
|
||||
return [
|
||||
InfoDrawerComponent,
|
||||
MetadataTabComponent,
|
||||
CommentsTabComponent,
|
||||
VersionsTabComponent
|
||||
];
|
||||
return [
|
||||
InfoDrawerComponent,
|
||||
MetadataTabComponent,
|
||||
CommentsTabComponent,
|
||||
VersionsTabComponent
|
||||
];
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
CommonModule,
|
||||
MaterialModule,
|
||||
CoreModule.forChild(),
|
||||
ExtensionsModule.forChild(),
|
||||
ContentMetadataModule,
|
||||
VersionManagerModule,
|
||||
DirectivesModule
|
||||
],
|
||||
declarations: [...components()],
|
||||
exports: [...components()],
|
||||
entryComponents: [...components()]
|
||||
imports: [
|
||||
CommonModule,
|
||||
MaterialModule,
|
||||
CoreModule.forChild(),
|
||||
ExtensionsModule.forChild(),
|
||||
ContentMetadataModule,
|
||||
VersionManagerModule,
|
||||
DirectivesModule
|
||||
],
|
||||
declarations: [...components()],
|
||||
exports: [...components()],
|
||||
entryComponents: [...components()]
|
||||
})
|
||||
export class AppInfoDrawerModule {}
|
||||
|
@ -28,25 +28,25 @@ import { MinimalNodeEntryEntity } from 'alfresco-js-api';
|
||||
import { NodePermissionService } from '../../../services/node-permission.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-metadata-tab',
|
||||
template: `
|
||||
<adf-content-metadata-card
|
||||
[readOnly]="!canUpdateNode"
|
||||
[displayEmpty]="canUpdateNode"
|
||||
[preset]="'custom'"
|
||||
[node]="node">
|
||||
</adf-content-metadata-card>
|
||||
selector: 'app-metadata-tab',
|
||||
template: `
|
||||
<adf-content-metadata-card
|
||||
[readOnly]="!canUpdateNode"
|
||||
[displayEmpty]="canUpdateNode"
|
||||
[preset]="'custom'"
|
||||
[node]="node">
|
||||
</adf-content-metadata-card>
|
||||
`,
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
host: { 'class': 'app-metadata-tab' }
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
host: { class: 'app-metadata-tab' }
|
||||
})
|
||||
export class MetadataTabComponent {
|
||||
@Input()
|
||||
node: MinimalNodeEntryEntity;
|
||||
@Input()
|
||||
node: MinimalNodeEntryEntity;
|
||||
|
||||
constructor(private permission: NodePermissionService) {}
|
||||
constructor(private permission: NodePermissionService) {}
|
||||
|
||||
get canUpdateNode() {
|
||||
return this.node && this.permission.check(this.node, ['update']);
|
||||
}
|
||||
get canUpdateNode() {
|
||||
return this.node && this.permission.check(this.node, ['update']);
|
||||
}
|
||||
}
|
||||
|
@ -27,8 +27,8 @@ import { Component, Input, OnChanges, OnInit } from '@angular/core';
|
||||
import { MinimalNodeEntryEntity } from 'alfresco-js-api';
|
||||
|
||||
@Component({
|
||||
selector: 'app-versions-tab',
|
||||
template: `
|
||||
selector: 'app-versions-tab',
|
||||
template: `
|
||||
<ng-container *ngIf="isFileSelected;else empty">
|
||||
<adf-version-manager
|
||||
[showComments]="'adf-version-manager.allowComments' | adfAppConfig:true"
|
||||
@ -46,25 +46,25 @@ import { MinimalNodeEntryEntity } from 'alfresco-js-api';
|
||||
`
|
||||
})
|
||||
export class VersionsTabComponent implements OnInit, OnChanges {
|
||||
@Input()
|
||||
node: MinimalNodeEntryEntity;
|
||||
@Input()
|
||||
node: MinimalNodeEntryEntity;
|
||||
|
||||
isFileSelected = false;
|
||||
isFileSelected = false;
|
||||
|
||||
ngOnInit() {
|
||||
this.updateState();
|
||||
}
|
||||
|
||||
ngOnChanges() {
|
||||
this.updateState();
|
||||
}
|
||||
|
||||
private updateState() {
|
||||
if (this.node && this.node.nodeId) {
|
||||
// workaround for shared files type.
|
||||
this.isFileSelected = true;
|
||||
} else {
|
||||
this.isFileSelected = this.node.isFile;
|
||||
}
|
||||
ngOnInit() {
|
||||
this.updateState();
|
||||
}
|
||||
|
||||
ngOnChanges() {
|
||||
this.updateState();
|
||||
}
|
||||
|
||||
private updateState() {
|
||||
if (this.node && this.node.nodeId) {
|
||||
// workaround for shared files type.
|
||||
this.isFileSelected = true;
|
||||
} else {
|
||||
this.isFileSelected = this.node.isFile;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,16 +1,16 @@
|
||||
:host {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 599px) {
|
||||
.adf-app-title {
|
||||
display: none;
|
||||
}
|
||||
.adf-app-title {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 719px) {
|
||||
.adf-app-logo {
|
||||
display: none;
|
||||
}
|
||||
.adf-app-logo {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
@ -31,85 +31,82 @@ import { SidenavViewsManagerDirective } from './sidenav-views-manager.directive'
|
||||
import { AppTestingModule } from '../../testing/app-testing.module';
|
||||
|
||||
describe('LayoutComponent', () => {
|
||||
let fixture: ComponentFixture<LayoutComponent>;
|
||||
let component: LayoutComponent;
|
||||
let appConfig: AppConfigService;
|
||||
let userPreference: UserPreferencesService;
|
||||
let fixture: ComponentFixture<LayoutComponent>;
|
||||
let component: LayoutComponent;
|
||||
let appConfig: AppConfigService;
|
||||
let userPreference: UserPreferencesService;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [ AppTestingModule ],
|
||||
declarations: [
|
||||
LayoutComponent,
|
||||
SidenavViewsManagerDirective
|
||||
],
|
||||
schemas: [ NO_ERRORS_SCHEMA ]
|
||||
});
|
||||
|
||||
fixture = TestBed.createComponent(LayoutComponent);
|
||||
component = fixture.componentInstance;
|
||||
appConfig = TestBed.get(AppConfigService);
|
||||
userPreference = TestBed.get(UserPreferencesService);
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [AppTestingModule],
|
||||
declarations: [LayoutComponent, SidenavViewsManagerDirective],
|
||||
schemas: [NO_ERRORS_SCHEMA]
|
||||
});
|
||||
|
||||
describe('sidenav state', () => {
|
||||
it('should get state from configuration', () => {
|
||||
appConfig.config = {
|
||||
sideNav: {
|
||||
expandedSidenav: false,
|
||||
preserveState: false
|
||||
}
|
||||
};
|
||||
fixture = TestBed.createComponent(LayoutComponent);
|
||||
component = fixture.componentInstance;
|
||||
appConfig = TestBed.get(AppConfigService);
|
||||
userPreference = TestBed.get(UserPreferencesService);
|
||||
});
|
||||
|
||||
fixture.detectChanges();
|
||||
describe('sidenav state', () => {
|
||||
it('should get state from configuration', () => {
|
||||
appConfig.config = {
|
||||
sideNav: {
|
||||
expandedSidenav: false,
|
||||
preserveState: false
|
||||
}
|
||||
};
|
||||
|
||||
expect(component.expandedSidenav).toBe(false);
|
||||
});
|
||||
fixture.detectChanges();
|
||||
|
||||
it('should resolve state to true is no configuration', () => {
|
||||
appConfig.config = {};
|
||||
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(component.expandedSidenav).toBe(true);
|
||||
});
|
||||
|
||||
it('should get state from user settings as true', () => {
|
||||
appConfig.config = {
|
||||
sideNav: {
|
||||
expandedSidenav: false,
|
||||
preserveState: true
|
||||
}
|
||||
};
|
||||
|
||||
spyOn(userPreference, 'get').and.callFake(key => {
|
||||
if (key === 'expandedSidenav') {
|
||||
return 'true';
|
||||
}
|
||||
});
|
||||
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(component.expandedSidenav).toBe(true);
|
||||
});
|
||||
|
||||
it('should get state from user settings as false', () => {
|
||||
appConfig.config = {
|
||||
sideNav: {
|
||||
expandedSidenav: false,
|
||||
preserveState: true
|
||||
}
|
||||
};
|
||||
|
||||
spyOn(userPreference, 'get').and.callFake(key => {
|
||||
if (key === 'expandedSidenav') {
|
||||
return 'false';
|
||||
}
|
||||
});
|
||||
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(component.expandedSidenav).toBe(false);
|
||||
});
|
||||
expect(component.expandedSidenav).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
it('should resolve state to true is no configuration', () => {
|
||||
appConfig.config = {};
|
||||
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(component.expandedSidenav).toBe(true);
|
||||
});
|
||||
|
||||
it('should get state from user settings as true', () => {
|
||||
appConfig.config = {
|
||||
sideNav: {
|
||||
expandedSidenav: false,
|
||||
preserveState: true
|
||||
}
|
||||
};
|
||||
|
||||
spyOn(userPreference, 'get').and.callFake(key => {
|
||||
if (key === 'expandedSidenav') {
|
||||
return 'true';
|
||||
}
|
||||
});
|
||||
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(component.expandedSidenav).toBe(true);
|
||||
});
|
||||
|
||||
it('should get state from user settings as false', () => {
|
||||
appConfig.config = {
|
||||
sideNav: {
|
||||
expandedSidenav: false,
|
||||
preserveState: true
|
||||
}
|
||||
};
|
||||
|
||||
spyOn(userPreference, 'get').and.callFake(key => {
|
||||
if (key === 'expandedSidenav') {
|
||||
return 'false';
|
||||
}
|
||||
});
|
||||
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(component.expandedSidenav).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -24,11 +24,11 @@
|
||||
*/
|
||||
|
||||
import {
|
||||
Component,
|
||||
OnInit,
|
||||
OnDestroy,
|
||||
ViewChild,
|
||||
ViewEncapsulation
|
||||
Component,
|
||||
OnInit,
|
||||
OnDestroy,
|
||||
ViewChild,
|
||||
ViewEncapsulation
|
||||
} from '@angular/core';
|
||||
import { Observable, Subject } from 'rxjs';
|
||||
import { MinimalNodeEntryEntity } from 'alfresco-js-api';
|
||||
@ -37,76 +37,72 @@ import { SidenavViewsManagerDirective } from './sidenav-views-manager.directive'
|
||||
import { Store } from '@ngrx/store';
|
||||
import { AppStore } from '../../store/states';
|
||||
import {
|
||||
currentFolder,
|
||||
selectAppName,
|
||||
selectHeaderColor,
|
||||
selectLogoPath
|
||||
currentFolder,
|
||||
selectAppName,
|
||||
selectHeaderColor,
|
||||
selectLogoPath
|
||||
} from '../../store/selectors/app.selectors';
|
||||
import { takeUntil } from 'rxjs/operators';
|
||||
import { BreakpointObserver, Breakpoints } from '@angular/cdk/layout';
|
||||
|
||||
@Component({
|
||||
selector: 'app-layout',
|
||||
templateUrl: './layout.component.html',
|
||||
styleUrls: ['./layout.component.scss'],
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
host: { class: 'app-layout' }
|
||||
selector: 'app-layout',
|
||||
templateUrl: './layout.component.html',
|
||||
styleUrls: ['./layout.component.scss'],
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
host: { class: 'app-layout' }
|
||||
})
|
||||
export class LayoutComponent implements OnInit, OnDestroy {
|
||||
@ViewChild(SidenavViewsManagerDirective)
|
||||
manager: SidenavViewsManagerDirective;
|
||||
@ViewChild(SidenavViewsManagerDirective)
|
||||
manager: SidenavViewsManagerDirective;
|
||||
|
||||
onDestroy$: Subject<boolean> = new Subject<boolean>();
|
||||
expandedSidenav: boolean;
|
||||
node: MinimalNodeEntryEntity;
|
||||
canUpload = false;
|
||||
onDestroy$: Subject<boolean> = new Subject<boolean>();
|
||||
expandedSidenav: boolean;
|
||||
node: MinimalNodeEntryEntity;
|
||||
canUpload = false;
|
||||
|
||||
appName$: Observable<string>;
|
||||
headerColor$: Observable<string>;
|
||||
logo$: Observable<string>;
|
||||
appName$: Observable<string>;
|
||||
headerColor$: Observable<string>;
|
||||
logo$: Observable<string>;
|
||||
|
||||
isSmallScreen = false;
|
||||
isSmallScreen = false;
|
||||
|
||||
constructor(
|
||||
protected store: Store<AppStore>,
|
||||
private permission: NodePermissionService,
|
||||
private breakpointObserver: BreakpointObserver
|
||||
) {
|
||||
this.headerColor$ = store.select(selectHeaderColor);
|
||||
this.appName$ = store.select(selectAppName);
|
||||
this.logo$ = store.select(selectLogoPath);
|
||||
constructor(
|
||||
protected store: Store<AppStore>,
|
||||
private permission: NodePermissionService,
|
||||
private breakpointObserver: BreakpointObserver
|
||||
) {
|
||||
this.headerColor$ = store.select(selectHeaderColor);
|
||||
this.appName$ = store.select(selectAppName);
|
||||
this.logo$ = store.select(selectLogoPath);
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
if (!this.manager.minimizeSidenav) {
|
||||
this.expandedSidenav = this.manager.sidenavState;
|
||||
} else {
|
||||
this.expandedSidenav = false;
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
if (!this.manager.minimizeSidenav) {
|
||||
this.expandedSidenav = this.manager.sidenavState;
|
||||
} else {
|
||||
this.expandedSidenav = false;
|
||||
}
|
||||
this.manager.run(true);
|
||||
|
||||
this.manager.run(true);
|
||||
this.store
|
||||
.select(currentFolder)
|
||||
.pipe(takeUntil(this.onDestroy$))
|
||||
.subscribe(node => {
|
||||
this.node = node;
|
||||
this.canUpload = node && this.permission.check(node, ['create']);
|
||||
});
|
||||
|
||||
this.store
|
||||
.select(currentFolder)
|
||||
.pipe(takeUntil(this.onDestroy$))
|
||||
.subscribe(node => {
|
||||
this.node = node;
|
||||
this.canUpload =
|
||||
node && this.permission.check(node, ['create']);
|
||||
});
|
||||
this.breakpointObserver
|
||||
.observe([Breakpoints.HandsetPortrait, Breakpoints.HandsetLandscape])
|
||||
.subscribe(result => {
|
||||
this.isSmallScreen = result.matches;
|
||||
});
|
||||
}
|
||||
|
||||
this.breakpointObserver
|
||||
.observe([
|
||||
Breakpoints.HandsetPortrait,
|
||||
Breakpoints.HandsetLandscape
|
||||
])
|
||||
.subscribe(result => {
|
||||
this.isSmallScreen = result.matches;
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.onDestroy$.next(true);
|
||||
this.onDestroy$.complete();
|
||||
}
|
||||
ngOnDestroy() {
|
||||
this.onDestroy$.next(true);
|
||||
this.onDestroy$.complete();
|
||||
}
|
||||
}
|
||||
|
@ -1,93 +1,92 @@
|
||||
import { Directive, ContentChild } from '@angular/core';
|
||||
import { Router, NavigationEnd } from '@angular/router';
|
||||
import {
|
||||
UserPreferencesService,
|
||||
AppConfigService,
|
||||
SidenavLayoutComponent
|
||||
UserPreferencesService,
|
||||
AppConfigService,
|
||||
SidenavLayoutComponent
|
||||
} from '@alfresco/adf-core';
|
||||
import { filter } from 'rxjs/operators';
|
||||
|
||||
@Directive({
|
||||
selector: '[acaSidenavManager]',
|
||||
exportAs: 'acaSidenavManager'
|
||||
selector: '[acaSidenavManager]',
|
||||
exportAs: 'acaSidenavManager'
|
||||
})
|
||||
export class SidenavViewsManagerDirective {
|
||||
@ContentChild(SidenavLayoutComponent) sidenavLayout: SidenavLayoutComponent;
|
||||
@ContentChild(SidenavLayoutComponent)
|
||||
sidenavLayout: SidenavLayoutComponent;
|
||||
|
||||
minimizeSidenav = false;
|
||||
hideSidenav = false;
|
||||
minimizeSidenav = false;
|
||||
hideSidenav = false;
|
||||
|
||||
private _run = false;
|
||||
private minimizeConditions: string[] = ['search'];
|
||||
private hideConditions: string[] = ['preview'];
|
||||
private _run = false;
|
||||
private minimizeConditions: string[] = ['search'];
|
||||
private hideConditions: string[] = ['preview'];
|
||||
|
||||
constructor(
|
||||
private router: Router,
|
||||
private userPreferenceService: UserPreferencesService,
|
||||
private appConfigService: AppConfigService
|
||||
constructor(
|
||||
private router: Router,
|
||||
private userPreferenceService: UserPreferencesService,
|
||||
private appConfigService: AppConfigService
|
||||
) {
|
||||
this.router.events
|
||||
.pipe(filter(event => event instanceof NavigationEnd))
|
||||
.subscribe((event: any) => {
|
||||
this.minimizeSidenav = this.minimizeConditions.some(el =>
|
||||
event.urlAfterRedirects.includes(el)
|
||||
);
|
||||
this.hideSidenav = this.hideConditions.some(el =>
|
||||
event.urlAfterRedirects.includes(el)
|
||||
);
|
||||
|
||||
if (this._run) {
|
||||
this.manageSidenavState();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
run(shouldRun) {
|
||||
this._run = shouldRun;
|
||||
}
|
||||
|
||||
manageSidenavState() {
|
||||
if (this.minimizeSidenav && !this.sidenavLayout.isMenuMinimized) {
|
||||
this.sidenavLayout.isMenuMinimized = true;
|
||||
this.sidenavLayout.container.toggleMenu();
|
||||
}
|
||||
|
||||
if (!this.minimizeSidenav) {
|
||||
if (this.sidenavState && this.sidenavLayout.isMenuMinimized) {
|
||||
this.sidenavLayout.isMenuMinimized = false;
|
||||
this.sidenavLayout.container.toggleMenu();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setState(state) {
|
||||
if (
|
||||
!this.minimizeSidenav &&
|
||||
this.appConfigService.get('sideNav.preserveState')
|
||||
) {
|
||||
this.router.events
|
||||
.pipe(filter(event => event instanceof NavigationEnd))
|
||||
.subscribe((event: any) => {
|
||||
this.minimizeSidenav = this.minimizeConditions.some(el =>
|
||||
event.urlAfterRedirects.includes(el)
|
||||
);
|
||||
this.hideSidenav = this.hideConditions.some(el =>
|
||||
event.urlAfterRedirects.includes(el)
|
||||
);
|
||||
this.userPreferenceService.set('expandedSidenav', state);
|
||||
}
|
||||
}
|
||||
|
||||
if (this._run) {
|
||||
this.manageSidenavState();
|
||||
}
|
||||
});
|
||||
get sidenavState(): boolean {
|
||||
const expand = this.appConfigService.get<boolean>(
|
||||
'sideNav.expandedSidenav',
|
||||
true
|
||||
);
|
||||
const preserveState = this.appConfigService.get<boolean>(
|
||||
'sideNav.preserveState',
|
||||
true
|
||||
);
|
||||
|
||||
if (preserveState) {
|
||||
return (
|
||||
this.userPreferenceService.get('expandedSidenav', expand.toString()) ===
|
||||
'true'
|
||||
);
|
||||
}
|
||||
|
||||
run(shouldRun) {
|
||||
this._run = shouldRun;
|
||||
}
|
||||
|
||||
manageSidenavState() {
|
||||
if (this.minimizeSidenav && !this.sidenavLayout.isMenuMinimized) {
|
||||
this.sidenavLayout.isMenuMinimized = true;
|
||||
this.sidenavLayout.container.toggleMenu();
|
||||
}
|
||||
|
||||
if (!this.minimizeSidenav) {
|
||||
if (this.sidenavState && this.sidenavLayout.isMenuMinimized) {
|
||||
this.sidenavLayout.isMenuMinimized = false;
|
||||
this.sidenavLayout.container.toggleMenu();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setState(state) {
|
||||
if (
|
||||
!this.minimizeSidenav &&
|
||||
this.appConfigService.get('sideNav.preserveState')
|
||||
) {
|
||||
this.userPreferenceService.set('expandedSidenav', state);
|
||||
}
|
||||
}
|
||||
|
||||
get sidenavState(): boolean {
|
||||
const expand = this.appConfigService.get<boolean>(
|
||||
'sideNav.expandedSidenav',
|
||||
true
|
||||
);
|
||||
const preserveState = this.appConfigService.get<boolean>(
|
||||
'sideNav.preserveState',
|
||||
true
|
||||
);
|
||||
|
||||
if (preserveState) {
|
||||
return (
|
||||
this.userPreferenceService.get(
|
||||
'expandedSidenav',
|
||||
expand.toString()
|
||||
) === 'true'
|
||||
);
|
||||
}
|
||||
|
||||
return expand;
|
||||
}
|
||||
return expand;
|
||||
}
|
||||
}
|
||||
|
@ -28,8 +28,12 @@ import { of } from 'rxjs';
|
||||
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import {
|
||||
AlfrescoApiService,
|
||||
TimeAgoPipe, NodeNameTooltipPipe, NodeFavoriteDirective, DataTableComponent, AppConfigPipe
|
||||
AlfrescoApiService,
|
||||
TimeAgoPipe,
|
||||
NodeNameTooltipPipe,
|
||||
NodeFavoriteDirective,
|
||||
DataTableComponent,
|
||||
AppConfigPipe
|
||||
} from '@alfresco/adf-core';
|
||||
import { DocumentListComponent } from '@alfresco/adf-content-services';
|
||||
import { ShareDataTableAdapter } from '@alfresco/adf-content-services';
|
||||
@ -39,149 +43,157 @@ import { ContentApiService } from '../../services/content-api.service';
|
||||
import { ExperimentalDirective } from '../../directives/experimental.directive';
|
||||
|
||||
describe('LibrariesComponent', () => {
|
||||
let fixture: ComponentFixture<LibrariesComponent>;
|
||||
let component: LibrariesComponent;
|
||||
let contentApi: ContentApiService;
|
||||
let alfrescoApi: AlfrescoApiService;
|
||||
let router: Router;
|
||||
let page;
|
||||
let node;
|
||||
let fixture: ComponentFixture<LibrariesComponent>;
|
||||
let component: LibrariesComponent;
|
||||
let contentApi: ContentApiService;
|
||||
let alfrescoApi: AlfrescoApiService;
|
||||
let router: Router;
|
||||
let page;
|
||||
let node;
|
||||
|
||||
beforeEach(() => {
|
||||
page = {
|
||||
list: {
|
||||
entries: [{ entry: { id: 1 } }, { entry: { id: 2 } }],
|
||||
pagination: { data: 'data' }
|
||||
}
|
||||
};
|
||||
|
||||
node = <any>{
|
||||
id: 'nodeId',
|
||||
path: {
|
||||
elements: []
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [AppTestingModule],
|
||||
declarations: [
|
||||
DataTableComponent,
|
||||
TimeAgoPipe,
|
||||
NodeNameTooltipPipe,
|
||||
NodeFavoriteDirective,
|
||||
DocumentListComponent,
|
||||
LibrariesComponent,
|
||||
AppConfigPipe,
|
||||
ExperimentalDirective
|
||||
],
|
||||
schemas: [NO_ERRORS_SCHEMA]
|
||||
});
|
||||
|
||||
fixture = TestBed.createComponent(LibrariesComponent);
|
||||
component = fixture.componentInstance;
|
||||
|
||||
alfrescoApi = TestBed.get(AlfrescoApiService);
|
||||
alfrescoApi.reset();
|
||||
router = TestBed.get(Router);
|
||||
|
||||
spyOn(alfrescoApi.sitesApi, 'getSites').and.returnValue(
|
||||
Promise.resolve(page)
|
||||
);
|
||||
spyOn(alfrescoApi.peopleApi, 'getSiteMembership').and.returnValue(
|
||||
Promise.resolve({})
|
||||
);
|
||||
|
||||
contentApi = TestBed.get(ContentApiService);
|
||||
});
|
||||
|
||||
describe('makeLibraryTooltip()', () => {
|
||||
it('maps tooltip to description', () => {
|
||||
node.description = 'description';
|
||||
const tooltip = component.makeLibraryTooltip(node);
|
||||
|
||||
expect(tooltip).toBe(node.description);
|
||||
});
|
||||
|
||||
it('maps tooltip to description', () => {
|
||||
node.title = 'title';
|
||||
const tooltip = component.makeLibraryTooltip(node);
|
||||
|
||||
expect(tooltip).toBe(node.title);
|
||||
});
|
||||
|
||||
it('sets tooltip to empty string', () => {
|
||||
const tooltip = component.makeLibraryTooltip(node);
|
||||
|
||||
expect(tooltip).toBe('');
|
||||
});
|
||||
});
|
||||
|
||||
describe('makeLibraryTitle()', () => {
|
||||
it('sets title with id when duplicate nodes title exists in list', () => {
|
||||
node.title = 'title';
|
||||
|
||||
const data = new ShareDataTableAdapter(null, null);
|
||||
data.setRows([
|
||||
<any>{ node: { entry: { id: 'some-id', title: 'title' } } }
|
||||
]);
|
||||
|
||||
component.documentList.data = data;
|
||||
|
||||
const title = component.makeLibraryTitle(node);
|
||||
expect(title).toContain('nodeId');
|
||||
});
|
||||
|
||||
it('sets title when no duplicate nodes title exists in list', () => {
|
||||
node.title = 'title';
|
||||
|
||||
const data = new ShareDataTableAdapter(null, null);
|
||||
data.setRows([
|
||||
<any>{ node: { entry: { id: 'some-id', title: 'title-some-id' } } }
|
||||
]);
|
||||
|
||||
component.documentList.data = data;
|
||||
const title = component.makeLibraryTitle(node);
|
||||
|
||||
expect(title).toBe('title');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Node navigation', () => {
|
||||
let routerSpy;
|
||||
|
||||
beforeEach(() => {
|
||||
page = {
|
||||
list: {
|
||||
entries: [ { entry: { id: 1 } }, { entry: { id: 2 } } ],
|
||||
pagination: { data: 'data'}
|
||||
}
|
||||
};
|
||||
|
||||
node = <any> {
|
||||
id: 'nodeId',
|
||||
path: {
|
||||
elements: []
|
||||
}
|
||||
};
|
||||
routerSpy = spyOn(router, 'navigate');
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [ AppTestingModule ],
|
||||
declarations: [
|
||||
DataTableComponent,
|
||||
TimeAgoPipe,
|
||||
NodeNameTooltipPipe,
|
||||
NodeFavoriteDirective,
|
||||
DocumentListComponent,
|
||||
LibrariesComponent,
|
||||
AppConfigPipe,
|
||||
ExperimentalDirective
|
||||
],
|
||||
schemas: [ NO_ERRORS_SCHEMA ]
|
||||
});
|
||||
it('does not navigate when id is not passed', () => {
|
||||
component.navigate(null);
|
||||
|
||||
fixture = TestBed.createComponent(LibrariesComponent);
|
||||
component = fixture.componentInstance;
|
||||
|
||||
alfrescoApi = TestBed.get(AlfrescoApiService);
|
||||
alfrescoApi.reset();
|
||||
router = TestBed.get(Router);
|
||||
|
||||
spyOn(alfrescoApi.sitesApi, 'getSites').and.returnValue((Promise.resolve(page)));
|
||||
spyOn(alfrescoApi.peopleApi, 'getSiteMembership').and.returnValue((Promise.resolve({})));
|
||||
|
||||
contentApi = TestBed.get(ContentApiService);
|
||||
expect(router.navigate).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
describe('makeLibraryTooltip()', () => {
|
||||
it('maps tooltip to description', () => {
|
||||
node.description = 'description';
|
||||
const tooltip = component.makeLibraryTooltip(node);
|
||||
it('navigates to node id', () => {
|
||||
const document = { id: 'documentId' };
|
||||
spyOn(contentApi, 'getNode').and.returnValue(of({ entry: document }));
|
||||
|
||||
expect(tooltip).toBe(node.description);
|
||||
});
|
||||
component.navigate(node.id);
|
||||
|
||||
it('maps tooltip to description', () => {
|
||||
node.title = 'title';
|
||||
const tooltip = component.makeLibraryTooltip(node);
|
||||
expect(routerSpy.calls.argsFor(0)[0]).toEqual(['./', document.id]);
|
||||
});
|
||||
});
|
||||
|
||||
expect(tooltip).toBe(node.title);
|
||||
});
|
||||
describe('navigateTo', () => {
|
||||
it('navigates into library folder', () => {
|
||||
spyOn(component, 'navigate');
|
||||
|
||||
it('sets tooltip to empty string', () => {
|
||||
const tooltip = component.makeLibraryTooltip(node);
|
||||
const site: any = {
|
||||
entry: { guid: 'node-guid' }
|
||||
};
|
||||
|
||||
expect(tooltip).toBe('');
|
||||
});
|
||||
component.navigateTo(site);
|
||||
|
||||
expect(component.navigate).toHaveBeenCalledWith('node-guid');
|
||||
});
|
||||
|
||||
describe('makeLibraryTitle()', () => {
|
||||
it('sets title with id when duplicate nodes title exists in list', () => {
|
||||
node.title = 'title';
|
||||
it(' does not navigate when library is not provided', () => {
|
||||
spyOn(component, 'navigate');
|
||||
|
||||
const data = new ShareDataTableAdapter(null, null);
|
||||
data.setRows([<any>{ node: { entry: { id: 'some-id', title: 'title' } } }]);
|
||||
component.navigateTo(null);
|
||||
|
||||
component.documentList.data = data;
|
||||
|
||||
const title = component.makeLibraryTitle(node);
|
||||
expect(title).toContain('nodeId');
|
||||
});
|
||||
|
||||
it('sets title when no duplicate nodes title exists in list', () => {
|
||||
node.title = 'title';
|
||||
|
||||
const data = new ShareDataTableAdapter(null, null);
|
||||
data.setRows([<any>{ node: { entry: { id: 'some-id', title: 'title-some-id' } } }]);
|
||||
|
||||
component.documentList.data = data;
|
||||
const title = component.makeLibraryTitle(node);
|
||||
|
||||
expect(title).toBe('title');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Node navigation', () => {
|
||||
let routerSpy;
|
||||
|
||||
beforeEach(() => {
|
||||
routerSpy = spyOn(router, 'navigate');
|
||||
});
|
||||
|
||||
it('does not navigate when id is not passed', () => {
|
||||
component.navigate(null);
|
||||
|
||||
expect(router.navigate).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('navigates to node id', () => {
|
||||
const document = { id: 'documentId' };
|
||||
spyOn(contentApi, 'getNode').and.returnValue(of({ entry: document }));
|
||||
|
||||
component.navigate(node.id);
|
||||
|
||||
expect(routerSpy.calls.argsFor(0)[0]).toEqual(['./', document.id]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('navigateTo', () => {
|
||||
it('navigates into library folder', () => {
|
||||
spyOn(component, 'navigate');
|
||||
|
||||
const site: any = {
|
||||
entry: { guid: 'node-guid' }
|
||||
};
|
||||
|
||||
component.navigateTo(site);
|
||||
|
||||
expect(component.navigate).toHaveBeenCalledWith('node-guid');
|
||||
});
|
||||
|
||||
it(' does not navigate when library is not provided', () => {
|
||||
spyOn(component, 'navigate');
|
||||
|
||||
component.navigateTo(null);
|
||||
|
||||
expect(component.navigate).not.toHaveBeenCalled();
|
||||
});
|
||||
expect(component.navigate).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -38,79 +38,78 @@ import { AppExtensionService } from '../../extensions/extension.service';
|
||||
import { map } from 'rxjs/operators';
|
||||
|
||||
@Component({
|
||||
templateUrl: './libraries.component.html'
|
||||
templateUrl: './libraries.component.html'
|
||||
})
|
||||
export class LibrariesComponent extends PageComponent implements OnInit {
|
||||
isSmallScreen = false;
|
||||
|
||||
isSmallScreen = false;
|
||||
constructor(
|
||||
private route: ActivatedRoute,
|
||||
content: ContentManagementService,
|
||||
private contentApi: ContentApiService,
|
||||
store: Store<AppStore>,
|
||||
extensions: AppExtensionService,
|
||||
private router: Router,
|
||||
private breakpointObserver: BreakpointObserver
|
||||
) {
|
||||
super(store, extensions, content);
|
||||
}
|
||||
|
||||
constructor(private route: ActivatedRoute,
|
||||
content: ContentManagementService,
|
||||
private contentApi: ContentApiService,
|
||||
store: Store<AppStore>,
|
||||
extensions: AppExtensionService,
|
||||
private router: Router,
|
||||
private breakpointObserver: BreakpointObserver) {
|
||||
super(store, extensions, content);
|
||||
ngOnInit() {
|
||||
super.ngOnInit();
|
||||
|
||||
this.subscriptions.push(
|
||||
this.content.libraryDeleted.subscribe(() => this.reload()),
|
||||
this.content.libraryCreated.subscribe((node: SiteEntry) => {
|
||||
this.navigate(node.entry.guid);
|
||||
}),
|
||||
|
||||
this.breakpointObserver
|
||||
.observe([Breakpoints.HandsetPortrait, Breakpoints.HandsetLandscape])
|
||||
.subscribe(result => {
|
||||
this.isSmallScreen = result.matches;
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
makeLibraryTooltip(library: any): string {
|
||||
const { description, title } = library;
|
||||
|
||||
return description || title || '';
|
||||
}
|
||||
|
||||
makeLibraryTitle(library: any): string {
|
||||
const rows = this.documentList.data.getRows();
|
||||
const entries = rows.map((r: ShareDataRow) => r.node.entry);
|
||||
const { title, id } = library;
|
||||
|
||||
let isDuplicate = false;
|
||||
|
||||
if (entries) {
|
||||
isDuplicate = entries.some((entry: any) => {
|
||||
return entry.id !== id && entry.title === title;
|
||||
});
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
super.ngOnInit();
|
||||
return isDuplicate ? `${title} (${id})` : `${title}`;
|
||||
}
|
||||
|
||||
this.subscriptions.push(
|
||||
this.content.libraryDeleted.subscribe(() => this.reload()),
|
||||
this.content.libraryCreated.subscribe((node: SiteEntry) => {
|
||||
this.navigate(node.entry.guid);
|
||||
}),
|
||||
|
||||
this.breakpointObserver
|
||||
.observe([
|
||||
Breakpoints.HandsetPortrait,
|
||||
Breakpoints.HandsetLandscape
|
||||
])
|
||||
.subscribe(result => {
|
||||
this.isSmallScreen = result.matches;
|
||||
})
|
||||
);
|
||||
navigateTo(node: SiteEntry) {
|
||||
if (node && node.entry.guid) {
|
||||
this.navigate(node.entry.guid);
|
||||
}
|
||||
}
|
||||
|
||||
makeLibraryTooltip(library: any): string {
|
||||
const { description, title } = library;
|
||||
|
||||
return description || title || '';
|
||||
}
|
||||
|
||||
makeLibraryTitle(library: any): string {
|
||||
const rows = this.documentList.data.getRows();
|
||||
const entries = rows.map((r: ShareDataRow) => r.node.entry);
|
||||
const { title, id } = library;
|
||||
|
||||
let isDuplicate = false;
|
||||
|
||||
if (entries) {
|
||||
isDuplicate = entries
|
||||
.some((entry: any) => {
|
||||
return (entry.id !== id && entry.title === title);
|
||||
});
|
||||
}
|
||||
|
||||
return isDuplicate ? `${title} (${id})` : `${title}`;
|
||||
}
|
||||
|
||||
navigateTo(node: SiteEntry) {
|
||||
if (node && node.entry.guid) {
|
||||
this.navigate(node.entry.guid);
|
||||
}
|
||||
}
|
||||
|
||||
navigate(libraryId: string) {
|
||||
if (libraryId) {
|
||||
this.contentApi
|
||||
.getNode(libraryId, { relativePath: '/documentLibrary' })
|
||||
.pipe(map(node => node.entry))
|
||||
.subscribe(documentLibrary => {
|
||||
this.router.navigate([ './', documentLibrary.id ], { relativeTo: this.route });
|
||||
});
|
||||
}
|
||||
navigate(libraryId: string) {
|
||||
if (libraryId) {
|
||||
this.contentApi
|
||||
.getNode(libraryId, { relativePath: '/documentLibrary' })
|
||||
.pipe(map(node => node.entry))
|
||||
.subscribe(documentLibrary => {
|
||||
this.router.navigate(['./', documentLibrary.id], {
|
||||
relativeTo: this.route
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -23,7 +23,14 @@
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Component, Input, ChangeDetectionStrategy, OnInit, ViewEncapsulation, HostListener } from '@angular/core';
|
||||
import {
|
||||
Component,
|
||||
Input,
|
||||
ChangeDetectionStrategy,
|
||||
OnInit,
|
||||
ViewEncapsulation,
|
||||
HostListener
|
||||
} from '@angular/core';
|
||||
import { PathInfo, MinimalNodeEntity } from 'alfresco-js-api';
|
||||
import { Observable, BehaviorSubject, of } from 'rxjs';
|
||||
|
||||
@ -33,140 +40,148 @@ import { NavigateToParentFolder } from '../../store/actions';
|
||||
import { ContentApiService } from '../../services/content-api.service';
|
||||
|
||||
@Component({
|
||||
selector: 'aca-location-link',
|
||||
template: `
|
||||
selector: 'aca-location-link',
|
||||
template: `
|
||||
<a href="" [title]="nodeLocation$ | async" (click)="goToLocation()">
|
||||
{{ displayText | async }}
|
||||
</a>
|
||||
`,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
host: { 'class': 'aca-location-link adf-location-cell' }
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
host: { class: 'aca-location-link adf-location-cell' }
|
||||
})
|
||||
export class LocationLinkComponent implements OnInit {
|
||||
private _path: PathInfo;
|
||||
private _path: PathInfo;
|
||||
|
||||
nodeLocation$ = new BehaviorSubject(null);
|
||||
nodeLocation$ = new BehaviorSubject(null);
|
||||
|
||||
@Input()
|
||||
context: any;
|
||||
@Input()
|
||||
context: any;
|
||||
|
||||
@Input()
|
||||
link: any[];
|
||||
@Input()
|
||||
link: any[];
|
||||
|
||||
@Input()
|
||||
displayText: Observable<string>;
|
||||
@Input()
|
||||
displayText: Observable<string>;
|
||||
|
||||
@Input()
|
||||
tooltip: Observable<string>;
|
||||
@Input()
|
||||
tooltip: Observable<string>;
|
||||
|
||||
@HostListener('mouseenter') onMouseEnter() {
|
||||
this.getTooltip(this._path);
|
||||
@HostListener('mouseenter')
|
||||
onMouseEnter() {
|
||||
this.getTooltip(this._path);
|
||||
}
|
||||
|
||||
constructor(
|
||||
private store: Store<AppStore>,
|
||||
private contentApi: ContentApiService
|
||||
) {}
|
||||
|
||||
goToLocation() {
|
||||
if (this.context) {
|
||||
const node: MinimalNodeEntity = this.context.row.node;
|
||||
this.store.dispatch(new NavigateToParentFolder(node));
|
||||
}
|
||||
}
|
||||
|
||||
constructor(
|
||||
private store: Store<AppStore>,
|
||||
private contentApi: ContentApiService) {
|
||||
}
|
||||
ngOnInit() {
|
||||
if (this.context) {
|
||||
const node: MinimalNodeEntity = this.context.row.node;
|
||||
if (node && node.entry && node.entry.path) {
|
||||
const path = node.entry.path;
|
||||
|
||||
goToLocation() {
|
||||
if (this.context) {
|
||||
const node: MinimalNodeEntity = this.context.row.node;
|
||||
this.store.dispatch(new NavigateToParentFolder(node));
|
||||
if (path && path.name && path.elements) {
|
||||
this.displayText = this.getDisplayText(path);
|
||||
this._path = path;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// todo: review once 5.2.3 is out
|
||||
private getDisplayText(path: PathInfo): Observable<string> {
|
||||
const elements = path.elements.map(e => e.name);
|
||||
|
||||
// for admin users
|
||||
if (elements.length === 1 && elements[0] === 'Company Home') {
|
||||
return of('Personal Files');
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
if (this.context) {
|
||||
const node: MinimalNodeEntity = this.context.row.node;
|
||||
if (node && node.entry && node.entry.path) {
|
||||
const path = node.entry.path;
|
||||
// for non-admin users
|
||||
if (
|
||||
elements.length === 3 &&
|
||||
elements[0] === 'Company Home' &&
|
||||
elements[1] === 'User Homes'
|
||||
) {
|
||||
return of('Personal Files');
|
||||
}
|
||||
|
||||
if (path && path.name && path.elements) {
|
||||
this.displayText = this.getDisplayText(path);
|
||||
this._path = path;
|
||||
}
|
||||
const result = elements[elements.length - 1];
|
||||
|
||||
if (result === 'documentLibrary') {
|
||||
const fragment = path.elements[path.elements.length - 2];
|
||||
|
||||
return new Observable<string>(observer => {
|
||||
this.contentApi.getNodeInfo(fragment.id).subscribe(
|
||||
node => {
|
||||
observer.next(
|
||||
node.properties['cm:title'] || node.name || fragment.name
|
||||
);
|
||||
observer.complete();
|
||||
},
|
||||
() => {
|
||||
observer.next(fragment.name);
|
||||
observer.complete();
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
return of(result);
|
||||
}
|
||||
|
||||
// todo: review once 5.2.3 is out
|
||||
private getTooltip(path: PathInfo) {
|
||||
let result: string = null;
|
||||
|
||||
const elements = path.elements.map(e => Object.assign({}, e));
|
||||
|
||||
if (elements[0].name === 'Company Home') {
|
||||
elements[0].name = 'Personal Files';
|
||||
|
||||
if (elements.length > 2) {
|
||||
if (elements[1].name === 'Sites') {
|
||||
const fragment = elements[2];
|
||||
this.contentApi.getNodeInfo(fragment.id).subscribe(
|
||||
node => {
|
||||
elements.splice(0, 2);
|
||||
elements[0].name =
|
||||
node.properties['cm:title'] || node.name || fragment.name;
|
||||
elements.splice(1, 1);
|
||||
elements.unshift({ id: null, name: 'File Libraries' });
|
||||
|
||||
result = elements.map(e => e.name).join('/');
|
||||
this.nodeLocation$.next(result);
|
||||
},
|
||||
() => {
|
||||
elements.splice(0, 2);
|
||||
elements.unshift({ id: null, name: 'File Libraries' });
|
||||
elements.splice(2, 1);
|
||||
|
||||
result = elements.map(e => e.name).join('/');
|
||||
this.nodeLocation$.next(result);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
if (elements[1].name === 'User Homes') {
|
||||
elements.splice(0, 3);
|
||||
elements.unshift({ id: null, name: 'Personal Files' });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// todo: review once 5.2.3 is out
|
||||
private getDisplayText(path: PathInfo): Observable<string> {
|
||||
const elements = path.elements.map(e => e.name);
|
||||
|
||||
// for admin users
|
||||
if (elements.length === 1 && elements[0] === 'Company Home') {
|
||||
return of('Personal Files');
|
||||
}
|
||||
|
||||
// for non-admin users
|
||||
if (elements.length === 3 && elements[0] === 'Company Home' && elements[1] === 'User Homes') {
|
||||
return of('Personal Files');
|
||||
}
|
||||
|
||||
const result = elements[elements.length - 1];
|
||||
|
||||
if (result === 'documentLibrary') {
|
||||
const fragment = path.elements[path.elements.length - 2];
|
||||
|
||||
return new Observable<string>(observer => {
|
||||
this.contentApi.getNodeInfo(fragment.id).subscribe(
|
||||
node => {
|
||||
observer.next(node.properties['cm:title'] || node.name || fragment.name);
|
||||
observer.complete();
|
||||
},
|
||||
() => {
|
||||
observer.next(fragment.name);
|
||||
observer.complete();
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
return of(result);
|
||||
}
|
||||
|
||||
// todo: review once 5.2.3 is out
|
||||
private getTooltip(path: PathInfo) {
|
||||
let result: string = null;
|
||||
|
||||
const elements = path.elements.map(e => Object.assign({}, e));
|
||||
|
||||
if (elements[0].name === 'Company Home') {
|
||||
elements[0].name = 'Personal Files';
|
||||
|
||||
if (elements.length > 2) {
|
||||
if (elements[1].name === 'Sites') {
|
||||
const fragment = elements[2];
|
||||
this.contentApi.getNodeInfo(fragment.id).subscribe(
|
||||
node => {
|
||||
elements.splice(0, 2);
|
||||
elements[0].name = node.properties['cm:title'] || node.name || fragment.name;
|
||||
elements.splice(1, 1);
|
||||
elements.unshift({ id: null, name: 'File Libraries' });
|
||||
|
||||
result = elements.map(e => e.name).join('/');
|
||||
this.nodeLocation$.next(result);
|
||||
},
|
||||
() => {
|
||||
elements.splice(0, 2);
|
||||
elements.unshift({ id: null, name: 'File Libraries' });
|
||||
elements.splice(2, 1);
|
||||
|
||||
result = elements.map(e => e.name).join('/');
|
||||
this.nodeLocation$.next(result);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
if (elements[1].name === 'User Homes') {
|
||||
elements.splice(0, 3);
|
||||
elements.unshift({ id: null, name: 'Personal Files'});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result = elements.map(e => e.name).join('/');
|
||||
this.nodeLocation$.next(result);
|
||||
}
|
||||
result = elements.map(e => e.name).join('/');
|
||||
this.nodeLocation$.next(result);
|
||||
}
|
||||
}
|
||||
|
@ -27,58 +27,57 @@ import { NO_ERRORS_SCHEMA } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { Location } from '@angular/common';
|
||||
import { TestBed, ComponentFixture } from '@angular/core/testing';
|
||||
import { AuthenticationService, UserPreferencesService, AppConfigPipe } from '@alfresco/adf-core';
|
||||
import {
|
||||
AuthenticationService,
|
||||
UserPreferencesService,
|
||||
AppConfigPipe
|
||||
} from '@alfresco/adf-core';
|
||||
import { LoginComponent } from './login.component';
|
||||
import { AppTestingModule } from '../../testing/app-testing.module';
|
||||
|
||||
describe('LoginComponent', () => {
|
||||
let fixture: ComponentFixture<LoginComponent>;
|
||||
let router: Router;
|
||||
let userPreference: UserPreferencesService;
|
||||
let auth: AuthenticationService;
|
||||
let location: Location;
|
||||
let fixture: ComponentFixture<LoginComponent>;
|
||||
let router: Router;
|
||||
let userPreference: UserPreferencesService;
|
||||
let auth: AuthenticationService;
|
||||
let location: Location;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [ AppTestingModule ],
|
||||
declarations: [
|
||||
LoginComponent,
|
||||
AppConfigPipe
|
||||
],
|
||||
providers: [
|
||||
Location
|
||||
],
|
||||
schemas: [ NO_ERRORS_SCHEMA ]
|
||||
});
|
||||
|
||||
fixture = TestBed.createComponent(LoginComponent);
|
||||
|
||||
router = TestBed.get(Router);
|
||||
spyOn(router, 'navigateByUrl');
|
||||
|
||||
location = TestBed.get(Location);
|
||||
spyOn(location, 'forward');
|
||||
|
||||
auth = TestBed.get(AuthenticationService);
|
||||
spyOn(auth, 'getRedirect').and.returnValue('/some-url');
|
||||
|
||||
userPreference = TestBed.get(UserPreferencesService);
|
||||
spyOn(userPreference, 'setStoragePrefix');
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [AppTestingModule],
|
||||
declarations: [LoginComponent, AppConfigPipe],
|
||||
providers: [Location],
|
||||
schemas: [NO_ERRORS_SCHEMA]
|
||||
});
|
||||
|
||||
describe('OnInit()', () => {
|
||||
it('should perform normal login when user is not logged in', () => {
|
||||
spyOn(auth, 'isEcmLoggedIn').and.returnValue(false);
|
||||
fixture.detectChanges();
|
||||
fixture = TestBed.createComponent(LoginComponent);
|
||||
|
||||
expect(location.forward).not.toHaveBeenCalled();
|
||||
});
|
||||
router = TestBed.get(Router);
|
||||
spyOn(router, 'navigateByUrl');
|
||||
|
||||
it('should redirect when user is logged in', () => {
|
||||
spyOn(auth, 'isEcmLoggedIn').and.returnValue(true);
|
||||
fixture.detectChanges();
|
||||
location = TestBed.get(Location);
|
||||
spyOn(location, 'forward');
|
||||
|
||||
expect(location.forward).toHaveBeenCalled();
|
||||
});
|
||||
auth = TestBed.get(AuthenticationService);
|
||||
spyOn(auth, 'getRedirect').and.returnValue('/some-url');
|
||||
|
||||
userPreference = TestBed.get(UserPreferencesService);
|
||||
spyOn(userPreference, 'setStoragePrefix');
|
||||
});
|
||||
|
||||
describe('OnInit()', () => {
|
||||
it('should perform normal login when user is not logged in', () => {
|
||||
spyOn(auth, 'isEcmLoggedIn').and.returnValue(false);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(location.forward).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should redirect when user is logged in', () => {
|
||||
spyOn(auth, 'isEcmLoggedIn').and.returnValue(true);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(location.forward).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -28,17 +28,17 @@ import { Location } from '@angular/common';
|
||||
import { AuthenticationService } from '@alfresco/adf-core';
|
||||
|
||||
@Component({
|
||||
templateUrl: './login.component.html'
|
||||
templateUrl: './login.component.html'
|
||||
})
|
||||
export class LoginComponent implements OnInit {
|
||||
constructor(
|
||||
private location: Location,
|
||||
private auth: AuthenticationService,
|
||||
) {}
|
||||
constructor(
|
||||
private location: Location,
|
||||
private auth: AuthenticationService
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
if (this.auth.isEcmLoggedIn()) {
|
||||
this.location.forward();
|
||||
}
|
||||
ngOnInit() {
|
||||
if (this.auth.isEcmLoggedIn()) {
|
||||
this.location.forward();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -26,31 +26,31 @@
|
||||
import { PageComponent } from './page.component';
|
||||
|
||||
class TestClass extends PageComponent {
|
||||
node: any;
|
||||
node: any;
|
||||
|
||||
constructor() {
|
||||
super(null, null, null);
|
||||
}
|
||||
constructor() {
|
||||
super(null, null, null);
|
||||
}
|
||||
}
|
||||
|
||||
describe('PageComponent', () => {
|
||||
let component: TestClass;
|
||||
let component: TestClass;
|
||||
|
||||
beforeEach(() => {
|
||||
component = new TestClass();
|
||||
beforeEach(() => {
|
||||
component = new TestClass();
|
||||
});
|
||||
|
||||
describe('getParentNodeId()', () => {
|
||||
it('returns parent node id when node is set', () => {
|
||||
component.node = { id: 'node-id' };
|
||||
|
||||
expect(component.getParentNodeId()).toBe('node-id');
|
||||
});
|
||||
|
||||
describe('getParentNodeId()', () => {
|
||||
it('returns parent node id when node is set', () => {
|
||||
component.node = { id: 'node-id' };
|
||||
it('returns null when node is not set', () => {
|
||||
component.node = null;
|
||||
|
||||
expect(component.getParentNodeId()).toBe('node-id');
|
||||
});
|
||||
|
||||
it('returns null when node is not set', () => {
|
||||
component.node = null;
|
||||
|
||||
expect(component.getParentNodeId()).toBe(null);
|
||||
});
|
||||
expect(component.getParentNodeId()).toBe(null);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -23,7 +23,10 @@
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { DocumentListComponent, ShareDataRow } from '@alfresco/adf-content-services';
|
||||
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';
|
||||
@ -33,97 +36,109 @@ import { takeUntil } from 'rxjs/operators';
|
||||
import { AppExtensionService } from '../extensions/extension.service';
|
||||
import { ContentManagementService } from '../services/content-management.service';
|
||||
import { SetSelectedNodesAction, ViewFileAction } from '../store/actions';
|
||||
import { appSelection, currentFolder, documentDisplayMode, infoDrawerOpened, sharedUrl } from '../store/selectors/app.selectors';
|
||||
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 {
|
||||
onDestroy$: Subject<boolean> = new Subject<boolean>();
|
||||
|
||||
onDestroy$: Subject<boolean> = new Subject<boolean>();
|
||||
@ViewChild(DocumentListComponent)
|
||||
documentList: DocumentListComponent;
|
||||
|
||||
@ViewChild(DocumentListComponent)
|
||||
documentList: DocumentListComponent;
|
||||
title = 'Page';
|
||||
infoDrawerOpened$: Observable<boolean>;
|
||||
node: MinimalNodeEntryEntity;
|
||||
selection: SelectionState;
|
||||
documentDisplayMode$: Observable<string>;
|
||||
sharedPreviewUrl$: Observable<string>;
|
||||
actions: Array<ContentActionRef> = [];
|
||||
viewerToolbarActions: Array<ContentActionRef> = [];
|
||||
canUpdateNode = false;
|
||||
canUpload = false;
|
||||
|
||||
title = 'Page';
|
||||
infoDrawerOpened$: Observable<boolean>;
|
||||
node: MinimalNodeEntryEntity;
|
||||
selection: SelectionState;
|
||||
documentDisplayMode$: Observable<string>;
|
||||
sharedPreviewUrl$: Observable<string>;
|
||||
actions: Array<ContentActionRef> = [];
|
||||
viewerToolbarActions: Array<ContentActionRef> = [];
|
||||
canUpdateNode = false;
|
||||
canUpload = false;
|
||||
protected subscriptions: Subscription[] = [];
|
||||
|
||||
protected subscriptions: Subscription[] = [];
|
||||
static isLockedNode(node) {
|
||||
return (
|
||||
node.isLocked ||
|
||||
(node.properties && node.properties['cm:lockType'] === 'READ_ONLY_LOCK')
|
||||
);
|
||||
}
|
||||
|
||||
static isLockedNode(node) {
|
||||
return node.isLocked || (node.properties && node.properties['cm:lockType'] === 'READ_ONLY_LOCK');
|
||||
constructor(
|
||||
protected store: Store<AppStore>,
|
||||
protected extensions: AppExtensionService,
|
||||
protected content: ContentManagementService
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.sharedPreviewUrl$ = this.store.select(sharedUrl);
|
||||
this.infoDrawerOpened$ = this.store.select(infoDrawerOpened);
|
||||
this.documentDisplayMode$ = this.store.select(documentDisplayMode);
|
||||
|
||||
this.store
|
||||
.select(appSelection)
|
||||
.pipe(takeUntil(this.onDestroy$))
|
||||
.subscribe(selection => {
|
||||
this.selection = selection;
|
||||
this.actions = this.extensions.getAllowedToolbarActions();
|
||||
this.viewerToolbarActions = this.extensions.getViewerToolbarActions();
|
||||
this.canUpdateNode =
|
||||
this.selection.count === 1 &&
|
||||
this.content.canUpdateNode(selection.first);
|
||||
});
|
||||
|
||||
this.store
|
||||
.select(currentFolder)
|
||||
.pipe(takeUntil(this.onDestroy$))
|
||||
.subscribe(node => {
|
||||
this.canUpload = node && this.content.canUploadContent(node);
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.subscriptions.forEach(subscription => subscription.unsubscribe());
|
||||
this.subscriptions = [];
|
||||
|
||||
this.onDestroy$.next(true);
|
||||
this.onDestroy$.complete();
|
||||
}
|
||||
|
||||
showPreview(node: MinimalNodeEntity) {
|
||||
if (node && node.entry) {
|
||||
const parentId = this.node ? this.node.id : null;
|
||||
this.store.dispatch(new ViewFileAction(node, parentId));
|
||||
}
|
||||
}
|
||||
|
||||
constructor(
|
||||
protected store: Store<AppStore>,
|
||||
protected extensions: AppExtensionService,
|
||||
protected content: ContentManagementService) {}
|
||||
getParentNodeId(): string {
|
||||
return this.node ? this.node.id : null;
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.sharedPreviewUrl$ = this.store.select(sharedUrl);
|
||||
this.infoDrawerOpened$ = this.store.select(infoDrawerOpened);
|
||||
this.documentDisplayMode$ = this.store.select(documentDisplayMode);
|
||||
imageResolver(row: ShareDataRow): string | null {
|
||||
const entry: MinimalNodeEntryEntity = row.node.entry;
|
||||
|
||||
this.store
|
||||
.select(appSelection)
|
||||
.pipe(takeUntil(this.onDestroy$))
|
||||
.subscribe(selection => {
|
||||
this.selection = selection;
|
||||
this.actions = this.extensions.getAllowedToolbarActions();
|
||||
this.viewerToolbarActions = this.extensions.getViewerToolbarActions();
|
||||
this.canUpdateNode = this.selection.count === 1 && this.content.canUpdateNode(selection.first);
|
||||
});
|
||||
|
||||
this.store.select(currentFolder)
|
||||
.pipe(takeUntil(this.onDestroy$))
|
||||
.subscribe(node => {
|
||||
this.canUpload = node && this.content.canUploadContent(node);
|
||||
});
|
||||
if (PageComponent.isLockedNode(entry)) {
|
||||
return 'assets/images/ic_lock_black_24dp_1x.png';
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.subscriptions.forEach(subscription => subscription.unsubscribe());
|
||||
this.subscriptions = [];
|
||||
|
||||
this.onDestroy$.next(true);
|
||||
this.onDestroy$.complete();
|
||||
reload(): void {
|
||||
if (this.documentList) {
|
||||
this.documentList.resetSelection();
|
||||
this.store.dispatch(new SetSelectedNodesAction([]));
|
||||
this.documentList.reload();
|
||||
}
|
||||
}
|
||||
|
||||
showPreview(node: MinimalNodeEntity) {
|
||||
if (node && node.entry) {
|
||||
const parentId = this.node ? this.node.id : null;
|
||||
this.store.dispatch(new ViewFileAction(node, parentId));
|
||||
}
|
||||
}
|
||||
|
||||
getParentNodeId(): string {
|
||||
return this.node ? this.node.id : null;
|
||||
}
|
||||
|
||||
imageResolver(row: ShareDataRow): string | null {
|
||||
const entry: MinimalNodeEntryEntity = row.node.entry;
|
||||
|
||||
if (PageComponent.isLockedNode(entry)) {
|
||||
return 'assets/images/ic_lock_black_24dp_1x.png';
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
reload(): void {
|
||||
if (this.documentList) {
|
||||
this.documentList.resetSelection();
|
||||
this.store.dispatch(new SetSelectedNodesAction([]));
|
||||
this.documentList.reload();
|
||||
}
|
||||
}
|
||||
|
||||
trackByActionId(index: number, action: ContentActionRef) {
|
||||
return action.id;
|
||||
}
|
||||
trackByActionId(index: number, action: ContentActionRef) {
|
||||
return action.id;
|
||||
}
|
||||
}
|
||||
|
@ -1,49 +1,49 @@
|
||||
@mixin aca-permissions-manager-theme($theme) {
|
||||
$foreground: map-get($theme, foreground);
|
||||
$accent: map-get($theme, accent);
|
||||
$foreground: map-get($theme, foreground);
|
||||
$accent: map-get($theme, accent);
|
||||
|
||||
aca-permissions-dialog-panel {
|
||||
height: 400px;
|
||||
aca-permissions-dialog-panel {
|
||||
height: 400px;
|
||||
}
|
||||
|
||||
.aca-node-permissions-dialog {
|
||||
.mat-dialog-title {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
font-style: normal;
|
||||
font-stretch: normal;
|
||||
line-height: 1.6;
|
||||
margin: 0;
|
||||
letter-spacing: -0.5px;
|
||||
color: mat-color($foreground, text, 0.87);
|
||||
}
|
||||
|
||||
.aca-node-permissions-dialog {
|
||||
.mat-dialog-title {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
font-style: normal;
|
||||
font-stretch: normal;
|
||||
line-height: 1.6;
|
||||
margin: 0;
|
||||
letter-spacing: -0.5px;
|
||||
color: mat-color($foreground, text, 0.87);
|
||||
}
|
||||
.mat-dialog-content {
|
||||
flex: 1 1 auto;
|
||||
position: relative;
|
||||
overflow: auto;
|
||||
|
||||
.mat-dialog-content {
|
||||
flex: 1 1 auto;
|
||||
position: relative;
|
||||
overflow: auto;
|
||||
|
||||
adf-permission-list {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
.mat-dialog-actions {
|
||||
flex: 0 0 auto;
|
||||
|
||||
padding: 8px 8px 24px 8px;
|
||||
display: -webkit-box;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-box-pack: end;
|
||||
-ms-flex-pack: end;
|
||||
justify-content: flex-end;
|
||||
color: mat-color($foreground, text, 0.54);
|
||||
|
||||
button {
|
||||
text-transform: uppercase;
|
||||
font-weight: normal;
|
||||
}
|
||||
}
|
||||
adf-permission-list {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
.mat-dialog-actions {
|
||||
flex: 0 0 auto;
|
||||
|
||||
padding: 8px 8px 24px 8px;
|
||||
display: -webkit-box;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-box-pack: end;
|
||||
-ms-flex-pack: end;
|
||||
justify-content: flex-end;
|
||||
color: mat-color($foreground, text, 0.54);
|
||||
|
||||
button {
|
||||
text-transform: uppercase;
|
||||
font-weight: normal;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -24,7 +24,10 @@
|
||||
*/
|
||||
|
||||
import { Component, Input, OnInit, ViewChild } from '@angular/core';
|
||||
import { NodePermissionDialogService, PermissionListComponent } from '@alfresco/adf-content-services';
|
||||
import {
|
||||
NodePermissionDialogService,
|
||||
PermissionListComponent
|
||||
} from '@alfresco/adf-content-services';
|
||||
import { MinimalNodeEntryEntity } from 'alfresco-js-api';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { AppStore } from '../../store/states/app.state';
|
||||
@ -34,58 +37,60 @@ import { MatDialog } from '@angular/material';
|
||||
import { ContentApiService } from '../../services/content-api.service';
|
||||
|
||||
@Component({
|
||||
selector: 'aca-permissions-manager',
|
||||
templateUrl: './permissions-manager.component.html'
|
||||
selector: 'aca-permissions-manager',
|
||||
templateUrl: './permissions-manager.component.html'
|
||||
})
|
||||
export class PermissionsManagerComponent implements OnInit {
|
||||
@ViewChild('permissionList')
|
||||
permissionList: PermissionListComponent;
|
||||
@ViewChild('permissionList')
|
||||
permissionList: PermissionListComponent;
|
||||
|
||||
@Input()
|
||||
nodeId: string;
|
||||
@Input()
|
||||
nodeId: string;
|
||||
|
||||
toggleStatus = false;
|
||||
toggleStatus = false;
|
||||
|
||||
constructor(
|
||||
private store: Store<AppStore>,
|
||||
private dialog: MatDialog,
|
||||
private contentApi: ContentApiService,
|
||||
private nodePermissionDialogService: NodePermissionDialogService
|
||||
) {
|
||||
}
|
||||
constructor(
|
||||
private store: Store<AppStore>,
|
||||
private dialog: MatDialog,
|
||||
private contentApi: ContentApiService,
|
||||
private nodePermissionDialogService: NodePermissionDialogService
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.contentApi.getNodeInfo(this.nodeId, {include: ['permissions'] }).subscribe( (currentNode: MinimalNodeEntryEntity) => {
|
||||
this.toggleStatus = currentNode.permissions.isInheritanceEnabled;
|
||||
});
|
||||
}
|
||||
ngOnInit() {
|
||||
this.contentApi
|
||||
.getNodeInfo(this.nodeId, { include: ['permissions'] })
|
||||
.subscribe((currentNode: MinimalNodeEntryEntity) => {
|
||||
this.toggleStatus = currentNode.permissions.isInheritanceEnabled;
|
||||
});
|
||||
}
|
||||
|
||||
onError(errorMessage: string) {
|
||||
this.store.dispatch(new SnackbarErrorAction(errorMessage));
|
||||
}
|
||||
onError(errorMessage: string) {
|
||||
this.store.dispatch(new SnackbarErrorAction(errorMessage));
|
||||
}
|
||||
|
||||
onUpdate(event) {
|
||||
this.permissionList.reload();
|
||||
}
|
||||
onUpdate(event) {
|
||||
this.permissionList.reload();
|
||||
}
|
||||
|
||||
onUpdatedPermissions(node: MinimalNodeEntryEntity) {
|
||||
this.toggleStatus = node.permissions.isInheritanceEnabled;
|
||||
this.permissionList.reload();
|
||||
}
|
||||
onUpdatedPermissions(node: MinimalNodeEntryEntity) {
|
||||
this.toggleStatus = node.permissions.isInheritanceEnabled;
|
||||
this.permissionList.reload();
|
||||
}
|
||||
|
||||
openAddPermissionDialog(event: Event) {
|
||||
this.nodePermissionDialogService.updateNodePermissionByDialog(this.nodeId)
|
||||
.subscribe(() => {
|
||||
this.dialog.open(NodePermissionsDialogComponent, {
|
||||
data: { nodeId: this.nodeId },
|
||||
panelClass: 'aca-permissions-dialog-panel',
|
||||
width: '800px'
|
||||
}
|
||||
);
|
||||
},
|
||||
(error) => {
|
||||
this.store.dispatch(new SnackbarErrorAction(error));
|
||||
}
|
||||
);
|
||||
}
|
||||
openAddPermissionDialog(event: Event) {
|
||||
this.nodePermissionDialogService
|
||||
.updateNodePermissionByDialog(this.nodeId)
|
||||
.subscribe(
|
||||
() => {
|
||||
this.dialog.open(NodePermissionsDialogComponent, {
|
||||
data: { nodeId: this.nodeId },
|
||||
panelClass: 'aca-permissions-dialog-panel',
|
||||
width: '800px'
|
||||
});
|
||||
},
|
||||
error => {
|
||||
this.store.dispatch(new SnackbarErrorAction(error));
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -24,82 +24,82 @@
|
||||
*/
|
||||
|
||||
import {
|
||||
Component,
|
||||
Input,
|
||||
ComponentRef,
|
||||
OnInit,
|
||||
ComponentFactoryResolver,
|
||||
ViewChild,
|
||||
ViewContainerRef,
|
||||
OnDestroy,
|
||||
OnChanges
|
||||
Component,
|
||||
Input,
|
||||
ComponentRef,
|
||||
OnInit,
|
||||
ComponentFactoryResolver,
|
||||
ViewChild,
|
||||
ViewContainerRef,
|
||||
OnDestroy,
|
||||
OnChanges
|
||||
} from '@angular/core';
|
||||
import { AppExtensionService } from '../../extensions/extension.service';
|
||||
import { MinimalNodeEntryEntity } from 'alfresco-js-api';
|
||||
|
||||
@Component({
|
||||
selector: 'app-preview-extension',
|
||||
template: `<div #content></div>`
|
||||
selector: 'app-preview-extension',
|
||||
template: `<div #content></div>`
|
||||
})
|
||||
export class PreviewExtensionComponent implements OnInit, OnChanges, OnDestroy {
|
||||
@ViewChild('content', { read: ViewContainerRef })
|
||||
content: ViewContainerRef;
|
||||
@ViewChild('content', { read: ViewContainerRef })
|
||||
content: ViewContainerRef;
|
||||
|
||||
@Input()
|
||||
id: string;
|
||||
@Input()
|
||||
id: string;
|
||||
|
||||
@Input()
|
||||
url: string;
|
||||
@Input()
|
||||
url: string;
|
||||
|
||||
@Input()
|
||||
extension: string;
|
||||
@Input()
|
||||
extension: string;
|
||||
|
||||
@Input()
|
||||
node: MinimalNodeEntryEntity;
|
||||
@Input()
|
||||
node: MinimalNodeEntryEntity;
|
||||
|
||||
private componentRef: ComponentRef<any>;
|
||||
private componentRef: ComponentRef<any>;
|
||||
|
||||
constructor(
|
||||
private extensions: AppExtensionService,
|
||||
private componentFactoryResolver: ComponentFactoryResolver
|
||||
) {}
|
||||
constructor(
|
||||
private extensions: AppExtensionService,
|
||||
private componentFactoryResolver: ComponentFactoryResolver
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
if (!this.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
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.updateInstance();
|
||||
}
|
||||
}
|
||||
ngOnInit() {
|
||||
if (!this.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
ngOnChanges() {
|
||||
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.updateInstance();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
if (this.componentRef) {
|
||||
this.componentRef.destroy();
|
||||
this.componentRef = null;
|
||||
}
|
||||
ngOnChanges() {
|
||||
this.updateInstance();
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
if (this.componentRef) {
|
||||
this.componentRef.destroy();
|
||||
this.componentRef = null;
|
||||
}
|
||||
}
|
||||
|
||||
private updateInstance() {
|
||||
if (this.componentRef && this.componentRef.instance) {
|
||||
const instance = this.componentRef.instance;
|
||||
private updateInstance() {
|
||||
if (this.componentRef && this.componentRef.instance) {
|
||||
const instance = this.componentRef.instance;
|
||||
|
||||
instance.node = this.node;
|
||||
instance.url = this.url;
|
||||
instance.extension = this.extension;
|
||||
}
|
||||
instance.node = this.node;
|
||||
instance.url = this.url;
|
||||
instance.extension = this.extension;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
.app-preview {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -24,7 +24,14 @@
|
||||
*/
|
||||
|
||||
import { Component, OnInit, ViewEncapsulation } from '@angular/core';
|
||||
import { ActivatedRoute, Router, UrlTree, UrlSegmentGroup, UrlSegment, PRIMARY_OUTLET } from '@angular/router';
|
||||
import {
|
||||
ActivatedRoute,
|
||||
Router,
|
||||
UrlTree,
|
||||
UrlSegmentGroup,
|
||||
UrlSegment,
|
||||
PRIMARY_OUTLET
|
||||
} from '@angular/router';
|
||||
import { UserPreferencesService, ObjectUtils } from '@alfresco/adf-core';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { AppStore } from '../../store/states/app.state';
|
||||
@ -37,324 +44,358 @@ import { ContentActionRef, ViewerExtensionRef } from '@alfresco/adf-extensions';
|
||||
import { ViewUtilService } from './view-util.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-preview',
|
||||
templateUrl: 'preview.component.html',
|
||||
styleUrls: ['preview.component.scss'],
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
host: { 'class': 'app-preview' }
|
||||
selector: 'app-preview',
|
||||
templateUrl: 'preview.component.html',
|
||||
styleUrls: ['preview.component.scss'],
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
host: { class: 'app-preview' }
|
||||
})
|
||||
export class PreviewComponent extends PageComponent implements OnInit {
|
||||
previewLocation: string = null;
|
||||
routesSkipNavigation = ['shared', 'recent-files', 'favorites'];
|
||||
navigateSource: string = null;
|
||||
navigationSources = [
|
||||
'favorites',
|
||||
'libraries',
|
||||
'personal-files',
|
||||
'recent-files',
|
||||
'shared'
|
||||
];
|
||||
folderId: string = null;
|
||||
nodeId: string = null;
|
||||
previousNodeId: string;
|
||||
nextNodeId: string;
|
||||
navigateMultiple = false;
|
||||
openWith: Array<ContentActionRef> = [];
|
||||
contentExtensions: Array<ViewerExtensionRef> = [];
|
||||
|
||||
previewLocation: string = null;
|
||||
routesSkipNavigation = [ 'shared', 'recent-files', 'favorites' ];
|
||||
navigateSource: string = null;
|
||||
navigationSources = ['favorites', 'libraries', 'personal-files', 'recent-files', 'shared'];
|
||||
folderId: string = null;
|
||||
nodeId: string = null;
|
||||
previousNodeId: string;
|
||||
nextNodeId: string;
|
||||
navigateMultiple = false;
|
||||
openWith: Array<ContentActionRef> = [];
|
||||
contentExtensions: Array<ViewerExtensionRef> = [];
|
||||
constructor(
|
||||
private contentApi: ContentApiService,
|
||||
private preferences: UserPreferencesService,
|
||||
private route: ActivatedRoute,
|
||||
private router: Router,
|
||||
private viewUtils: ViewUtilService,
|
||||
store: Store<AppStore>,
|
||||
extensions: AppExtensionService,
|
||||
content: ContentManagementService
|
||||
) {
|
||||
super(store, extensions, content);
|
||||
}
|
||||
|
||||
constructor(
|
||||
private contentApi: ContentApiService,
|
||||
private preferences: UserPreferencesService,
|
||||
private route: ActivatedRoute,
|
||||
private router: Router,
|
||||
private viewUtils: ViewUtilService,
|
||||
store: Store<AppStore>,
|
||||
extensions: AppExtensionService,
|
||||
content: ContentManagementService) {
|
||||
super(store, extensions, content);
|
||||
ngOnInit() {
|
||||
super.ngOnInit();
|
||||
|
||||
this.previewLocation = this.router.url
|
||||
.substr(0, this.router.url.indexOf('/', 1))
|
||||
.replace(/\//g, '');
|
||||
|
||||
const routeData = this.route.snapshot.data;
|
||||
|
||||
if (routeData.navigateMultiple) {
|
||||
this.navigateMultiple = true;
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
super.ngOnInit();
|
||||
|
||||
this.previewLocation = this.router.url
|
||||
.substr(0, this.router.url.indexOf('/', 1))
|
||||
.replace(/\//g, '');
|
||||
|
||||
const routeData = this.route.snapshot.data;
|
||||
|
||||
if (routeData.navigateMultiple) {
|
||||
this.navigateMultiple = true;
|
||||
}
|
||||
|
||||
if (routeData.navigateSource) {
|
||||
const source = routeData.navigateSource.toLowerCase();
|
||||
if (this.navigationSources.includes(source)) {
|
||||
this.navigateSource = routeData.navigateSource;
|
||||
}
|
||||
}
|
||||
|
||||
this.route.params.subscribe(params => {
|
||||
this.folderId = params.folderId;
|
||||
const id = params.nodeId;
|
||||
if (id) {
|
||||
this.displayNode(id);
|
||||
}
|
||||
});
|
||||
|
||||
this.openWith = this.extensions.openWithActions;
|
||||
this.contentExtensions = this.extensions.viewerContentExtensions;
|
||||
if (routeData.navigateSource) {
|
||||
const source = routeData.navigateSource.toLowerCase();
|
||||
if (this.navigationSources.includes(source)) {
|
||||
this.navigateSource = routeData.navigateSource;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the particular node into the Viewer
|
||||
* @param id Unique identifier for the Node to display
|
||||
*/
|
||||
async displayNode(id: string) {
|
||||
if (id) {
|
||||
try {
|
||||
this.node = await this.contentApi.getNodeInfo(id).toPromise();
|
||||
this.store.dispatch(new SetSelectedNodesAction([{ entry: this.node }]));
|
||||
this.route.params.subscribe(params => {
|
||||
this.folderId = params.folderId;
|
||||
const id = params.nodeId;
|
||||
if (id) {
|
||||
this.displayNode(id);
|
||||
}
|
||||
});
|
||||
|
||||
if (this.node && this.node.isFile) {
|
||||
const nearest = await this.getNearestNodes(this.node.id, this.node.parentId);
|
||||
this.openWith = this.extensions.openWithActions;
|
||||
this.contentExtensions = this.extensions.viewerContentExtensions;
|
||||
}
|
||||
|
||||
this.previousNodeId = nearest.left;
|
||||
this.nextNodeId = nearest.right;
|
||||
this.nodeId = this.node.id;
|
||||
return;
|
||||
}
|
||||
this.router.navigate([this.previewLocation, id]);
|
||||
} catch (err) {
|
||||
if (!err || err.status !== 401) {
|
||||
this.router.navigate([this.previewLocation, id]);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Loads the particular node into the Viewer
|
||||
* @param id Unique identifier for the Node to display
|
||||
*/
|
||||
async displayNode(id: string) {
|
||||
if (id) {
|
||||
try {
|
||||
this.node = await this.contentApi.getNodeInfo(id).toPromise();
|
||||
this.store.dispatch(new SetSelectedNodesAction([{ entry: this.node }]));
|
||||
|
||||
if (this.node && this.node.isFile) {
|
||||
const nearest = await this.getNearestNodes(
|
||||
this.node.id,
|
||||
this.node.parentId
|
||||
);
|
||||
|
||||
this.previousNodeId = nearest.left;
|
||||
this.nextNodeId = nearest.right;
|
||||
this.nodeId = this.node.id;
|
||||
return;
|
||||
}
|
||||
this.router.navigate([this.previewLocation, id]);
|
||||
} catch (err) {
|
||||
if (!err || err.status !== 401) {
|
||||
this.router.navigate([this.previewLocation, id]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the visibility change of the Viewer component.
|
||||
* @param isVisible Indicator whether Viewer is visible or hidden.
|
||||
*/
|
||||
onVisibilityChanged(isVisible: boolean): void {
|
||||
const shouldSkipNavigation = this.routesSkipNavigation.includes(
|
||||
this.previewLocation
|
||||
);
|
||||
|
||||
if (!isVisible) {
|
||||
const route = this.getNavigationCommands(this.previewLocation);
|
||||
|
||||
if (!shouldSkipNavigation && this.folderId) {
|
||||
route.push(this.folderId);
|
||||
}
|
||||
|
||||
this.router.navigate(route);
|
||||
}
|
||||
}
|
||||
|
||||
/** Handles navigation to a previous document */
|
||||
onNavigateBefore(): void {
|
||||
if (this.previousNodeId) {
|
||||
this.router.navigate(
|
||||
this.getPreviewPath(this.folderId, this.previousNodeId)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/** Handles navigation to a next document */
|
||||
onNavigateNext(): void {
|
||||
if (this.nextNodeId) {
|
||||
this.router.navigate(this.getPreviewPath(this.folderId, this.nextNodeId));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a node preview route based on folder and node IDs.
|
||||
* @param folderId Folder ID
|
||||
* @param nodeId Node ID
|
||||
*/
|
||||
getPreviewPath(folderId: string, nodeId: string): any[] {
|
||||
const route = [this.previewLocation];
|
||||
|
||||
if (folderId) {
|
||||
route.push(folderId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the visibility change of the Viewer component.
|
||||
* @param isVisible Indicator whether Viewer is visible or hidden.
|
||||
*/
|
||||
onVisibilityChanged(isVisible: boolean): void {
|
||||
const shouldSkipNavigation = this.routesSkipNavigation.includes(this.previewLocation);
|
||||
|
||||
if (!isVisible) {
|
||||
const route = this.getNavigationCommands(this.previewLocation);
|
||||
|
||||
if ( !shouldSkipNavigation && this.folderId ) {
|
||||
route.push(this.folderId);
|
||||
}
|
||||
|
||||
this.router.navigate(route);
|
||||
}
|
||||
if (nodeId) {
|
||||
route.push('preview', nodeId);
|
||||
}
|
||||
|
||||
/** Handles navigation to a previous document */
|
||||
onNavigateBefore(): void {
|
||||
if (this.previousNodeId) {
|
||||
this.router.navigate(
|
||||
this.getPreviewPath(this.folderId, this.previousNodeId)
|
||||
);
|
||||
}
|
||||
}
|
||||
return route;
|
||||
}
|
||||
|
||||
/** Handles navigation to a next document */
|
||||
onNavigateNext(): void {
|
||||
if (this.nextNodeId) {
|
||||
this.router.navigate(
|
||||
this.getPreviewPath(this.folderId, this.nextNodeId)
|
||||
);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Retrieves nearest node information for the given node and folder.
|
||||
* @param nodeId Unique identifier of the document node
|
||||
* @param folderId Unique identifier of the containing folder node.
|
||||
*/
|
||||
async getNearestNodes(
|
||||
nodeId: string,
|
||||
folderId: string
|
||||
): Promise<{ left: string; right: string }> {
|
||||
const empty = {
|
||||
left: null,
|
||||
right: null
|
||||
};
|
||||
|
||||
/**
|
||||
* Generates a node preview route based on folder and node IDs.
|
||||
* @param folderId Folder ID
|
||||
* @param nodeId Node ID
|
||||
*/
|
||||
getPreviewPath(folderId: string, nodeId: string): any[] {
|
||||
const route = [this.previewLocation];
|
||||
if (nodeId && folderId) {
|
||||
try {
|
||||
const ids = await this.getFileIds(this.navigateSource, folderId);
|
||||
const idx = ids.indexOf(nodeId);
|
||||
|
||||
if (folderId) {
|
||||
route.push(folderId);
|
||||
}
|
||||
|
||||
if (nodeId) {
|
||||
route.push('preview', nodeId);
|
||||
}
|
||||
|
||||
return route;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieves nearest node information for the given node and folder.
|
||||
* @param nodeId Unique identifier of the document node
|
||||
* @param folderId Unique identifier of the containing folder node.
|
||||
*/
|
||||
async getNearestNodes(nodeId: string, folderId: string): Promise<{ left: string, right: string }> {
|
||||
const empty = {
|
||||
left: null,
|
||||
right: null
|
||||
};
|
||||
|
||||
if (nodeId && folderId) {
|
||||
try {
|
||||
const ids = await this.getFileIds(this.navigateSource, folderId);
|
||||
const idx = ids.indexOf(nodeId);
|
||||
|
||||
if (idx >= 0) {
|
||||
return {
|
||||
left: ids[idx - 1] || null,
|
||||
right: ids[idx + 1] || null
|
||||
};
|
||||
} else {
|
||||
return empty;
|
||||
}
|
||||
} catch {
|
||||
return empty;
|
||||
}
|
||||
if (idx >= 0) {
|
||||
return {
|
||||
left: ids[idx - 1] || null,
|
||||
right: ids[idx + 1] || null
|
||||
};
|
||||
} else {
|
||||
return empty;
|
||||
return empty;
|
||||
}
|
||||
} catch {
|
||||
return empty;
|
||||
}
|
||||
} else {
|
||||
return empty;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a list of node identifiers for the folder and data source.
|
||||
* @param source Data source name. Allowed values are: personal-files, libraries, favorites, shared, recent-files.
|
||||
* @param folderId Containing folder node identifier for 'personal-files' and 'libraries' sources.
|
||||
*/
|
||||
async getFileIds(source: string, folderId?: string): Promise<string[]> {
|
||||
if ((source === 'personal-files' || source === 'libraries') && folderId) {
|
||||
const sortKey =
|
||||
this.preferences.get('personal-files.sorting.key') || 'modifiedAt';
|
||||
const sortDirection =
|
||||
this.preferences.get('personal-files.sorting.direction') || 'desc';
|
||||
const nodes = await this.contentApi
|
||||
.getNodeChildren(folderId, {
|
||||
// orderBy: `${sortKey} ${sortDirection}`,
|
||||
fields: ['id', this.getRootField(sortKey)],
|
||||
where: '(isFile=true)'
|
||||
})
|
||||
.toPromise();
|
||||
|
||||
const entries = nodes.list.entries.map(obj => obj.entry);
|
||||
this.sort(entries, sortKey, sortDirection);
|
||||
|
||||
return entries.map(obj => obj.id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a list of node identifiers for the folder and data source.
|
||||
* @param source Data source name. Allowed values are: personal-files, libraries, favorites, shared, recent-files.
|
||||
* @param folderId Containing folder node identifier for 'personal-files' and 'libraries' sources.
|
||||
*/
|
||||
async getFileIds(source: string, folderId?: string): Promise<string[]> {
|
||||
if ((source === 'personal-files' || source === 'libraries') && folderId) {
|
||||
const sortKey = this.preferences.get('personal-files.sorting.key') || 'modifiedAt';
|
||||
const sortDirection = this.preferences.get('personal-files.sorting.direction') || 'desc';
|
||||
const nodes = await this.contentApi.getNodeChildren(folderId, {
|
||||
// orderBy: `${sortKey} ${sortDirection}`,
|
||||
fields: ['id', this.getRootField(sortKey)],
|
||||
where: '(isFile=true)'
|
||||
}).toPromise();
|
||||
if (source === 'favorites') {
|
||||
const nodes = await this.contentApi
|
||||
.getFavorites('-me-', {
|
||||
where: '(EXISTS(target/file))',
|
||||
fields: ['target']
|
||||
})
|
||||
.toPromise();
|
||||
|
||||
const entries = nodes.list.entries.map(obj => obj.entry);
|
||||
this.sort(entries, sortKey, sortDirection);
|
||||
const sortKey =
|
||||
this.preferences.get('favorites.sorting.key') || 'modifiedAt';
|
||||
const sortDirection =
|
||||
this.preferences.get('favorites.sorting.direction') || 'desc';
|
||||
const files = nodes.list.entries.map(obj => obj.entry.target.file);
|
||||
this.sort(files, sortKey, sortDirection);
|
||||
|
||||
return entries.map(obj => obj.id);
|
||||
}
|
||||
|
||||
if (source === 'favorites') {
|
||||
const nodes = await this.contentApi.getFavorites('-me-', {
|
||||
where: '(EXISTS(target/file))',
|
||||
fields: ['target']
|
||||
}).toPromise();
|
||||
|
||||
const sortKey = this.preferences.get('favorites.sorting.key') || 'modifiedAt';
|
||||
const sortDirection = this.preferences.get('favorites.sorting.direction') || 'desc';
|
||||
const files = nodes.list.entries.map(obj => obj.entry.target.file);
|
||||
this.sort(files, sortKey, sortDirection);
|
||||
|
||||
return files.map(f => f.id);
|
||||
}
|
||||
|
||||
if (source === 'shared') {
|
||||
const sortingKey = this.preferences.get('shared.sorting.key') || 'modifiedAt';
|
||||
const sortingDirection = this.preferences.get('shared.sorting.direction') || 'desc';
|
||||
|
||||
const nodes = await this.contentApi.findSharedLinks({
|
||||
fields: ['nodeId', this.getRootField(sortingKey)]
|
||||
}).toPromise();
|
||||
|
||||
const entries = nodes.list.entries.map(obj => obj.entry);
|
||||
this.sort(entries, sortingKey, sortingDirection);
|
||||
|
||||
return entries.map(obj => obj.nodeId);
|
||||
}
|
||||
|
||||
if (source === 'recent-files') {
|
||||
const person = await this.contentApi.getPerson('-me-').toPromise();
|
||||
const username = person.entry.id;
|
||||
const sortingKey = this.preferences.get('recent-files.sorting.key') || 'modifiedAt';
|
||||
const sortingDirection = this.preferences.get('recent-files.sorting.direction') || 'desc';
|
||||
|
||||
const nodes = await this.contentApi.search({
|
||||
query: {
|
||||
query: '*',
|
||||
language: 'afts'
|
||||
},
|
||||
filterQueries: [
|
||||
{ query: `cm:modified:[NOW/DAY-30DAYS TO NOW/DAY+1DAY]` },
|
||||
{ query: `cm:modifier:${username} OR cm:creator:${username}` },
|
||||
{ query: `TYPE:"content" AND -TYPE:"app:filelink" AND -TYPE:"fm:post"` }
|
||||
],
|
||||
fields: ['id', this.getRootField(sortingKey)],
|
||||
sort: [{
|
||||
type: 'FIELD',
|
||||
field: 'cm:modified',
|
||||
ascending: false
|
||||
}]
|
||||
}).toPromise();
|
||||
|
||||
const entries = nodes.list.entries.map(obj => obj.entry);
|
||||
this.sort(entries, sortingKey, sortingDirection);
|
||||
|
||||
return entries.map(obj => obj.id);
|
||||
}
|
||||
|
||||
return [];
|
||||
return files.map(f => f.id);
|
||||
}
|
||||
|
||||
private sort(items: any[], key: string, direction: string) {
|
||||
const options: Intl.CollatorOptions = {};
|
||||
if (source === 'shared') {
|
||||
const sortingKey =
|
||||
this.preferences.get('shared.sorting.key') || 'modifiedAt';
|
||||
const sortingDirection =
|
||||
this.preferences.get('shared.sorting.direction') || 'desc';
|
||||
|
||||
if (key.includes('sizeInBytes') || key === 'name') {
|
||||
options.numeric = true;
|
||||
}
|
||||
const nodes = await this.contentApi
|
||||
.findSharedLinks({
|
||||
fields: ['nodeId', this.getRootField(sortingKey)]
|
||||
})
|
||||
.toPromise();
|
||||
|
||||
items.sort((a: any, b: any) => {
|
||||
let left = ObjectUtils.getValue(a, key);
|
||||
if (left) {
|
||||
left = (left instanceof Date) ? left.valueOf().toString() : left.toString();
|
||||
} else {
|
||||
left = '';
|
||||
const entries = nodes.list.entries.map(obj => obj.entry);
|
||||
this.sort(entries, sortingKey, sortingDirection);
|
||||
|
||||
return entries.map(obj => obj.nodeId);
|
||||
}
|
||||
|
||||
if (source === 'recent-files') {
|
||||
const person = await this.contentApi.getPerson('-me-').toPromise();
|
||||
const username = person.entry.id;
|
||||
const sortingKey =
|
||||
this.preferences.get('recent-files.sorting.key') || 'modifiedAt';
|
||||
const sortingDirection =
|
||||
this.preferences.get('recent-files.sorting.direction') || 'desc';
|
||||
|
||||
const nodes = await this.contentApi
|
||||
.search({
|
||||
query: {
|
||||
query: '*',
|
||||
language: 'afts'
|
||||
},
|
||||
filterQueries: [
|
||||
{ query: `cm:modified:[NOW/DAY-30DAYS TO NOW/DAY+1DAY]` },
|
||||
{ query: `cm:modifier:${username} OR cm:creator:${username}` },
|
||||
{
|
||||
query: `TYPE:"content" AND -TYPE:"app:filelink" AND -TYPE:"fm:post"`
|
||||
}
|
||||
|
||||
let right = ObjectUtils.getValue(b, key);
|
||||
if (right) {
|
||||
right = (right instanceof Date) ? right.valueOf().toString() : right.toString();
|
||||
} else {
|
||||
right = '';
|
||||
],
|
||||
fields: ['id', this.getRootField(sortingKey)],
|
||||
sort: [
|
||||
{
|
||||
type: 'FIELD',
|
||||
field: 'cm:modified',
|
||||
ascending: false
|
||||
}
|
||||
]
|
||||
})
|
||||
.toPromise();
|
||||
|
||||
return direction === 'asc'
|
||||
? left.localeCompare(right, undefined, options)
|
||||
: right.localeCompare(left, undefined, options);
|
||||
});
|
||||
const entries = nodes.list.entries.map(obj => obj.entry);
|
||||
this.sort(entries, sortingKey, sortingDirection);
|
||||
|
||||
return entries.map(obj => obj.id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the root field name from the property path.
|
||||
* Example: 'property1.some.child.property' => 'property1'
|
||||
* @param path Property path
|
||||
*/
|
||||
getRootField(path: string) {
|
||||
if (path) {
|
||||
return path.split('.')[0];
|
||||
}
|
||||
return path;
|
||||
return [];
|
||||
}
|
||||
|
||||
private sort(items: any[], key: string, direction: string) {
|
||||
const options: Intl.CollatorOptions = {};
|
||||
|
||||
if (key.includes('sizeInBytes') || key === 'name') {
|
||||
options.numeric = true;
|
||||
}
|
||||
|
||||
printFile() {
|
||||
this.viewUtils.printFileGeneric(this.nodeId, this.node.content.mimeType);
|
||||
items.sort((a: any, b: any) => {
|
||||
let left = ObjectUtils.getValue(a, key);
|
||||
if (left) {
|
||||
left =
|
||||
left instanceof Date ? left.valueOf().toString() : left.toString();
|
||||
} else {
|
||||
left = '';
|
||||
}
|
||||
|
||||
let right = ObjectUtils.getValue(b, key);
|
||||
if (right) {
|
||||
right =
|
||||
right instanceof Date ? right.valueOf().toString() : right.toString();
|
||||
} else {
|
||||
right = '';
|
||||
}
|
||||
|
||||
return direction === 'asc'
|
||||
? left.localeCompare(right, undefined, options)
|
||||
: right.localeCompare(left, undefined, options);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the root field name from the property path.
|
||||
* Example: 'property1.some.child.property' => 'property1'
|
||||
* @param path Property path
|
||||
*/
|
||||
getRootField(path: string) {
|
||||
if (path) {
|
||||
return path.split('.')[0];
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
printFile() {
|
||||
this.viewUtils.printFileGeneric(this.nodeId, this.node.content.mimeType);
|
||||
}
|
||||
|
||||
private getNavigationCommands(url: string): any[] {
|
||||
const urlTree: UrlTree = this.router.parseUrl(url);
|
||||
const urlSegmentGroup: UrlSegmentGroup =
|
||||
urlTree.root.children[PRIMARY_OUTLET];
|
||||
|
||||
if (!urlSegmentGroup) {
|
||||
return [url];
|
||||
}
|
||||
|
||||
private getNavigationCommands(url: string): any[] {
|
||||
const urlTree: UrlTree = this.router.parseUrl(url);
|
||||
const urlSegmentGroup: UrlSegmentGroup = urlTree.root.children[PRIMARY_OUTLET];
|
||||
const urlSegments: UrlSegment[] = urlSegmentGroup.segments;
|
||||
|
||||
if (!urlSegmentGroup) {
|
||||
return [url];
|
||||
}
|
||||
|
||||
const urlSegments: UrlSegment[] = urlSegmentGroup.segments;
|
||||
|
||||
return urlSegments.reduce(function(acc, item) {
|
||||
acc.push(item.path, item.parameters);
|
||||
return acc;
|
||||
}, []);
|
||||
}
|
||||
return urlSegments.reduce(function(acc, item) {
|
||||
acc.push(item.path, item.parameters);
|
||||
return acc;
|
||||
}, []);
|
||||
}
|
||||
}
|
||||
|
@ -37,32 +37,25 @@ import { PreviewExtensionComponent } from './preview-extension.component';
|
||||
import { AppToolbarModule } from '../toolbar/toolbar.module';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: '',
|
||||
component: PreviewComponent
|
||||
}
|
||||
{
|
||||
path: '',
|
||||
component: PreviewComponent
|
||||
}
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
CommonModule,
|
||||
RouterModule.forChild(routes),
|
||||
CoreModule.forChild(),
|
||||
ContentDirectiveModule,
|
||||
DirectivesModule,
|
||||
AppInfoDrawerModule,
|
||||
CoreExtensionsModule.forChild(),
|
||||
AppToolbarModule
|
||||
],
|
||||
declarations: [
|
||||
PreviewComponent,
|
||||
PreviewExtensionComponent
|
||||
],
|
||||
providers: [
|
||||
ViewUtilService
|
||||
],
|
||||
exports: [
|
||||
PreviewComponent
|
||||
]
|
||||
imports: [
|
||||
CommonModule,
|
||||
RouterModule.forChild(routes),
|
||||
CoreModule.forChild(),
|
||||
ContentDirectiveModule,
|
||||
DirectivesModule,
|
||||
AppInfoDrawerModule,
|
||||
CoreExtensionsModule.forChild(),
|
||||
AppToolbarModule
|
||||
],
|
||||
declarations: [PreviewComponent, PreviewExtensionComponent],
|
||||
providers: [ViewUtilService],
|
||||
exports: [PreviewComponent]
|
||||
})
|
||||
export class PreviewModule {}
|
||||
|
@ -1,183 +1,228 @@
|
||||
import {Injectable} from '@angular/core';
|
||||
import {AlfrescoApiService, LogService} from '@alfresco/adf-core';
|
||||
import {RenditionEntry} from 'alfresco-js-api';
|
||||
import {ContentApiService} from './../../services/content-api.service';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { AlfrescoApiService, LogService } from '@alfresco/adf-core';
|
||||
import { RenditionEntry } from 'alfresco-js-api';
|
||||
import { ContentApiService } from './../../services/content-api.service';
|
||||
|
||||
@Injectable()
|
||||
export class ViewUtilService {
|
||||
static TARGET = '_new';
|
||||
static TARGET = '_new';
|
||||
|
||||
/**
|
||||
* Content groups based on categorization of files that can be viewed in the web browser. This
|
||||
* implementation or grouping is tied to the definition the ng component: ViewerComponent
|
||||
*/
|
||||
public static ContentGroup = {
|
||||
IMAGE: 'image',
|
||||
MEDIA: 'media',
|
||||
PDF: 'pdf',
|
||||
TEXT: 'text'
|
||||
};
|
||||
/**
|
||||
* Content groups based on categorization of files that can be viewed in the web browser. This
|
||||
* implementation or grouping is tied to the definition the ng component: ViewerComponent
|
||||
*/
|
||||
public static ContentGroup = {
|
||||
IMAGE: 'image',
|
||||
MEDIA: 'media',
|
||||
PDF: 'pdf',
|
||||
TEXT: 'text'
|
||||
};
|
||||
|
||||
/**
|
||||
* Based on ViewerComponent Implementation, this value is used to determine how many times we try
|
||||
* to get the rendition of a file for preview, or printing.
|
||||
* @type {number}
|
||||
*/
|
||||
maxRetries = 5;
|
||||
/**
|
||||
* Based on ViewerComponent Implementation, this value is used to determine how many times we try
|
||||
* to get the rendition of a file for preview, or printing.
|
||||
* @type {number}
|
||||
*/
|
||||
maxRetries = 5;
|
||||
|
||||
/**
|
||||
* Mime-type grouping based on the ViewerComponent.
|
||||
*/
|
||||
private mimeTypes = {
|
||||
text: ['text/plain', 'text/csv', 'text/xml', 'text/html', 'application/x-javascript'],
|
||||
pdf: ['application/pdf'],
|
||||
image: ['image/png', 'image/jpeg', 'image/gif', 'image/bmp', 'image/svg+xml'],
|
||||
media: ['video/mp4', 'video/webm', 'video/ogg', 'audio/mpeg', 'audio/ogg', 'audio/wav']
|
||||
};
|
||||
/**
|
||||
* Mime-type grouping based on the ViewerComponent.
|
||||
*/
|
||||
private mimeTypes = {
|
||||
text: [
|
||||
'text/plain',
|
||||
'text/csv',
|
||||
'text/xml',
|
||||
'text/html',
|
||||
'application/x-javascript'
|
||||
],
|
||||
pdf: ['application/pdf'],
|
||||
image: [
|
||||
'image/png',
|
||||
'image/jpeg',
|
||||
'image/gif',
|
||||
'image/bmp',
|
||||
'image/svg+xml'
|
||||
],
|
||||
media: [
|
||||
'video/mp4',
|
||||
'video/webm',
|
||||
'video/ogg',
|
||||
'audio/mpeg',
|
||||
'audio/ogg',
|
||||
'audio/wav'
|
||||
]
|
||||
};
|
||||
|
||||
constructor(private apiService: AlfrescoApiService,
|
||||
private contentApi: ContentApiService,
|
||||
private logService: LogService) {
|
||||
constructor(
|
||||
private apiService: AlfrescoApiService,
|
||||
private contentApi: ContentApiService,
|
||||
private logService: LogService
|
||||
) {}
|
||||
|
||||
/**
|
||||
* This method takes a url to trigger the print dialog against, and the type of artifact that it
|
||||
* is.
|
||||
* This URL should be one that can be rendered in the browser, for example PDF, Image, or Text
|
||||
* @param {string} url
|
||||
* @param {string} type
|
||||
*/
|
||||
public printFile(url: string, type: string) {
|
||||
const pwa = window.open(url, ViewUtilService.TARGET);
|
||||
// Because of the way chrome focus and close image window vs. pdf preview window
|
||||
if (type === ViewUtilService.ContentGroup.IMAGE) {
|
||||
pwa.onfocus = () => {
|
||||
setTimeout(() => {
|
||||
pwa.close();
|
||||
}, 500);
|
||||
};
|
||||
pwa.onload = () => {
|
||||
pwa.print();
|
||||
};
|
||||
} else {
|
||||
pwa.onload = () => {
|
||||
pwa.print();
|
||||
pwa.onfocus = () => {
|
||||
setTimeout(() => {
|
||||
pwa.close();
|
||||
}, 10);
|
||||
};
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method takes a url to trigger the print dialog against, and the type of artifact that it
|
||||
* is.
|
||||
* This URL should be one that can be rendered in the browser, for example PDF, Image, or Text
|
||||
* @param {string} url
|
||||
* @param {string} type
|
||||
*/
|
||||
public printFile(url: string, type: string) {
|
||||
const pwa = window.open(url, ViewUtilService.TARGET);
|
||||
// Because of the way chrome focus and close image window vs. pdf preview window
|
||||
if (type === ViewUtilService.ContentGroup.IMAGE) {
|
||||
pwa.onfocus = () => {
|
||||
setTimeout( () => {
|
||||
pwa.close();
|
||||
}, 500);
|
||||
};
|
||||
pwa.onload = () => {
|
||||
pwa.print();
|
||||
};
|
||||
} else {
|
||||
pwa.onload = () => {
|
||||
pwa.print();
|
||||
pwa.onfocus = () => {
|
||||
setTimeout( () => {
|
||||
pwa.close();
|
||||
}, 10);
|
||||
};
|
||||
};
|
||||
/**
|
||||
* Launch the File Print dialog from anywhere other than the preview service, which resolves the
|
||||
* rendition of the object that can be printed from a web browser.
|
||||
* These are: images, PDF files, or PDF rendition of files.
|
||||
* We also force PDF rendition for TEXT type objects, otherwise the default URL is to download.
|
||||
* TODO there are different TEXT type objects, (HTML, plaintext, xml, etc. we should determine how these are handled)
|
||||
* @param {string} objectId
|
||||
* @param {string} objectType
|
||||
*/
|
||||
public printFileGeneric(objectId: string, mimeType: string) {
|
||||
const nodeId = objectId;
|
||||
const type: string = this.getViewerTypeByMimeType(mimeType);
|
||||
|
||||
this.getRendition(nodeId, ViewUtilService.ContentGroup.PDF)
|
||||
.then(value => {
|
||||
const url: string = this.getRenditionUrl(
|
||||
nodeId,
|
||||
type,
|
||||
value ? true : false
|
||||
);
|
||||
const printType =
|
||||
type === ViewUtilService.ContentGroup.PDF ||
|
||||
type === ViewUtilService.ContentGroup.TEXT
|
||||
? ViewUtilService.ContentGroup.PDF
|
||||
: type;
|
||||
this.printFile(url, printType);
|
||||
})
|
||||
.catch(err => {
|
||||
this.logService.error('Error with Printing');
|
||||
this.logService.error(err);
|
||||
});
|
||||
}
|
||||
|
||||
public getRenditionUrl(
|
||||
nodeId: string,
|
||||
type: string,
|
||||
renditionExists: boolean
|
||||
): string {
|
||||
return renditionExists && type !== ViewUtilService.ContentGroup.IMAGE
|
||||
? this.apiService.contentApi.getRenditionUrl(
|
||||
nodeId,
|
||||
ViewUtilService.ContentGroup.PDF
|
||||
)
|
||||
: this.contentApi.getContentUrl(nodeId, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* From ViewerComponent
|
||||
* @param {string} nodeId
|
||||
* @param {string} renditionId
|
||||
* @param {number} retries
|
||||
* @returns {Promise<AlfrescoApi.RenditionEntry>}
|
||||
*/
|
||||
private async waitRendition(
|
||||
nodeId: string,
|
||||
renditionId: string,
|
||||
retries: number
|
||||
): Promise<RenditionEntry> {
|
||||
const rendition = await this.apiService.renditionsApi.getRendition(
|
||||
nodeId,
|
||||
renditionId
|
||||
);
|
||||
|
||||
if (this.maxRetries < retries) {
|
||||
const status = rendition.entry.status.toString();
|
||||
|
||||
if (status === 'CREATED') {
|
||||
return rendition;
|
||||
} else {
|
||||
retries += 1;
|
||||
await this.wait(1000);
|
||||
return await this.waitRendition(nodeId, renditionId, retries);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* From ViewerComponent
|
||||
* @param {string} mimeType
|
||||
* @returns {string}
|
||||
*/
|
||||
getViewerTypeByMimeType(mimeType: string) {
|
||||
if (mimeType) {
|
||||
mimeType = mimeType.toLowerCase();
|
||||
|
||||
const editorTypes = Object.keys(this.mimeTypes);
|
||||
for (const type of editorTypes) {
|
||||
if (this.mimeTypes[type].indexOf(mimeType) >= 0) {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
}
|
||||
return 'unknown';
|
||||
}
|
||||
|
||||
/**
|
||||
* Launch the File Print dialog from anywhere other than the preview service, which resolves the
|
||||
* rendition of the object that can be printed from a web browser.
|
||||
* These are: images, PDF files, or PDF rendition of files.
|
||||
* We also force PDF rendition for TEXT type objects, otherwise the default URL is to download.
|
||||
* TODO there are different TEXT type objects, (HTML, plaintext, xml, etc. we should determine how these are handled)
|
||||
* @param {string} objectId
|
||||
* @param {string} objectType
|
||||
*/
|
||||
public printFileGeneric(objectId: string, mimeType: string) {
|
||||
const nodeId = objectId;
|
||||
const type: string = this.getViewerTypeByMimeType(mimeType);
|
||||
/**
|
||||
* From ViewerComponent
|
||||
* @param {number} ms
|
||||
* @returns {Promise<any>}
|
||||
*/
|
||||
public wait(ms: number): Promise<any> {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
this.getRendition(nodeId, ViewUtilService.ContentGroup.PDF)
|
||||
.then(value => {
|
||||
const url: string = this.getRenditionUrl(nodeId, type, (value ? true : false));
|
||||
const printType = (type === ViewUtilService.ContentGroup.PDF
|
||||
|| type === ViewUtilService.ContentGroup.TEXT)
|
||||
? ViewUtilService.ContentGroup.PDF : type;
|
||||
this.printFile(url, printType);
|
||||
})
|
||||
.catch(err => {
|
||||
this.logService.error('Error with Printing');
|
||||
this.logService.error(err);
|
||||
});
|
||||
}
|
||||
/**
|
||||
* From ViewerComponent
|
||||
* @param {string} nodeId
|
||||
* @returns {string}
|
||||
*/
|
||||
public async getRendition(
|
||||
nodeId: string,
|
||||
renditionId: string
|
||||
): Promise<RenditionEntry> {
|
||||
const supported = await this.apiService.renditionsApi.getRenditions(nodeId);
|
||||
let rendition = supported.list.entries.find(
|
||||
obj => obj.entry.id.toLowerCase() === renditionId
|
||||
);
|
||||
|
||||
public getRenditionUrl(nodeId: string, type: string, renditionExists: boolean): string {
|
||||
return (renditionExists && type !== ViewUtilService.ContentGroup.IMAGE) ?
|
||||
this.apiService.contentApi.getRenditionUrl(nodeId, ViewUtilService.ContentGroup.PDF) :
|
||||
this.contentApi.getContentUrl(nodeId, false);
|
||||
}
|
||||
if (rendition) {
|
||||
const status = rendition.entry.status.toString();
|
||||
|
||||
/**
|
||||
* From ViewerComponent
|
||||
* @param {string} nodeId
|
||||
* @param {string} renditionId
|
||||
* @param {number} retries
|
||||
* @returns {Promise<AlfrescoApi.RenditionEntry>}
|
||||
*/
|
||||
private async waitRendition(nodeId: string, renditionId: string, retries: number): Promise<RenditionEntry> {
|
||||
const rendition = await this.apiService.renditionsApi.getRendition(nodeId, renditionId);
|
||||
|
||||
if (this.maxRetries < retries) {
|
||||
const status = rendition.entry.status.toString();
|
||||
|
||||
if (status === 'CREATED') {
|
||||
return rendition;
|
||||
} else {
|
||||
retries += 1;
|
||||
await this.wait(1000);
|
||||
return await this.waitRendition(nodeId, renditionId, retries);
|
||||
}
|
||||
if (status === 'NOT_CREATED') {
|
||||
try {
|
||||
await this.apiService.renditionsApi.createRendition(nodeId, {
|
||||
id: renditionId
|
||||
});
|
||||
rendition = await this.waitRendition(nodeId, renditionId, 0);
|
||||
} catch (err) {
|
||||
this.logService.error(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* From ViewerComponent
|
||||
* @param {string} mimeType
|
||||
* @returns {string}
|
||||
*/
|
||||
getViewerTypeByMimeType(mimeType: string) {
|
||||
if (mimeType) {
|
||||
mimeType = mimeType.toLowerCase();
|
||||
|
||||
const editorTypes = Object.keys(this.mimeTypes);
|
||||
for (const type of editorTypes) {
|
||||
if (this.mimeTypes[type].indexOf(mimeType) >= 0) {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
}
|
||||
return 'unknown';
|
||||
}
|
||||
|
||||
/**
|
||||
* From ViewerComponent
|
||||
* @param {number} ms
|
||||
* @returns {Promise<any>}
|
||||
*/
|
||||
public wait(ms: number): Promise<any> {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
/**
|
||||
* From ViewerComponent
|
||||
* @param {string} nodeId
|
||||
* @returns {string}
|
||||
*/
|
||||
public async getRendition(nodeId: string, renditionId: string): Promise<RenditionEntry> {
|
||||
const supported = await this.apiService.renditionsApi.getRenditions(nodeId);
|
||||
let rendition = supported.list.entries.find(obj => obj.entry.id.toLowerCase() === renditionId);
|
||||
|
||||
if (rendition) {
|
||||
const status = rendition.entry.status.toString();
|
||||
|
||||
if (status === 'NOT_CREATED') {
|
||||
try {
|
||||
await this.apiService.renditionsApi.createRendition(nodeId, {id: renditionId});
|
||||
rendition = await this.waitRendition(nodeId, renditionId, 0);
|
||||
} catch (err) {
|
||||
this.logService.error(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
return new Promise(resolve => resolve(rendition));
|
||||
}
|
||||
|
||||
return new Promise(resolve => resolve(rendition));
|
||||
}
|
||||
}
|
||||
|
@ -26,8 +26,12 @@
|
||||
import { TestBed, ComponentFixture } from '@angular/core/testing';
|
||||
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
||||
import {
|
||||
AlfrescoApiService,
|
||||
TimeAgoPipe, NodeNameTooltipPipe, NodeFavoriteDirective, DataTableComponent, AppConfigPipe
|
||||
AlfrescoApiService,
|
||||
TimeAgoPipe,
|
||||
NodeNameTooltipPipe,
|
||||
NodeFavoriteDirective,
|
||||
DataTableComponent,
|
||||
AppConfigPipe
|
||||
} from '@alfresco/adf-core';
|
||||
import { DocumentListComponent } from '@alfresco/adf-content-services';
|
||||
import { ContentManagementService } from '../../services/content-management.service';
|
||||
@ -37,91 +41,93 @@ import { AppTestingModule } from '../../testing/app-testing.module';
|
||||
import { ExperimentalDirective } from '../../directives/experimental.directive';
|
||||
|
||||
describe('RecentFilesComponent', () => {
|
||||
let fixture: ComponentFixture<RecentFilesComponent>;
|
||||
let component: RecentFilesComponent;
|
||||
let alfrescoApi: AlfrescoApiService;
|
||||
let contentService: ContentManagementService;
|
||||
let page;
|
||||
let fixture: ComponentFixture<RecentFilesComponent>;
|
||||
let component: RecentFilesComponent;
|
||||
let alfrescoApi: AlfrescoApiService;
|
||||
let contentService: ContentManagementService;
|
||||
let page;
|
||||
|
||||
beforeEach(() => {
|
||||
page = {
|
||||
list: {
|
||||
entries: [{ entry: { id: 1 } }, { entry: { id: 2 } }],
|
||||
pagination: { data: 'data' }
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [AppTestingModule],
|
||||
declarations: [
|
||||
DataTableComponent,
|
||||
TimeAgoPipe,
|
||||
NodeNameTooltipPipe,
|
||||
NodeFavoriteDirective,
|
||||
DocumentListComponent,
|
||||
RecentFilesComponent,
|
||||
AppConfigPipe,
|
||||
ExperimentalDirective
|
||||
],
|
||||
schemas: [NO_ERRORS_SCHEMA]
|
||||
});
|
||||
|
||||
fixture = TestBed.createComponent(RecentFilesComponent);
|
||||
component = fixture.componentInstance;
|
||||
|
||||
contentService = TestBed.get(ContentManagementService);
|
||||
alfrescoApi = TestBed.get(AlfrescoApiService);
|
||||
alfrescoApi.reset();
|
||||
|
||||
spyOn(alfrescoApi.peopleApi, 'getPerson').and.returnValue(
|
||||
Promise.resolve({
|
||||
entry: { id: 'personId' }
|
||||
})
|
||||
);
|
||||
|
||||
spyOn(alfrescoApi.searchApi, 'search').and.returnValue(
|
||||
Promise.resolve(page)
|
||||
);
|
||||
});
|
||||
|
||||
describe('OnInit()', () => {
|
||||
beforeEach(() => {
|
||||
page = {
|
||||
list: {
|
||||
entries: [ { entry: { id: 1 } }, { entry: { id: 2 } } ],
|
||||
pagination: { data: 'data'}
|
||||
}
|
||||
};
|
||||
spyOn(component, 'reload').and.stub();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
AppTestingModule
|
||||
],
|
||||
declarations: [
|
||||
DataTableComponent,
|
||||
TimeAgoPipe,
|
||||
NodeNameTooltipPipe,
|
||||
NodeFavoriteDirective,
|
||||
DocumentListComponent,
|
||||
RecentFilesComponent,
|
||||
AppConfigPipe,
|
||||
ExperimentalDirective
|
||||
],
|
||||
schemas: [ NO_ERRORS_SCHEMA ]
|
||||
});
|
||||
it('should reload nodes on onDeleteNode event', () => {
|
||||
fixture.detectChanges();
|
||||
|
||||
fixture = TestBed.createComponent(RecentFilesComponent);
|
||||
component = fixture.componentInstance;
|
||||
contentService.nodesDeleted.next();
|
||||
|
||||
contentService = TestBed.get(ContentManagementService);
|
||||
alfrescoApi = TestBed.get(AlfrescoApiService);
|
||||
alfrescoApi.reset();
|
||||
|
||||
spyOn(alfrescoApi.peopleApi, 'getPerson').and.returnValue(Promise.resolve({
|
||||
entry: { id: 'personId' }
|
||||
}));
|
||||
|
||||
spyOn(alfrescoApi.searchApi, 'search').and.returnValue(Promise.resolve(page));
|
||||
expect(component.reload).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
describe('OnInit()', () => {
|
||||
beforeEach(() => {
|
||||
spyOn(component, 'reload').and.stub();
|
||||
});
|
||||
it('should reload on onRestoreNode event', () => {
|
||||
fixture.detectChanges();
|
||||
|
||||
it('should reload nodes on onDeleteNode event', () => {
|
||||
fixture.detectChanges();
|
||||
contentService.nodesRestored.next();
|
||||
|
||||
contentService.nodesDeleted.next();
|
||||
|
||||
expect(component.reload).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should reload on onRestoreNode event', () => {
|
||||
fixture.detectChanges();
|
||||
|
||||
contentService.nodesRestored.next();
|
||||
|
||||
expect(component.reload).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should reload on move node event', () => {
|
||||
fixture.detectChanges();
|
||||
|
||||
contentService.nodesMoved.next();
|
||||
|
||||
expect(component.reload).toHaveBeenCalled();
|
||||
});
|
||||
expect(component.reload).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
describe('refresh', () => {
|
||||
it('should call document list reload', () => {
|
||||
spyOn(component.documentList, 'reload');
|
||||
fixture.detectChanges();
|
||||
it('should reload on move node event', () => {
|
||||
fixture.detectChanges();
|
||||
|
||||
component.reload();
|
||||
contentService.nodesMoved.next();
|
||||
|
||||
expect(component.documentList.reload).toHaveBeenCalled();
|
||||
});
|
||||
expect(component.reload).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('refresh', () => {
|
||||
it('should call document list reload', () => {
|
||||
spyOn(component.documentList, 'reload');
|
||||
fixture.detectChanges();
|
||||
|
||||
component.reload();
|
||||
|
||||
expect(component.documentList.reload).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -33,47 +33,44 @@ import { AppStore } from '../../store/states/app.state';
|
||||
import { AppExtensionService } from '../../extensions/extension.service';
|
||||
|
||||
@Component({
|
||||
templateUrl: './recent-files.component.html'
|
||||
templateUrl: './recent-files.component.html'
|
||||
})
|
||||
export class RecentFilesComponent extends PageComponent implements OnInit {
|
||||
isSmallScreen = false;
|
||||
isSmallScreen = false;
|
||||
|
||||
constructor(
|
||||
store: Store<AppStore>,
|
||||
extensions: AppExtensionService,
|
||||
content: ContentManagementService,
|
||||
private breakpointObserver: BreakpointObserver
|
||||
) {
|
||||
super(store, extensions, content);
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
super.ngOnInit();
|
||||
|
||||
this.subscriptions = this.subscriptions.concat([
|
||||
this.content.nodesDeleted.subscribe(() => this.reload()),
|
||||
this.content.nodesMoved.subscribe(() => this.reload()),
|
||||
this.content.nodesRestored.subscribe(() => this.reload()),
|
||||
|
||||
this.breakpointObserver
|
||||
.observe([
|
||||
Breakpoints.HandsetPortrait,
|
||||
Breakpoints.HandsetLandscape
|
||||
])
|
||||
.subscribe(result => {
|
||||
this.isSmallScreen = result.matches;
|
||||
})
|
||||
]);
|
||||
}
|
||||
|
||||
onNodeDoubleClick(node: MinimalNodeEntity) {
|
||||
if (node && node.entry) {
|
||||
if (PageComponent.isLockedNode(node.entry)) {
|
||||
event.preventDefault();
|
||||
return;
|
||||
}
|
||||
|
||||
this.showPreview(node);
|
||||
}
|
||||
constructor(
|
||||
store: Store<AppStore>,
|
||||
extensions: AppExtensionService,
|
||||
content: ContentManagementService,
|
||||
private breakpointObserver: BreakpointObserver
|
||||
) {
|
||||
super(store, extensions, content);
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
super.ngOnInit();
|
||||
|
||||
this.subscriptions = this.subscriptions.concat([
|
||||
this.content.nodesDeleted.subscribe(() => this.reload()),
|
||||
this.content.nodesMoved.subscribe(() => this.reload()),
|
||||
this.content.nodesRestored.subscribe(() => this.reload()),
|
||||
|
||||
this.breakpointObserver
|
||||
.observe([Breakpoints.HandsetPortrait, Breakpoints.HandsetLandscape])
|
||||
.subscribe(result => {
|
||||
this.isSmallScreen = result.matches;
|
||||
})
|
||||
]);
|
||||
}
|
||||
|
||||
onNodeDoubleClick(node: MinimalNodeEntity) {
|
||||
if (node && node.entry) {
|
||||
if (PageComponent.isLockedNode(node.entry)) {
|
||||
event.preventDefault();
|
||||
return;
|
||||
}
|
||||
|
||||
this.showPreview(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
.adf-clear-search-icon-wrapper {
|
||||
width: 1em;
|
||||
width: 1em;
|
||||
|
||||
.mat-icon {
|
||||
font-size: 100%;
|
||||
cursor: pointer;
|
||||
}
|
||||
.mat-icon {
|
||||
font-size: 100%;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
@ -24,253 +24,299 @@
|
||||
*/
|
||||
|
||||
import { ThumbnailService } from '@alfresco/adf-core';
|
||||
import { animate, state, style, transition, trigger } from '@angular/animations';
|
||||
import { Component, EventEmitter, Input, OnDestroy, OnInit, Output,
|
||||
QueryList, ViewEncapsulation, ViewChild, ViewChildren, ElementRef, TemplateRef, ContentChild } from '@angular/core';
|
||||
import {
|
||||
animate,
|
||||
state,
|
||||
style,
|
||||
transition,
|
||||
trigger
|
||||
} from '@angular/animations';
|
||||
import {
|
||||
Component,
|
||||
EventEmitter,
|
||||
Input,
|
||||
OnDestroy,
|
||||
OnInit,
|
||||
Output,
|
||||
QueryList,
|
||||
ViewEncapsulation,
|
||||
ViewChild,
|
||||
ViewChildren,
|
||||
ElementRef,
|
||||
TemplateRef,
|
||||
ContentChild
|
||||
} from '@angular/core';
|
||||
import { MinimalNodeEntity, QueryBody } from 'alfresco-js-api';
|
||||
import { Subject } from 'rxjs';
|
||||
import { MatListItem } from '@angular/material';
|
||||
import { debounceTime, filter } from 'rxjs/operators';
|
||||
import { EmptySearchResultComponent, SearchComponent } from '@alfresco/adf-content-services';
|
||||
import {
|
||||
EmptySearchResultComponent,
|
||||
SearchComponent
|
||||
} from '@alfresco/adf-content-services';
|
||||
|
||||
@Component({
|
||||
selector: 'app-search-input-control',
|
||||
templateUrl: './search-input-control.component.html',
|
||||
styleUrls: ['./search-input-control.component.scss'],
|
||||
animations: [
|
||||
trigger('transitionMessages', [
|
||||
state('active', style({ transform: 'translateX(0%)', 'margin-left': '13px' })),
|
||||
state('inactive', style({ transform: 'translateX(81%)'})),
|
||||
state('no-animation', style({ transform: 'translateX(0%)', width: '100%' })),
|
||||
transition('inactive => active',
|
||||
animate('300ms cubic-bezier(0.55, 0, 0.55, 0.2)')),
|
||||
transition('active => inactive',
|
||||
animate('300ms cubic-bezier(0.55, 0, 0.55, 0.2)'))
|
||||
])
|
||||
],
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
host: { class: 'adf-search-control' }
|
||||
selector: 'app-search-input-control',
|
||||
templateUrl: './search-input-control.component.html',
|
||||
styleUrls: ['./search-input-control.component.scss'],
|
||||
animations: [
|
||||
trigger('transitionMessages', [
|
||||
state(
|
||||
'active',
|
||||
style({ transform: 'translateX(0%)', 'margin-left': '13px' })
|
||||
),
|
||||
state('inactive', style({ transform: 'translateX(81%)' })),
|
||||
state(
|
||||
'no-animation',
|
||||
style({ transform: 'translateX(0%)', width: '100%' })
|
||||
),
|
||||
transition(
|
||||
'inactive => active',
|
||||
animate('300ms cubic-bezier(0.55, 0, 0.55, 0.2)')
|
||||
),
|
||||
transition(
|
||||
'active => inactive',
|
||||
animate('300ms cubic-bezier(0.55, 0, 0.55, 0.2)')
|
||||
)
|
||||
])
|
||||
],
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
host: { class: 'adf-search-control' }
|
||||
})
|
||||
export class SearchInputControlComponent implements OnInit, OnDestroy {
|
||||
/** Toggles whether to use an expanding search control. If false
|
||||
* then a regular input is used.
|
||||
*/
|
||||
@Input()
|
||||
expandable = true;
|
||||
|
||||
/** Toggles whether to use an expanding search control. If false
|
||||
* then a regular input is used.
|
||||
*/
|
||||
@Input()
|
||||
expandable = true;
|
||||
/** Toggles highlighting of the search term in the results. */
|
||||
@Input()
|
||||
highlight = false;
|
||||
|
||||
/** Toggles highlighting of the search term in the results. */
|
||||
@Input()
|
||||
highlight = false;
|
||||
/** Type of the input field to render, e.g. "search" or "text" (default). */
|
||||
@Input()
|
||||
inputType = 'text';
|
||||
|
||||
/** Type of the input field to render, e.g. "search" or "text" (default). */
|
||||
@Input()
|
||||
inputType = 'text';
|
||||
/** Toggles auto-completion of the search input field. */
|
||||
@Input()
|
||||
autocomplete = false;
|
||||
|
||||
/** Toggles auto-completion of the search input field. */
|
||||
@Input()
|
||||
autocomplete = false;
|
||||
/** Toggles "find-as-you-type" suggestions for possible matches. */
|
||||
@Input()
|
||||
liveSearchEnabled = true;
|
||||
|
||||
/** Toggles "find-as-you-type" suggestions for possible matches. */
|
||||
@Input()
|
||||
liveSearchEnabled = true;
|
||||
/** Maximum number of results to show in the live search. */
|
||||
@Input()
|
||||
liveSearchMaxResults = 5;
|
||||
|
||||
/** Maximum number of results to show in the live search. */
|
||||
@Input()
|
||||
liveSearchMaxResults = 5;
|
||||
/** @deprecated in 2.1.0 */
|
||||
@Input()
|
||||
customQueryBody: QueryBody;
|
||||
|
||||
/** @deprecated in 2.1.0 */
|
||||
@Input()
|
||||
customQueryBody: QueryBody;
|
||||
/** Emitted when the search is submitted pressing ENTER button.
|
||||
* The search term is provided as value of the event.
|
||||
*/
|
||||
@Output()
|
||||
submit: EventEmitter<any> = new EventEmitter();
|
||||
|
||||
/** Emitted when the search is submitted pressing ENTER button.
|
||||
* The search term is provided as value of the event.
|
||||
*/
|
||||
@Output()
|
||||
submit: EventEmitter<any> = new EventEmitter();
|
||||
/** Emitted when the search term is changed. The search term is provided
|
||||
* in the 'value' property of the returned object. If the term is less
|
||||
* than three characters in length then the term is truncated to an empty
|
||||
* string.
|
||||
*/
|
||||
@Output()
|
||||
searchChange: EventEmitter<string> = new EventEmitter();
|
||||
|
||||
/** Emitted when the search term is changed. The search term is provided
|
||||
* in the 'value' property of the returned object. If the term is less
|
||||
* than three characters in length then the term is truncated to an empty
|
||||
* string.
|
||||
*/
|
||||
@Output()
|
||||
searchChange: EventEmitter<string> = new EventEmitter();
|
||||
/** Emitted when a file item from the list of "find-as-you-type" results is selected. */
|
||||
@Output()
|
||||
optionClicked: EventEmitter<any> = new EventEmitter();
|
||||
|
||||
/** Emitted when a file item from the list of "find-as-you-type" results is selected. */
|
||||
@Output()
|
||||
optionClicked: EventEmitter<any> = new EventEmitter();
|
||||
@ViewChild('search')
|
||||
searchAutocomplete: SearchComponent;
|
||||
|
||||
@ViewChild('search')
|
||||
searchAutocomplete: SearchComponent;
|
||||
@ViewChild('searchInput')
|
||||
searchInput: ElementRef;
|
||||
|
||||
@ViewChild('searchInput')
|
||||
searchInput: ElementRef;
|
||||
@ViewChildren(MatListItem)
|
||||
private listResultElement: QueryList<MatListItem>;
|
||||
|
||||
@ViewChildren(MatListItem)
|
||||
private listResultElement: QueryList<MatListItem>;
|
||||
@ContentChild(EmptySearchResultComponent)
|
||||
emptySearchTemplate: EmptySearchResultComponent;
|
||||
|
||||
@ContentChild(EmptySearchResultComponent)
|
||||
emptySearchTemplate: EmptySearchResultComponent;
|
||||
searchTerm = '';
|
||||
subscriptAnimationState: string;
|
||||
noSearchResultTemplate: TemplateRef<any> = null;
|
||||
skipToggle = false;
|
||||
toggleDebounceTime = 200;
|
||||
|
||||
searchTerm = '';
|
||||
subscriptAnimationState: string;
|
||||
noSearchResultTemplate: TemplateRef <any> = null;
|
||||
skipToggle = false;
|
||||
toggleDebounceTime = 200;
|
||||
private toggleSearch = new Subject<any>();
|
||||
private focusSubject = new Subject<FocusEvent>();
|
||||
|
||||
private toggleSearch = new Subject<any>();
|
||||
private focusSubject = new Subject<FocusEvent>();
|
||||
constructor(private thumbnailService: ThumbnailService) {
|
||||
this.toggleSearch
|
||||
.asObservable()
|
||||
.pipe(debounceTime(this.toggleDebounceTime))
|
||||
.subscribe(() => {
|
||||
if (this.expandable && !this.skipToggle) {
|
||||
this.subscriptAnimationState =
|
||||
this.subscriptAnimationState === 'inactive' ? 'active' : 'inactive';
|
||||
|
||||
constructor(private thumbnailService: ThumbnailService) {
|
||||
|
||||
this.toggleSearch.asObservable().pipe(debounceTime(this.toggleDebounceTime)).subscribe(() => {
|
||||
if (this.expandable && !this.skipToggle) {
|
||||
this.subscriptAnimationState = this.subscriptAnimationState === 'inactive' ? 'active' : 'inactive';
|
||||
|
||||
if (this.subscriptAnimationState === 'inactive') {
|
||||
this.searchTerm = '';
|
||||
this.searchAutocomplete.resetResults();
|
||||
if ( document.activeElement.id === this.searchInput.nativeElement.id) {
|
||||
this.searchInput.nativeElement.blur();
|
||||
}
|
||||
}
|
||||
if (this.subscriptAnimationState === 'inactive') {
|
||||
this.searchTerm = '';
|
||||
this.searchAutocomplete.resetResults();
|
||||
if (
|
||||
document.activeElement.id === this.searchInput.nativeElement.id
|
||||
) {
|
||||
this.searchInput.nativeElement.blur();
|
||||
}
|
||||
this.skipToggle = false;
|
||||
});
|
||||
}
|
||||
|
||||
applySearchFocus(animationDoneEvent) {
|
||||
if (animationDoneEvent.toState === 'active') {
|
||||
this.searchInput.nativeElement.focus();
|
||||
}
|
||||
}
|
||||
this.skipToggle = false;
|
||||
});
|
||||
}
|
||||
|
||||
applySearchFocus(animationDoneEvent) {
|
||||
if (animationDoneEvent.toState === 'active') {
|
||||
this.searchInput.nativeElement.focus();
|
||||
}
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.subscriptAnimationState = this.expandable
|
||||
? 'inactive'
|
||||
: 'no-animation';
|
||||
this.setupFocusEventHandlers();
|
||||
}
|
||||
|
||||
isNoSearchTemplatePresent(): boolean {
|
||||
return this.emptySearchTemplate ? true : false;
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
if (this.focusSubject) {
|
||||
this.focusSubject.unsubscribe();
|
||||
this.focusSubject = null;
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.subscriptAnimationState = this.expandable ? 'inactive' : 'no-animation';
|
||||
this.setupFocusEventHandlers();
|
||||
if (this.toggleSearch) {
|
||||
this.toggleSearch.unsubscribe();
|
||||
this.toggleSearch = null;
|
||||
}
|
||||
}
|
||||
|
||||
searchSubmit(event: any) {
|
||||
this.submit.emit(event);
|
||||
this.toggleSearchBar();
|
||||
}
|
||||
|
||||
inputChange(event: any) {
|
||||
this.searchChange.emit(event);
|
||||
}
|
||||
|
||||
getAutoComplete(): string {
|
||||
return this.autocomplete ? 'on' : 'off';
|
||||
}
|
||||
|
||||
getMimeTypeIcon(node: MinimalNodeEntity): string {
|
||||
let mimeType;
|
||||
|
||||
if (node.entry.content && node.entry.content.mimeType) {
|
||||
mimeType = node.entry.content.mimeType;
|
||||
}
|
||||
if (node.entry.isFolder) {
|
||||
mimeType = 'folder';
|
||||
}
|
||||
|
||||
isNoSearchTemplatePresent(): boolean {
|
||||
return this.emptySearchTemplate ? true : false;
|
||||
return this.thumbnailService.getMimeTypeIcon(mimeType);
|
||||
}
|
||||
|
||||
isSearchBarActive() {
|
||||
return this.subscriptAnimationState === 'active' && this.liveSearchEnabled;
|
||||
}
|
||||
|
||||
toggleSearchBar() {
|
||||
if (this.toggleSearch) {
|
||||
this.toggleSearch.next();
|
||||
}
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
if (this.focusSubject) {
|
||||
this.focusSubject.unsubscribe();
|
||||
this.focusSubject = null;
|
||||
}
|
||||
|
||||
if (this.toggleSearch) {
|
||||
this.toggleSearch.unsubscribe();
|
||||
this.toggleSearch = null;
|
||||
}
|
||||
elementClicked(item: any) {
|
||||
if (item.entry) {
|
||||
this.optionClicked.next(item);
|
||||
this.toggleSearchBar();
|
||||
}
|
||||
}
|
||||
|
||||
searchSubmit(event: any) {
|
||||
this.submit.emit(event);
|
||||
onFocus($event): void {
|
||||
this.focusSubject.next($event);
|
||||
}
|
||||
|
||||
onBlur($event): void {
|
||||
this.focusSubject.next($event);
|
||||
}
|
||||
|
||||
activateToolbar() {
|
||||
if (!this.isSearchBarActive()) {
|
||||
this.toggleSearchBar();
|
||||
}
|
||||
}
|
||||
|
||||
selectFirstResult() {
|
||||
if (this.listResultElement && this.listResultElement.length > 0) {
|
||||
const firstElement: MatListItem = <MatListItem>(
|
||||
this.listResultElement.first
|
||||
);
|
||||
firstElement._getHostElement().focus();
|
||||
}
|
||||
}
|
||||
|
||||
onRowArrowDown($event: KeyboardEvent): void {
|
||||
const nextElement: any = this.getNextElementSibling(<Element>$event.target);
|
||||
if (nextElement) {
|
||||
nextElement.focus();
|
||||
}
|
||||
}
|
||||
|
||||
onRowArrowUp($event: KeyboardEvent): void {
|
||||
const previousElement: any = this.getPreviousElementSibling(<Element>(
|
||||
$event.target
|
||||
));
|
||||
if (previousElement) {
|
||||
previousElement.focus();
|
||||
} else {
|
||||
this.searchInput.nativeElement.focus();
|
||||
this.focusSubject.next(new FocusEvent('focus'));
|
||||
}
|
||||
}
|
||||
|
||||
private setupFocusEventHandlers() {
|
||||
this.focusSubject
|
||||
.pipe(
|
||||
debounceTime(50),
|
||||
filter(($event: any) => {
|
||||
return (
|
||||
this.isSearchBarActive() &&
|
||||
($event.type === 'blur' || $event.type === 'focusout')
|
||||
);
|
||||
})
|
||||
)
|
||||
.subscribe(() => {
|
||||
this.toggleSearchBar();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
inputChange(event: any) {
|
||||
this.searchChange.emit(event);
|
||||
}
|
||||
clear(event: any) {
|
||||
this.searchTerm = '';
|
||||
this.searchChange.emit('');
|
||||
this.skipToggle = true;
|
||||
}
|
||||
|
||||
getAutoComplete(): string {
|
||||
return this.autocomplete ? 'on' : 'off';
|
||||
}
|
||||
|
||||
getMimeTypeIcon(node: MinimalNodeEntity): string {
|
||||
let mimeType;
|
||||
|
||||
if (node.entry.content && node.entry.content.mimeType) {
|
||||
mimeType = node.entry.content.mimeType;
|
||||
}
|
||||
if (node.entry.isFolder) {
|
||||
mimeType = 'folder';
|
||||
}
|
||||
|
||||
return this.thumbnailService.getMimeTypeIcon(mimeType);
|
||||
}
|
||||
|
||||
isSearchBarActive() {
|
||||
return this.subscriptAnimationState === 'active' && this.liveSearchEnabled;
|
||||
}
|
||||
|
||||
toggleSearchBar() {
|
||||
if (this.toggleSearch) {
|
||||
this.toggleSearch.next();
|
||||
}
|
||||
}
|
||||
|
||||
elementClicked(item: any) {
|
||||
if (item.entry) {
|
||||
this.optionClicked.next(item);
|
||||
this.toggleSearchBar();
|
||||
}
|
||||
}
|
||||
|
||||
onFocus($event): void {
|
||||
this.focusSubject.next($event);
|
||||
}
|
||||
|
||||
onBlur($event): void {
|
||||
this.focusSubject.next($event);
|
||||
}
|
||||
|
||||
activateToolbar() {
|
||||
if (!this.isSearchBarActive()) {
|
||||
this.toggleSearchBar();
|
||||
}
|
||||
}
|
||||
|
||||
selectFirstResult() {
|
||||
if ( this.listResultElement && this.listResultElement.length > 0) {
|
||||
const firstElement: MatListItem = <MatListItem> this.listResultElement.first;
|
||||
firstElement._getHostElement().focus();
|
||||
}
|
||||
}
|
||||
|
||||
onRowArrowDown($event: KeyboardEvent): void {
|
||||
const nextElement: any = this.getNextElementSibling(<Element> $event.target);
|
||||
if (nextElement) {
|
||||
nextElement.focus();
|
||||
}
|
||||
}
|
||||
|
||||
onRowArrowUp($event: KeyboardEvent): void {
|
||||
const previousElement: any = this.getPreviousElementSibling(<Element> $event.target);
|
||||
if (previousElement) {
|
||||
previousElement.focus();
|
||||
} else {
|
||||
this.searchInput.nativeElement.focus();
|
||||
this.focusSubject.next(new FocusEvent('focus'));
|
||||
}
|
||||
}
|
||||
|
||||
private setupFocusEventHandlers() {
|
||||
this.focusSubject.pipe(
|
||||
debounceTime(50),
|
||||
filter(($event: any) => {
|
||||
return this.isSearchBarActive() && ($event.type === 'blur' || $event.type === 'focusout');
|
||||
})
|
||||
).subscribe(() => {
|
||||
this.toggleSearchBar();
|
||||
});
|
||||
}
|
||||
|
||||
clear(event: any) {
|
||||
this.searchTerm = '';
|
||||
this.searchChange.emit('');
|
||||
this.skipToggle = true;
|
||||
}
|
||||
|
||||
private getNextElementSibling(node: Element): Element {
|
||||
return node.nextElementSibling;
|
||||
}
|
||||
|
||||
private getPreviousElementSibling(node: Element): Element {
|
||||
return node.previousElementSibling;
|
||||
}
|
||||
private getNextElementSibling(node: Element): Element {
|
||||
return node.nextElementSibling;
|
||||
}
|
||||
|
||||
private getPreviousElementSibling(node: Element): Element {
|
||||
return node.previousElementSibling;
|
||||
}
|
||||
}
|
||||
|
@ -24,65 +24,74 @@
|
||||
*/
|
||||
|
||||
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
||||
import { TestBed, async, ComponentFixture, fakeAsync, tick } from '@angular/core/testing';
|
||||
import {
|
||||
TestBed,
|
||||
async,
|
||||
ComponentFixture,
|
||||
fakeAsync,
|
||||
tick
|
||||
} from '@angular/core/testing';
|
||||
|
||||
import { SearchInputComponent } from './search-input.component';
|
||||
import { AppTestingModule } from '../../../testing/app-testing.module';
|
||||
import { Actions, ofType } from '@ngrx/effects';
|
||||
import { NAVIGATE_FOLDER, NavigateToFolder, VIEW_FILE, ViewFileAction } from '../../../store/actions';
|
||||
import {
|
||||
NAVIGATE_FOLDER,
|
||||
NavigateToFolder,
|
||||
VIEW_FILE,
|
||||
ViewFileAction
|
||||
} from '../../../store/actions';
|
||||
import { map } from 'rxjs/operators';
|
||||
|
||||
describe('SearchInputComponent', () => {
|
||||
let fixture: ComponentFixture<SearchInputComponent>;
|
||||
let component: SearchInputComponent;
|
||||
let actions$: Actions;
|
||||
let fixture: ComponentFixture<SearchInputComponent>;
|
||||
let component: SearchInputComponent;
|
||||
let actions$: Actions;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
AppTestingModule
|
||||
],
|
||||
declarations: [
|
||||
SearchInputComponent
|
||||
],
|
||||
schemas: [ NO_ERRORS_SCHEMA ]
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [AppTestingModule],
|
||||
declarations: [SearchInputComponent],
|
||||
schemas: [NO_ERRORS_SCHEMA]
|
||||
})
|
||||
.compileComponents()
|
||||
.then(() => {
|
||||
actions$ = TestBed.get(Actions);
|
||||
fixture = TestBed.createComponent(SearchInputComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
}));
|
||||
|
||||
describe('onItemClicked()', () => {
|
||||
it('opens preview if node is file', fakeAsync(done => {
|
||||
actions$.pipe(
|
||||
ofType<ViewFileAction>(VIEW_FILE),
|
||||
map(action => {
|
||||
expect(action.payload.entry.id).toBe('node-id');
|
||||
done();
|
||||
})
|
||||
.compileComponents()
|
||||
.then(() => {
|
||||
actions$ = TestBed.get(Actions);
|
||||
fixture = TestBed.createComponent(SearchInputComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
);
|
||||
|
||||
const node = {
|
||||
entry: { isFile: true, id: 'node-id', parentId: 'parent-id' }
|
||||
};
|
||||
|
||||
component.onItemClicked(node);
|
||||
tick();
|
||||
}));
|
||||
|
||||
describe('onItemClicked()', () => {
|
||||
it('opens preview if node is file', fakeAsync(done => {
|
||||
actions$.pipe(
|
||||
ofType<ViewFileAction>(VIEW_FILE),
|
||||
map(action => {
|
||||
expect(action.payload.entry.id).toBe('node-id');
|
||||
done();
|
||||
})
|
||||
);
|
||||
|
||||
const node = { entry: { isFile: true, id: 'node-id', parentId: 'parent-id' } };
|
||||
|
||||
component.onItemClicked(node);
|
||||
tick();
|
||||
}));
|
||||
|
||||
it('navigates if node is folder', fakeAsync(done => {
|
||||
actions$.pipe(
|
||||
ofType<NavigateToFolder>(NAVIGATE_FOLDER),
|
||||
map(action => {
|
||||
expect(action.payload.entry.id).toBe('folder-id');
|
||||
done();
|
||||
})
|
||||
);
|
||||
const node = { entry: { id: 'folder-id', isFolder: true } };
|
||||
component.onItemClicked(node);
|
||||
tick();
|
||||
}));
|
||||
});
|
||||
it('navigates if node is folder', fakeAsync(done => {
|
||||
actions$.pipe(
|
||||
ofType<NavigateToFolder>(NAVIGATE_FOLDER),
|
||||
map(action => {
|
||||
expect(action.payload.entry.id).toBe('folder-id');
|
||||
done();
|
||||
})
|
||||
);
|
||||
const node = { entry: { id: 'folder-id', isFolder: true } };
|
||||
component.onItemClicked(node);
|
||||
tick();
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
@ -1,37 +1,37 @@
|
||||
@mixin aca-search-input-theme($theme) {
|
||||
$background: map-get($theme, background);
|
||||
$background: map-get($theme, background);
|
||||
|
||||
.aca-search-input{
|
||||
display: flex;
|
||||
box-sizing: border-box;
|
||||
padding: 0;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
white-space: nowrap;
|
||||
.aca-search-input {
|
||||
display: flex;
|
||||
box-sizing: border-box;
|
||||
padding: 0;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
white-space: nowrap;
|
||||
|
||||
.adf-search-control {
|
||||
color: mat-color($background, card);
|
||||
.adf-search-control {
|
||||
color: mat-color($background, card);
|
||||
|
||||
.mat-form-field-underline {
|
||||
background-color: mat-color($background, card);
|
||||
}
|
||||
.mat-form-field-underline {
|
||||
background-color: mat-color($background, card);
|
||||
}
|
||||
|
||||
.adf-input-form-field-divider {
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
.adf-search-button.mat-icon-button {
|
||||
left: -15px;
|
||||
margin-left: 15px;
|
||||
align-items: flex-start;
|
||||
font: 400 11px system-ui;
|
||||
color: mat-color($background, card);
|
||||
|
||||
.mat-icon {
|
||||
font-size: 24px;
|
||||
padding-right: 0;
|
||||
}
|
||||
}
|
||||
.adf-input-form-field-divider {
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
.adf-search-button.mat-icon-button {
|
||||
left: -15px;
|
||||
margin-left: 15px;
|
||||
align-items: flex-start;
|
||||
font: 400 11px system-ui;
|
||||
color: mat-color($background, card);
|
||||
|
||||
.mat-icon {
|
||||
font-size: 24px;
|
||||
padding-right: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -25,127 +25,132 @@
|
||||
|
||||
import { Component, OnInit, ViewChild, ViewEncapsulation } from '@angular/core';
|
||||
import {
|
||||
NavigationEnd, PRIMARY_OUTLET, Router, RouterEvent, UrlSegment, UrlSegmentGroup,
|
||||
UrlTree
|
||||
NavigationEnd,
|
||||
PRIMARY_OUTLET,
|
||||
Router,
|
||||
RouterEvent,
|
||||
UrlSegment,
|
||||
UrlSegmentGroup,
|
||||
UrlTree
|
||||
} from '@angular/router';
|
||||
import { MinimalNodeEntity } from 'alfresco-js-api';
|
||||
import { SearchInputControlComponent } from '../search-input-control/search-input-control.component';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { AppStore } from '../../../store/states/app.state';
|
||||
import { SearchByTermAction, NavigateToFolder, ViewFileAction } from '../../../store/actions';
|
||||
import {
|
||||
SearchByTermAction,
|
||||
NavigateToFolder,
|
||||
ViewFileAction
|
||||
} from '../../../store/actions';
|
||||
import { filter } from 'rxjs/operators';
|
||||
|
||||
@Component({
|
||||
selector: 'aca-search-input',
|
||||
templateUrl: 'search-input.component.html',
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
host: { class: 'aca-search-input' }
|
||||
selector: 'aca-search-input',
|
||||
templateUrl: 'search-input.component.html',
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
host: { class: 'aca-search-input' }
|
||||
})
|
||||
export class SearchInputComponent implements OnInit {
|
||||
hasOneChange = false;
|
||||
hasNewChange = false;
|
||||
navigationTimer: any;
|
||||
enableLiveSearch = true;
|
||||
|
||||
hasOneChange = false;
|
||||
hasNewChange = false;
|
||||
navigationTimer: any;
|
||||
enableLiveSearch = true;
|
||||
@ViewChild('searchInputControl')
|
||||
searchInputControl: SearchInputControlComponent;
|
||||
|
||||
@ViewChild('searchInputControl')
|
||||
searchInputControl: SearchInputControlComponent;
|
||||
constructor(private router: Router, private store: Store<AppStore>) {}
|
||||
|
||||
constructor(private router: Router, private store: Store<AppStore>) {
|
||||
}
|
||||
ngOnInit() {
|
||||
this.showInputValue();
|
||||
|
||||
ngOnInit() {
|
||||
this.showInputValue();
|
||||
|
||||
this.router.events
|
||||
.pipe(filter(e => e instanceof RouterEvent))
|
||||
.subscribe(event => {
|
||||
if (event instanceof NavigationEnd) {
|
||||
this.showInputValue();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
showInputValue() {
|
||||
if (this.onSearchResults) {
|
||||
|
||||
let searchedWord = null;
|
||||
const urlTree: UrlTree = this.router.parseUrl(this.router.url);
|
||||
const urlSegmentGroup: UrlSegmentGroup = urlTree.root.children[PRIMARY_OUTLET];
|
||||
|
||||
if (urlSegmentGroup) {
|
||||
const urlSegments: UrlSegment[] = urlSegmentGroup.segments;
|
||||
searchedWord = urlSegments[0].parameters['q'];
|
||||
}
|
||||
|
||||
if (this.searchInputControl) {
|
||||
this.enableLiveSearch = false;
|
||||
this.searchInputControl.searchTerm = searchedWord;
|
||||
this.searchInputControl.subscriptAnimationState = 'no-animation';
|
||||
}
|
||||
|
||||
} else {
|
||||
if (this.searchInputControl.subscriptAnimationState === 'no-animation') {
|
||||
this.searchInputControl.subscriptAnimationState = 'active';
|
||||
this.searchInputControl.searchTerm = '';
|
||||
this.searchInputControl.toggleSearchBar();
|
||||
}
|
||||
|
||||
if (!this.enableLiveSearch) {
|
||||
setTimeout(() => {
|
||||
this.enableLiveSearch = true;
|
||||
}, this.searchInputControl.toggleDebounceTime + 100);
|
||||
}
|
||||
this.router.events
|
||||
.pipe(filter(e => e instanceof RouterEvent))
|
||||
.subscribe(event => {
|
||||
if (event instanceof NavigationEnd) {
|
||||
this.showInputValue();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
onItemClicked(node: MinimalNodeEntity) {
|
||||
if (node && node.entry) {
|
||||
const { isFile, isFolder } = node.entry;
|
||||
if (isFile) {
|
||||
this.store.dispatch(new ViewFileAction(node));
|
||||
} else if (isFolder) {
|
||||
this.store.dispatch(new NavigateToFolder(node));
|
||||
}
|
||||
}
|
||||
}
|
||||
showInputValue() {
|
||||
if (this.onSearchResults) {
|
||||
let searchedWord = null;
|
||||
const urlTree: UrlTree = this.router.parseUrl(this.router.url);
|
||||
const urlSegmentGroup: UrlSegmentGroup =
|
||||
urlTree.root.children[PRIMARY_OUTLET];
|
||||
|
||||
/**
|
||||
* Called when the user submits the search, e.g. hits enter or clicks submit
|
||||
*
|
||||
* @param event Parameters relating to the search
|
||||
*/
|
||||
onSearchSubmit(event: KeyboardEvent) {
|
||||
const searchTerm = (event.target as HTMLInputElement).value;
|
||||
if (urlSegmentGroup) {
|
||||
const urlSegments: UrlSegment[] = urlSegmentGroup.segments;
|
||||
searchedWord = urlSegments[0].parameters['q'];
|
||||
}
|
||||
|
||||
if (this.searchInputControl) {
|
||||
this.enableLiveSearch = false;
|
||||
this.searchInputControl.searchTerm = searchedWord;
|
||||
this.searchInputControl.subscriptAnimationState = 'no-animation';
|
||||
}
|
||||
} else {
|
||||
if (this.searchInputControl.subscriptAnimationState === 'no-animation') {
|
||||
this.searchInputControl.subscriptAnimationState = 'active';
|
||||
this.searchInputControl.searchTerm = '';
|
||||
this.searchInputControl.toggleSearchBar();
|
||||
}
|
||||
|
||||
if (!this.enableLiveSearch) {
|
||||
setTimeout(() => {
|
||||
this.enableLiveSearch = true;
|
||||
}, this.searchInputControl.toggleDebounceTime + 100);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onItemClicked(node: MinimalNodeEntity) {
|
||||
if (node && node.entry) {
|
||||
const { isFile, isFolder } = node.entry;
|
||||
if (isFile) {
|
||||
this.store.dispatch(new ViewFileAction(node));
|
||||
} else if (isFolder) {
|
||||
this.store.dispatch(new NavigateToFolder(node));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the user submits the search, e.g. hits enter or clicks submit
|
||||
*
|
||||
* @param event Parameters relating to the search
|
||||
*/
|
||||
onSearchSubmit(event: KeyboardEvent) {
|
||||
const searchTerm = (event.target as HTMLInputElement).value;
|
||||
if (searchTerm) {
|
||||
this.store.dispatch(new SearchByTermAction(searchTerm));
|
||||
}
|
||||
}
|
||||
|
||||
onSearchChange(searchTerm: string) {
|
||||
if (this.onSearchResults) {
|
||||
if (this.hasOneChange) {
|
||||
this.hasNewChange = true;
|
||||
} else {
|
||||
this.hasOneChange = true;
|
||||
}
|
||||
|
||||
if (this.hasNewChange) {
|
||||
clearTimeout(this.navigationTimer);
|
||||
this.hasNewChange = false;
|
||||
}
|
||||
|
||||
this.navigationTimer = setTimeout(() => {
|
||||
if (searchTerm) {
|
||||
this.store.dispatch(new SearchByTermAction(searchTerm));
|
||||
this.store.dispatch(new SearchByTermAction(searchTerm));
|
||||
}
|
||||
this.hasOneChange = false;
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
|
||||
onSearchChange(searchTerm: string) {
|
||||
if (this.onSearchResults) {
|
||||
|
||||
if (this.hasOneChange) {
|
||||
this.hasNewChange = true;
|
||||
} else {
|
||||
this.hasOneChange = true;
|
||||
}
|
||||
|
||||
if (this.hasNewChange) {
|
||||
clearTimeout(this.navigationTimer);
|
||||
this.hasNewChange = false;
|
||||
}
|
||||
|
||||
this.navigationTimer = setTimeout(() => {
|
||||
if (searchTerm) {
|
||||
this.store.dispatch(new SearchByTermAction(searchTerm));
|
||||
}
|
||||
this.hasOneChange = false;
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
|
||||
get onSearchResults() {
|
||||
return this.router.url.indexOf('/search') === 0;
|
||||
}
|
||||
get onSearchResults() {
|
||||
return this.router.url.indexOf('/search') === 0;
|
||||
}
|
||||
}
|
||||
|
@ -1,24 +1,24 @@
|
||||
@import 'mixins';
|
||||
|
||||
.aca-search-results-row {
|
||||
@include flex-column;
|
||||
@include flex-column;
|
||||
}
|
||||
|
||||
.line {
|
||||
margin: 5px 0;
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
.bold {
|
||||
font-weight: 400;
|
||||
color: rgba(0, 0, 0, 0.87);
|
||||
font-weight: 400;
|
||||
color: rgba(0, 0, 0, 0.87);
|
||||
}
|
||||
|
||||
.link {
|
||||
text-decoration: none;
|
||||
color: rgba(0, 0, 0, 0.87);
|
||||
text-decoration: none;
|
||||
color: rgba(0, 0, 0, 0.87);
|
||||
}
|
||||
|
||||
.link:hover {
|
||||
color: #2196F3;
|
||||
text-decoration: underline;
|
||||
color: #2196f3;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
@ -23,7 +23,13 @@
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Component, Input, OnInit, ViewEncapsulation, ChangeDetectionStrategy } from '@angular/core';
|
||||
import {
|
||||
Component,
|
||||
Input,
|
||||
OnInit,
|
||||
ViewEncapsulation,
|
||||
ChangeDetectionStrategy
|
||||
} from '@angular/core';
|
||||
import { MinimalNodeEntity } from 'alfresco-js-api';
|
||||
import { ViewFileAction } from '../../../store/actions';
|
||||
import { Store } from '@ngrx/store';
|
||||
@ -31,85 +37,84 @@ import { AppStore } from '../../../store/states/app.state';
|
||||
import { NavigateToFolder } from '../../../store/actions';
|
||||
|
||||
@Component({
|
||||
selector: 'aca-search-results-row',
|
||||
templateUrl: './search-results-row.component.html',
|
||||
styleUrls: ['./search-results-row.component.scss'],
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
host: { class: 'aca-search-results-row' }
|
||||
selector: 'aca-search-results-row',
|
||||
templateUrl: './search-results-row.component.html',
|
||||
styleUrls: ['./search-results-row.component.scss'],
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
host: { class: 'aca-search-results-row' }
|
||||
})
|
||||
export class SearchResultsRowComponent implements OnInit {
|
||||
private node: MinimalNodeEntity;
|
||||
private node: MinimalNodeEntity;
|
||||
|
||||
@Input() context: any;
|
||||
@Input()
|
||||
context: any;
|
||||
|
||||
constructor(private store: Store<AppStore>) {}
|
||||
constructor(private store: Store<AppStore>) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.node = this.context.row.node;
|
||||
}
|
||||
ngOnInit() {
|
||||
this.node = this.context.row.node;
|
||||
}
|
||||
|
||||
get name() {
|
||||
return this.getValue('name');
|
||||
}
|
||||
get name() {
|
||||
return this.getValue('name');
|
||||
}
|
||||
|
||||
get title() {
|
||||
return this.getValue('properties["cm:title"]');
|
||||
}
|
||||
get title() {
|
||||
return this.getValue('properties["cm:title"]');
|
||||
}
|
||||
|
||||
get description() {
|
||||
return this.getValue('properties["cm:description"]');
|
||||
}
|
||||
get description() {
|
||||
return this.getValue('properties["cm:description"]');
|
||||
}
|
||||
|
||||
get modifiedAt() {
|
||||
return this.getValue('modifiedAt');
|
||||
}
|
||||
get modifiedAt() {
|
||||
return this.getValue('modifiedAt');
|
||||
}
|
||||
|
||||
get size() {
|
||||
return this.getValue('content.sizeInBytes');
|
||||
}
|
||||
get size() {
|
||||
return this.getValue('content.sizeInBytes');
|
||||
}
|
||||
|
||||
get user() {
|
||||
return this.getValue('modifiedByUser.displayName');
|
||||
}
|
||||
get user() {
|
||||
return this.getValue('modifiedByUser.displayName');
|
||||
}
|
||||
|
||||
get hasDescription() {
|
||||
return this.description;
|
||||
}
|
||||
get hasDescription() {
|
||||
return this.description;
|
||||
}
|
||||
|
||||
get hasTitle() {
|
||||
return this.title;
|
||||
}
|
||||
get hasTitle() {
|
||||
return this.title;
|
||||
}
|
||||
|
||||
get showTitle() {
|
||||
return this.name !== this.title;
|
||||
}
|
||||
get showTitle() {
|
||||
return this.name !== this.title;
|
||||
}
|
||||
|
||||
get hasSize() {
|
||||
return this.size;
|
||||
}
|
||||
get hasSize() {
|
||||
return this.size;
|
||||
}
|
||||
|
||||
get isFile() {
|
||||
return this.getValue('isFile');
|
||||
}
|
||||
get isFile() {
|
||||
return this.getValue('isFile');
|
||||
}
|
||||
|
||||
showPreview() {
|
||||
this.store.dispatch(
|
||||
new ViewFileAction(this.node)
|
||||
);
|
||||
}
|
||||
showPreview() {
|
||||
this.store.dispatch(new ViewFileAction(this.node));
|
||||
}
|
||||
|
||||
navigate() {
|
||||
this.store.dispatch(new NavigateToFolder(this.node));
|
||||
}
|
||||
navigate() {
|
||||
this.store.dispatch(new NavigateToFolder(this.node));
|
||||
}
|
||||
|
||||
private getValue(path) {
|
||||
return path
|
||||
.replace('["', '.')
|
||||
.replace('"]', '')
|
||||
.replace('[', '.')
|
||||
.replace(']', '')
|
||||
.split('.')
|
||||
.reduce((acc, part) => (acc ? acc[part] : null), this.node.entry);
|
||||
}
|
||||
private getValue(path) {
|
||||
return path
|
||||
.replace('["', '.')
|
||||
.replace('"]', '')
|
||||
.replace('[', '.')
|
||||
.replace(']', '')
|
||||
.split('.')
|
||||
.reduce((acc, part) => (acc ? acc[part] : null), this.node.entry);
|
||||
}
|
||||
}
|
||||
|
@ -1,60 +1,60 @@
|
||||
@import 'mixins';
|
||||
|
||||
.adf-search-results {
|
||||
@include flex-row;
|
||||
|
||||
&__facets {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
margin-top: 5px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
&__content {
|
||||
@include flex-column;
|
||||
border-left: 1px solid #eee;
|
||||
}
|
||||
|
||||
&__content-header {
|
||||
display: flex;
|
||||
padding: 0 25px 0 25px;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
&--info-text {
|
||||
flex: 1;
|
||||
font-size: 16px;
|
||||
color: rgba(0, 0, 0, 0.54);
|
||||
}
|
||||
|
||||
.adf-search-filter {
|
||||
min-width: 260px;
|
||||
padding: 5px;
|
||||
height: 100%;
|
||||
overflow: scroll;
|
||||
|
||||
&--hidden {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.text--bold {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.content {
|
||||
@include flex-row;
|
||||
flex: unset;
|
||||
height: unset;
|
||||
padding-top: 8px;
|
||||
padding-bottom: 8px;
|
||||
flex-wrap: wrap;
|
||||
|
||||
&__facets {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
margin-top: 5px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
&__content {
|
||||
@include flex-column;
|
||||
border-left: 1px solid #eee;
|
||||
}
|
||||
|
||||
&__content-header {
|
||||
display: flex;
|
||||
padding: 0 25px 0 25px;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
&--info-text {
|
||||
flex: 1;
|
||||
font-size: 16px;
|
||||
color: rgba(0, 0, 0, 0.54);
|
||||
}
|
||||
|
||||
.adf-search-filter {
|
||||
min-width: 260px;
|
||||
padding: 5px;
|
||||
height: 100%;
|
||||
overflow: scroll;
|
||||
|
||||
&--hidden {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.text--bold {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.content {
|
||||
@include flex-row;
|
||||
flex: unset;
|
||||
height: unset;
|
||||
padding-top: 8px;
|
||||
padding-bottom: 8px;
|
||||
flex-wrap: wrap;
|
||||
|
||||
&__side--left {
|
||||
@include flex-column;
|
||||
height: unset;
|
||||
}
|
||||
&__side--left {
|
||||
@include flex-column;
|
||||
height: unset;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -8,9 +8,8 @@ describe('SearchComponent', () => {
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ SearchResultsComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
declarations: [SearchResultsComponent]
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
|
@ -26,7 +26,11 @@
|
||||
import { Component, OnInit, ViewChild } from '@angular/core';
|
||||
import { NodePaging, Pagination, MinimalNodeEntity } from 'alfresco-js-api';
|
||||
import { ActivatedRoute, Params } from '@angular/router';
|
||||
import { SearchQueryBuilderService, SearchComponent as AdfSearchComponent, SearchFilterComponent } from '@alfresco/adf-content-services';
|
||||
import {
|
||||
SearchQueryBuilderService,
|
||||
SearchComponent as AdfSearchComponent,
|
||||
SearchFilterComponent
|
||||
} from '@alfresco/adf-content-services';
|
||||
import { PageComponent } from '../../page.component';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { AppStore } from '../../../store/states/app.state';
|
||||
@ -41,140 +45,146 @@ import { ContentManagementService } from '../../../services/content-management.s
|
||||
providers: [SearchQueryBuilderService]
|
||||
})
|
||||
export class SearchResultsComponent extends PageComponent implements OnInit {
|
||||
@ViewChild('search')
|
||||
search: AdfSearchComponent;
|
||||
|
||||
@ViewChild('search')
|
||||
search: AdfSearchComponent;
|
||||
@ViewChild('searchFilter')
|
||||
searchFilter: SearchFilterComponent;
|
||||
|
||||
@ViewChild('searchFilter')
|
||||
searchFilter: SearchFilterComponent;
|
||||
searchedWord: string;
|
||||
queryParamName = 'q';
|
||||
data: NodePaging;
|
||||
totalResults = 0;
|
||||
hasSelectedFilters = false;
|
||||
sorting = ['name', 'asc'];
|
||||
isLoading = false;
|
||||
|
||||
searchedWord: string;
|
||||
queryParamName = 'q';
|
||||
data: NodePaging;
|
||||
totalResults = 0;
|
||||
hasSelectedFilters = false;
|
||||
sorting = ['name', 'asc'];
|
||||
isLoading = false;
|
||||
constructor(
|
||||
private queryBuilder: SearchQueryBuilderService,
|
||||
private route: ActivatedRoute,
|
||||
store: Store<AppStore>,
|
||||
extensions: AppExtensionService,
|
||||
content: ContentManagementService
|
||||
) {
|
||||
super(store, extensions, content);
|
||||
|
||||
constructor(
|
||||
private queryBuilder: SearchQueryBuilderService,
|
||||
private route: ActivatedRoute,
|
||||
store: Store<AppStore>,
|
||||
extensions: AppExtensionService,
|
||||
content: ContentManagementService
|
||||
) {
|
||||
super(store, extensions, content);
|
||||
queryBuilder.paging = {
|
||||
skipCount: 0,
|
||||
maxItems: 25
|
||||
};
|
||||
}
|
||||
|
||||
queryBuilder.paging = {
|
||||
skipCount: 0,
|
||||
maxItems: 25
|
||||
};
|
||||
}
|
||||
ngOnInit() {
|
||||
super.ngOnInit();
|
||||
|
||||
ngOnInit() {
|
||||
super.ngOnInit();
|
||||
this.sorting = this.getSorting();
|
||||
|
||||
this.subscriptions.push(
|
||||
this.queryBuilder.updated.subscribe(() => {
|
||||
this.sorting = this.getSorting();
|
||||
this.isLoading = true;
|
||||
}),
|
||||
|
||||
this.subscriptions.push(
|
||||
this.queryBuilder.updated.subscribe(() => {
|
||||
this.sorting = this.getSorting();
|
||||
this.isLoading = true;
|
||||
}),
|
||||
this.queryBuilder.executed.subscribe(data => {
|
||||
this.onSearchResultLoaded(data);
|
||||
this.isLoading = false;
|
||||
})
|
||||
);
|
||||
|
||||
this.queryBuilder.executed.subscribe(data => {
|
||||
this.onSearchResultLoaded(data);
|
||||
this.isLoading = false;
|
||||
})
|
||||
);
|
||||
if (this.route) {
|
||||
this.route.params.forEach((params: Params) => {
|
||||
this.searchedWord = params.hasOwnProperty(this.queryParamName)
|
||||
? params[this.queryParamName]
|
||||
: null;
|
||||
const query = this.formatSearchQuery(this.searchedWord);
|
||||
|
||||
if (this.route) {
|
||||
this.route.params.forEach((params: Params) => {
|
||||
this.searchedWord = params.hasOwnProperty(this.queryParamName) ? params[this.queryParamName] : null;
|
||||
const query = this.formatSearchQuery(this.searchedWord);
|
||||
|
||||
if (query) {
|
||||
this.queryBuilder.userQuery = query;
|
||||
this.queryBuilder.update();
|
||||
} else {
|
||||
this.queryBuilder.userQuery = null;
|
||||
this.queryBuilder.executed.next( {list: { pagination: { totalItems: 0 }, entries: []}} );
|
||||
}
|
||||
});
|
||||
if (query) {
|
||||
this.queryBuilder.userQuery = query;
|
||||
this.queryBuilder.update();
|
||||
} else {
|
||||
this.queryBuilder.userQuery = null;
|
||||
this.queryBuilder.executed.next({
|
||||
list: { pagination: { totalItems: 0 }, entries: [] }
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private formatSearchQuery(userInput: string) {
|
||||
if (!userInput) {
|
||||
return null;
|
||||
}
|
||||
|
||||
private formatSearchQuery(userInput: string) {
|
||||
if (!userInput) {
|
||||
return null;
|
||||
}
|
||||
const suffix = userInput.lastIndexOf('*') >= 0 ? '' : '*';
|
||||
const query = `${userInput}${suffix} OR name:${userInput}${suffix}`;
|
||||
|
||||
const suffix = userInput.lastIndexOf('*') >= 0 ? '' : '*';
|
||||
const query = `${userInput}${suffix} OR name:${userInput}${suffix}`;
|
||||
return query;
|
||||
}
|
||||
|
||||
return query;
|
||||
onSearchResultLoaded(nodePaging: NodePaging) {
|
||||
this.data = nodePaging;
|
||||
this.totalResults = this.getNumberOfResults();
|
||||
this.hasSelectedFilters = this.isFiltered();
|
||||
}
|
||||
|
||||
getNumberOfResults() {
|
||||
if (this.data && this.data.list && this.data.list.pagination) {
|
||||
return this.data.list.pagination.totalItems;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
isFiltered(): boolean {
|
||||
return (
|
||||
this.searchFilter.selectedFacetQueries.length > 0 ||
|
||||
this.searchFilter.selectedBuckets.length > 0 ||
|
||||
this.hasCheckedCategories()
|
||||
);
|
||||
}
|
||||
|
||||
hasCheckedCategories() {
|
||||
const checkedCategory = this.queryBuilder.categories.find(
|
||||
category => !!this.queryBuilder.queryFragments[category.id]
|
||||
);
|
||||
return !!checkedCategory;
|
||||
}
|
||||
|
||||
onPaginationChanged(pagination: Pagination) {
|
||||
this.queryBuilder.paging = {
|
||||
maxItems: pagination.maxItems,
|
||||
skipCount: pagination.skipCount
|
||||
};
|
||||
this.queryBuilder.update();
|
||||
}
|
||||
|
||||
private getSorting(): string[] {
|
||||
const primary = this.queryBuilder.getPrimarySorting();
|
||||
|
||||
if (primary) {
|
||||
return [primary.key, primary.ascending ? 'asc' : 'desc'];
|
||||
}
|
||||
|
||||
onSearchResultLoaded(nodePaging: NodePaging) {
|
||||
this.data = nodePaging;
|
||||
this.totalResults = this.getNumberOfResults();
|
||||
this.hasSelectedFilters = this.isFiltered();
|
||||
return ['name', 'asc'];
|
||||
}
|
||||
|
||||
onNodeDoubleClick(node: MinimalNodeEntity) {
|
||||
if (node && node.entry) {
|
||||
if (node.entry.isFolder) {
|
||||
this.store.dispatch(new NavigateToFolder(node));
|
||||
return;
|
||||
}
|
||||
|
||||
if (PageComponent.isLockedNode(node.entry)) {
|
||||
event.preventDefault();
|
||||
return;
|
||||
}
|
||||
|
||||
this.showPreview(node);
|
||||
}
|
||||
}
|
||||
|
||||
getNumberOfResults() {
|
||||
if (this.data && this.data.list && this.data.list.pagination) {
|
||||
return this.data.list.pagination.totalItems;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
isFiltered(): boolean {
|
||||
return this.searchFilter.selectedFacetQueries.length > 0
|
||||
|| this.searchFilter.selectedBuckets.length > 0
|
||||
|| this.hasCheckedCategories();
|
||||
}
|
||||
|
||||
hasCheckedCategories() {
|
||||
const checkedCategory = this.queryBuilder.categories
|
||||
.find(category => !!this.queryBuilder.queryFragments[category.id]);
|
||||
return !!checkedCategory;
|
||||
}
|
||||
|
||||
onPaginationChanged(pagination: Pagination) {
|
||||
this.queryBuilder.paging = {
|
||||
maxItems: pagination.maxItems,
|
||||
skipCount: pagination.skipCount
|
||||
};
|
||||
this.queryBuilder.update();
|
||||
}
|
||||
|
||||
private getSorting(): string[] {
|
||||
const primary = this.queryBuilder.getPrimarySorting();
|
||||
|
||||
if (primary) {
|
||||
return [primary.key, primary.ascending ? 'asc' : 'desc'];
|
||||
}
|
||||
|
||||
return ['name', 'asc'];
|
||||
}
|
||||
|
||||
onNodeDoubleClick(node: MinimalNodeEntity) {
|
||||
if (node && node.entry) {
|
||||
if (node.entry.isFolder) {
|
||||
this.store.dispatch(new NavigateToFolder(node));
|
||||
return;
|
||||
}
|
||||
|
||||
if (PageComponent.isLockedNode(node.entry)) {
|
||||
event.preventDefault();
|
||||
return;
|
||||
}
|
||||
|
||||
this.showPreview(node);
|
||||
}
|
||||
}
|
||||
|
||||
hideSearchFilter() {
|
||||
return !this.totalResults && !this.hasSelectedFilters;
|
||||
}
|
||||
hideSearchFilter() {
|
||||
return !this.totalResults && !this.hasSelectedFilters;
|
||||
}
|
||||
}
|
||||
|
@ -1,70 +1,70 @@
|
||||
@mixin aca-settings-theme($theme) {
|
||||
$background: map-get($theme, background);
|
||||
$app-menu-height: 64px;
|
||||
$background: map-get($theme, background);
|
||||
$app-menu-height: 64px;
|
||||
|
||||
.aca-settings {
|
||||
.settings-input {
|
||||
width: 50%;
|
||||
}
|
||||
.aca-settings {
|
||||
.settings-input {
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
.settings-buttons {
|
||||
text-align: right;
|
||||
.settings-buttons {
|
||||
text-align: right;
|
||||
|
||||
.mat-button {
|
||||
text-transform: uppercase;
|
||||
}
|
||||
}
|
||||
.mat-button {
|
||||
text-transform: uppercase;
|
||||
}
|
||||
}
|
||||
|
||||
.app-menu {
|
||||
.app-menu {
|
||||
height: $app-menu-height;
|
||||
|
||||
&.adf-toolbar {
|
||||
.mat-toolbar {
|
||||
background-color: inherit;
|
||||
font-family: inherit;
|
||||
min-height: $app-menu-height;
|
||||
height: $app-menu-height;
|
||||
|
||||
.mat-toolbar-layout {
|
||||
height: $app-menu-height;
|
||||
|
||||
&.adf-toolbar {
|
||||
.mat-toolbar {
|
||||
background-color: inherit;
|
||||
font-family: inherit;
|
||||
min-height: $app-menu-height;
|
||||
height: $app-menu-height;
|
||||
|
||||
.mat-toolbar-layout {
|
||||
height: $app-menu-height;
|
||||
|
||||
.mat-toolbar-row {
|
||||
height: $app-menu-height;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.adf-toolbar-divider {
|
||||
margin-left: 5px;
|
||||
margin-right: 5px;
|
||||
|
||||
& > div {
|
||||
background-color: mat-color($background, card);
|
||||
}
|
||||
}
|
||||
|
||||
.adf-toolbar-title {
|
||||
color: mat-color($background, card);
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
.app-menu__title {
|
||||
width: 100px;
|
||||
height: 50px;
|
||||
margin-left: 40px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: stretch;
|
||||
|
||||
&> img {
|
||||
width: 100%;
|
||||
object-fit: contain;
|
||||
}
|
||||
.mat-toolbar-row {
|
||||
height: $app-menu-height;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.adf-toolbar-divider {
|
||||
margin-left: 5px;
|
||||
margin-right: 5px;
|
||||
|
||||
& > div {
|
||||
background-color: mat-color($background, card);
|
||||
}
|
||||
}
|
||||
|
||||
.adf-toolbar-title {
|
||||
color: mat-color($background, card);
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
.app-menu__title {
|
||||
width: 100px;
|
||||
height: 50px;
|
||||
margin-left: 40px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: stretch;
|
||||
|
||||
& > img {
|
||||
width: 100%;
|
||||
object-fit: contain;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -24,86 +24,98 @@
|
||||
*/
|
||||
|
||||
import { Component, ViewEncapsulation, OnInit } from '@angular/core';
|
||||
import { AppConfigService, StorageService, SettingsService } from '@alfresco/adf-core';
|
||||
import {
|
||||
AppConfigService,
|
||||
StorageService,
|
||||
SettingsService
|
||||
} from '@alfresco/adf-core';
|
||||
import { Validators, FormGroup, FormBuilder } from '@angular/forms';
|
||||
import { Observable } from 'rxjs';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { AppStore } from '../../store/states';
|
||||
import { appLanguagePicker, selectHeaderColor, selectAppName, selectUser } from '../../store/selectors/app.selectors';
|
||||
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',
|
||||
templateUrl: './settings.component.html',
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
host: { class: 'aca-settings' }
|
||||
selector: 'aca-settings',
|
||||
templateUrl: './settings.component.html',
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
host: { class: 'aca-settings' }
|
||||
})
|
||||
export class SettingsComponent implements OnInit {
|
||||
private defaultPath = '/assets/images/alfresco-logo-white.svg';
|
||||
|
||||
private defaultPath = '/assets/images/alfresco-logo-white.svg';
|
||||
form: FormGroup;
|
||||
|
||||
form: FormGroup;
|
||||
profile$: Observable<ProfileState>;
|
||||
appName$: Observable<string>;
|
||||
headerColor$: Observable<string>;
|
||||
languagePicker$: Observable<boolean>;
|
||||
experimental: Array<{ key: string; value: boolean }> = [];
|
||||
|
||||
profile$: Observable<ProfileState>;
|
||||
appName$: Observable<string>;
|
||||
headerColor$: Observable<string>;
|
||||
languagePicker$: Observable<boolean>;
|
||||
experimental: Array<{ key: string, value: boolean }> = [];
|
||||
constructor(
|
||||
private store: Store<AppStore>,
|
||||
private appConfig: AppConfigService,
|
||||
private settingsService: SettingsService,
|
||||
private storage: StorageService,
|
||||
private fb: FormBuilder
|
||||
) {
|
||||
this.profile$ = store.select(selectUser);
|
||||
this.appName$ = store.select(selectAppName);
|
||||
this.languagePicker$ = store.select(appLanguagePicker);
|
||||
this.headerColor$ = store.select(selectHeaderColor);
|
||||
}
|
||||
|
||||
constructor(
|
||||
private store: Store<AppStore>,
|
||||
private appConfig: AppConfigService,
|
||||
private settingsService: SettingsService,
|
||||
private storage: StorageService,
|
||||
private fb: FormBuilder) {
|
||||
this.profile$ = store.select(selectUser);
|
||||
this.appName$ = store.select(selectAppName);
|
||||
this.languagePicker$ = store.select(appLanguagePicker);
|
||||
this.headerColor$ = store.select(selectHeaderColor);
|
||||
}
|
||||
get logo() {
|
||||
return this.appConfig.get('application.logo', this.defaultPath);
|
||||
}
|
||||
|
||||
get logo() {
|
||||
return this.appConfig.get('application.logo', this.defaultPath);
|
||||
ngOnInit() {
|
||||
this.form = this.fb.group({
|
||||
ecmHost: [
|
||||
'',
|
||||
[Validators.required, Validators.pattern('^(http|https)://.*[^/]$')]
|
||||
]
|
||||
});
|
||||
|
||||
this.reset();
|
||||
|
||||
const settings = this.appConfig.get('experimental');
|
||||
this.experimental = Object.keys(settings).map(key => {
|
||||
const value = this.appConfig.get(`experimental.${key}`);
|
||||
return {
|
||||
key,
|
||||
value: value === true || value === 'true'
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
apply(model: any, isValid: boolean) {
|
||||
if (isValid) {
|
||||
this.storage.setItem('ecmHost', model.ecmHost);
|
||||
// window.location.reload(true);
|
||||
}
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.form = this.fb.group({
|
||||
ecmHost: ['', [Validators.required, Validators.pattern('^(http|https):\/\/.*[^/]$')]]
|
||||
});
|
||||
reset() {
|
||||
this.form.reset({
|
||||
ecmHost: this.storage.getItem('ecmHost') || this.settingsService.ecmHost
|
||||
});
|
||||
}
|
||||
|
||||
this.reset();
|
||||
onLanguagePickerValueChanged(event: MatCheckboxChange) {
|
||||
this.storage.setItem('languagePicker', event.checked.toString());
|
||||
this.store.dispatch(new SetLanguagePickerAction(event.checked));
|
||||
}
|
||||
|
||||
const settings = this.appConfig.get('experimental');
|
||||
this.experimental = Object.keys(settings).map(key => {
|
||||
const value = this.appConfig.get(`experimental.${key}`);
|
||||
return {
|
||||
key,
|
||||
value: (value === true || value === 'true')
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
apply(model: any, isValid: boolean) {
|
||||
if (isValid) {
|
||||
this.storage.setItem('ecmHost', model.ecmHost);
|
||||
// window.location.reload(true);
|
||||
}
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.form.reset({
|
||||
ecmHost: this.storage.getItem('ecmHost') || this.settingsService.ecmHost
|
||||
});
|
||||
}
|
||||
|
||||
onLanguagePickerValueChanged(event: MatCheckboxChange) {
|
||||
this.storage.setItem('languagePicker', event.checked.toString());
|
||||
this.store.dispatch(new SetLanguagePickerAction(event.checked));
|
||||
}
|
||||
|
||||
onToggleExperimentalFeature(key: string, event: MatCheckboxChange) {
|
||||
this.storage.setItem(`experimental.${key}`, event.checked.toString());
|
||||
}
|
||||
onToggleExperimentalFeature(key: string, event: MatCheckboxChange) {
|
||||
this.storage.setItem(`experimental.${key}`, event.checked.toString());
|
||||
}
|
||||
}
|
||||
|
@ -30,18 +30,14 @@ import { CommonModule } from '@angular/common';
|
||||
import { CoreModule } from '@alfresco/adf-core';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: '',
|
||||
component: SettingsComponent
|
||||
}
|
||||
{
|
||||
path: '',
|
||||
component: SettingsComponent
|
||||
}
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
CommonModule,
|
||||
CoreModule.forChild(),
|
||||
RouterModule.forChild(routes)
|
||||
],
|
||||
declarations: [SettingsComponent]
|
||||
imports: [CommonModule, CoreModule.forChild(), RouterModule.forChild(routes)],
|
||||
declarations: [SettingsComponent]
|
||||
})
|
||||
export class AppSettingsModule {}
|
||||
|
@ -26,8 +26,12 @@
|
||||
import { TestBed, ComponentFixture } from '@angular/core/testing';
|
||||
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
||||
import {
|
||||
AlfrescoApiService,
|
||||
TimeAgoPipe, NodeNameTooltipPipe, NodeFavoriteDirective, DataTableComponent, AppConfigPipe
|
||||
AlfrescoApiService,
|
||||
TimeAgoPipe,
|
||||
NodeNameTooltipPipe,
|
||||
NodeFavoriteDirective,
|
||||
DataTableComponent,
|
||||
AppConfigPipe
|
||||
} from '@alfresco/adf-core';
|
||||
import { DocumentListComponent } from '@alfresco/adf-content-services';
|
||||
import { ContentManagementService } from '../../services/content-management.service';
|
||||
@ -36,86 +40,87 @@ import { AppTestingModule } from '../../testing/app-testing.module';
|
||||
import { ExperimentalDirective } from '../../directives/experimental.directive';
|
||||
|
||||
describe('SharedFilesComponent', () => {
|
||||
let fixture: ComponentFixture<SharedFilesComponent>;
|
||||
let component: SharedFilesComponent;
|
||||
let contentService: ContentManagementService;
|
||||
let alfrescoApi: AlfrescoApiService;
|
||||
let page;
|
||||
let fixture: ComponentFixture<SharedFilesComponent>;
|
||||
let component: SharedFilesComponent;
|
||||
let contentService: ContentManagementService;
|
||||
let alfrescoApi: AlfrescoApiService;
|
||||
let page;
|
||||
|
||||
beforeEach(() => {
|
||||
page = {
|
||||
list: {
|
||||
entries: [{ entry: { id: 1 } }, { entry: { id: 2 } }],
|
||||
pagination: { data: 'data' }
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [AppTestingModule],
|
||||
declarations: [
|
||||
DataTableComponent,
|
||||
TimeAgoPipe,
|
||||
NodeNameTooltipPipe,
|
||||
NodeFavoriteDirective,
|
||||
DocumentListComponent,
|
||||
SharedFilesComponent,
|
||||
AppConfigPipe,
|
||||
ExperimentalDirective
|
||||
],
|
||||
schemas: [NO_ERRORS_SCHEMA]
|
||||
});
|
||||
|
||||
fixture = TestBed.createComponent(SharedFilesComponent);
|
||||
component = fixture.componentInstance;
|
||||
|
||||
contentService = TestBed.get(ContentManagementService);
|
||||
alfrescoApi = TestBed.get(AlfrescoApiService);
|
||||
alfrescoApi.reset();
|
||||
|
||||
spyOn(alfrescoApi.sharedLinksApi, 'findSharedLinks').and.returnValue(
|
||||
Promise.resolve(page)
|
||||
);
|
||||
});
|
||||
|
||||
describe('OnInit', () => {
|
||||
beforeEach(() => {
|
||||
page = {
|
||||
list: {
|
||||
entries: [ { entry: { id: 1 } }, { entry: { id: 2 } } ],
|
||||
pagination: { data: 'data'}
|
||||
}
|
||||
};
|
||||
spyOn(component, 'reload').and.callFake(val => val);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed
|
||||
.configureTestingModule({
|
||||
imports: [ AppTestingModule ],
|
||||
declarations: [
|
||||
DataTableComponent,
|
||||
TimeAgoPipe,
|
||||
NodeNameTooltipPipe,
|
||||
NodeFavoriteDirective,
|
||||
DocumentListComponent,
|
||||
SharedFilesComponent,
|
||||
AppConfigPipe,
|
||||
ExperimentalDirective
|
||||
],
|
||||
schemas: [ NO_ERRORS_SCHEMA ]
|
||||
});
|
||||
it('should refresh on deleteNode event', () => {
|
||||
fixture.detectChanges();
|
||||
|
||||
fixture = TestBed.createComponent(SharedFilesComponent);
|
||||
component = fixture.componentInstance;
|
||||
contentService.nodesDeleted.next();
|
||||
|
||||
contentService = TestBed.get(ContentManagementService);
|
||||
alfrescoApi = TestBed.get(AlfrescoApiService);
|
||||
alfrescoApi.reset();
|
||||
|
||||
spyOn(alfrescoApi.sharedLinksApi, 'findSharedLinks').and.returnValue(Promise.resolve(page));
|
||||
expect(component.reload).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
describe('OnInit', () => {
|
||||
beforeEach(() => {
|
||||
spyOn(component, 'reload').and.callFake(val => val);
|
||||
});
|
||||
it('should refresh on restoreNode event', () => {
|
||||
fixture.detectChanges();
|
||||
|
||||
it('should refresh on deleteNode event', () => {
|
||||
fixture.detectChanges();
|
||||
contentService.nodesRestored.next();
|
||||
|
||||
contentService.nodesDeleted.next();
|
||||
|
||||
expect(component.reload).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should refresh on restoreNode event', () => {
|
||||
fixture.detectChanges();
|
||||
|
||||
contentService.nodesRestored.next();
|
||||
|
||||
expect(component.reload).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should reload on move node event', () => {
|
||||
fixture.detectChanges();
|
||||
|
||||
contentService.nodesMoved.next();
|
||||
|
||||
expect(component.reload).toHaveBeenCalled();
|
||||
});
|
||||
expect(component.reload).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
describe('refresh', () => {
|
||||
it('should call document list reload', () => {
|
||||
spyOn(component.documentList, 'reload');
|
||||
fixture.detectChanges();
|
||||
it('should reload on move node event', () => {
|
||||
fixture.detectChanges();
|
||||
|
||||
component.reload();
|
||||
contentService.nodesMoved.next();
|
||||
|
||||
expect(component.documentList.reload).toHaveBeenCalled();
|
||||
});
|
||||
expect(component.reload).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('refresh', () => {
|
||||
it('should call document list reload', () => {
|
||||
spyOn(component.documentList, 'reload');
|
||||
fixture.detectChanges();
|
||||
|
||||
component.reload();
|
||||
|
||||
expect(component.documentList.reload).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -32,37 +32,34 @@ import { AppStore } from '../../store/states/app.state';
|
||||
import { AppExtensionService } from '../../extensions/extension.service';
|
||||
|
||||
@Component({
|
||||
templateUrl: './shared-files.component.html'
|
||||
templateUrl: './shared-files.component.html'
|
||||
})
|
||||
export class SharedFilesComponent extends PageComponent implements OnInit {
|
||||
isSmallScreen = false;
|
||||
isSmallScreen = false;
|
||||
|
||||
constructor(
|
||||
store: Store<AppStore>,
|
||||
extensions: AppExtensionService,
|
||||
content: ContentManagementService,
|
||||
private breakpointObserver: BreakpointObserver
|
||||
) {
|
||||
super(store, extensions, content);
|
||||
}
|
||||
constructor(
|
||||
store: Store<AppStore>,
|
||||
extensions: AppExtensionService,
|
||||
content: ContentManagementService,
|
||||
private breakpointObserver: BreakpointObserver
|
||||
) {
|
||||
super(store, extensions, content);
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
super.ngOnInit();
|
||||
ngOnInit() {
|
||||
super.ngOnInit();
|
||||
|
||||
this.subscriptions = this.subscriptions.concat([
|
||||
this.content.nodesDeleted.subscribe(() => this.reload()),
|
||||
this.content.nodesMoved.subscribe(() => this.reload()),
|
||||
this.content.nodesRestored.subscribe(() => this.reload()),
|
||||
this.content.linksUnshared.subscribe(() => this.reload()),
|
||||
this.subscriptions = this.subscriptions.concat([
|
||||
this.content.nodesDeleted.subscribe(() => this.reload()),
|
||||
this.content.nodesMoved.subscribe(() => this.reload()),
|
||||
this.content.nodesRestored.subscribe(() => this.reload()),
|
||||
this.content.linksUnshared.subscribe(() => this.reload()),
|
||||
|
||||
this.breakpointObserver
|
||||
.observe([
|
||||
Breakpoints.HandsetPortrait,
|
||||
Breakpoints.HandsetLandscape
|
||||
])
|
||||
.subscribe(result => {
|
||||
this.isSmallScreen = result.matches;
|
||||
})
|
||||
]);
|
||||
}
|
||||
this.breakpointObserver
|
||||
.observe([Breakpoints.HandsetPortrait, Breakpoints.HandsetLandscape])
|
||||
.subscribe(result => {
|
||||
this.isSmallScreen = result.matches;
|
||||
})
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
.app-shared-link-view {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
@ -2,23 +2,21 @@ import { Component, ViewEncapsulation, OnInit } from '@angular/core';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
|
||||
@Component({
|
||||
selector: 'app-shared-link-view',
|
||||
templateUrl: 'shared-link-view.component.html',
|
||||
styleUrls: [ 'shared-link-view.component.scss' ],
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
// tslint:disable-next-line:use-host-property-decorator
|
||||
host: { 'class': 'app-shared-link-view' }
|
||||
selector: 'app-shared-link-view',
|
||||
templateUrl: 'shared-link-view.component.html',
|
||||
styleUrls: ['shared-link-view.component.scss'],
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
// tslint:disable-next-line:use-host-property-decorator
|
||||
host: { class: 'app-shared-link-view' }
|
||||
})
|
||||
export class SharedLinkViewComponent implements OnInit {
|
||||
sharedLinkId: string = null;
|
||||
|
||||
sharedLinkId: string = null;
|
||||
|
||||
constructor(private route: ActivatedRoute) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.route.params.subscribe(params => {
|
||||
this.sharedLinkId = params.id;
|
||||
});
|
||||
}
|
||||
constructor(private route: ActivatedRoute) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.route.params.subscribe(params => {
|
||||
this.sharedLinkId = params.id;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -1,51 +1,51 @@
|
||||
.sidenav {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
|
||||
&__section:last-child {
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
&_action-menu {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
padding: 16px 24px;
|
||||
height: 40px;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
&__section {
|
||||
padding: 8px 14px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
&-menu {
|
||||
display: inline-flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
&__section:last-child {
|
||||
border-bottom: 0;
|
||||
}
|
||||
&-menu__item {
|
||||
padding: 12px 0;
|
||||
flex-direction: row;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
text-decoration: none;
|
||||
text-decoration: none;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
&_action-menu {
|
||||
display: flex;
|
||||
padding: 16px 24px;
|
||||
height: 40px;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
.menu__item--label {
|
||||
cursor: pointer;
|
||||
width: 240px;
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
&__section {
|
||||
padding: 8px 14px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
&-menu {
|
||||
display: inline-flex;
|
||||
flex-direction: column;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
&-menu__item {
|
||||
padding: 12px 0;
|
||||
flex-direction: row;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
text-decoration: none;
|
||||
text-decoration: none;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
.menu__item--label {
|
||||
cursor: pointer;
|
||||
width: 240px;
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
.menu__item--label:focus {
|
||||
outline: none;
|
||||
}
|
||||
.menu__item--label:focus {
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
|
@ -32,29 +32,23 @@ import { AppTestingModule } from '../../testing/app-testing.module';
|
||||
import { ExperimentalDirective } from '../../directives/experimental.directive';
|
||||
|
||||
describe('SidenavComponent', () => {
|
||||
let fixture: ComponentFixture<SidenavComponent>;
|
||||
let component: SidenavComponent;
|
||||
let fixture: ComponentFixture<SidenavComponent>;
|
||||
let component: SidenavComponent;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
AppTestingModule,
|
||||
EffectsModule.forRoot([NodeEffects])
|
||||
],
|
||||
declarations: [
|
||||
SidenavComponent,
|
||||
ExperimentalDirective
|
||||
],
|
||||
schemas: [ NO_ERRORS_SCHEMA ]
|
||||
})
|
||||
.compileComponents()
|
||||
.then(() => {
|
||||
fixture = TestBed.createComponent(SidenavComponent);
|
||||
component = fixture.componentInstance;
|
||||
});
|
||||
}));
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [AppTestingModule, EffectsModule.forRoot([NodeEffects])],
|
||||
declarations: [SidenavComponent, ExperimentalDirective],
|
||||
schemas: [NO_ERRORS_SCHEMA]
|
||||
})
|
||||
.compileComponents()
|
||||
.then(() => {
|
||||
fixture = TestBed.createComponent(SidenavComponent);
|
||||
component = fixture.componentInstance;
|
||||
});
|
||||
}));
|
||||
|
||||
it('should be created', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
it('should be created', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
@ -1,35 +1,34 @@
|
||||
@mixin sidenav-component-theme($theme) {
|
||||
$primary: map-get($theme, primary);
|
||||
$accent: map-get($theme, accent);
|
||||
$foreground: map-get($theme, foreground);
|
||||
$background: map-get($theme, background);
|
||||
$primary: map-get($theme, primary);
|
||||
$accent: map-get($theme, accent);
|
||||
$foreground: map-get($theme, foreground);
|
||||
$background: map-get($theme, background);
|
||||
|
||||
$border: 1px solid mat-color($foreground, divider, .07);
|
||||
$border: 1px solid mat-color($foreground, divider, 0.07);
|
||||
|
||||
.sidenav {
|
||||
@include angular-material-theme($theme);
|
||||
.sidenav {
|
||||
@include angular-material-theme($theme);
|
||||
|
||||
background-color: mat-color($background, background);
|
||||
background-color: mat-color($background, background);
|
||||
|
||||
.adf-sidebar-action-menu-button {
|
||||
background-color: mat-color($accent);
|
||||
}
|
||||
.adf-sidebar-action-menu-button {
|
||||
background-color: mat-color($accent);
|
||||
}
|
||||
|
||||
&__section {
|
||||
border-bottom: $border;
|
||||
}
|
||||
&__section {
|
||||
border-bottom: $border;
|
||||
}
|
||||
|
||||
.menu__item--label:not(.menu__item--active):hover {
|
||||
color: mat-color($foreground, text);
|
||||
}
|
||||
.menu__item--label:not(.menu__item--active):hover {
|
||||
color: mat-color($foreground, text);
|
||||
}
|
||||
|
||||
.menu__item--active {
|
||||
color: mat-color($accent);
|
||||
}
|
||||
|
||||
.menu__item--default {
|
||||
color: mat-color($primary, .87);
|
||||
}
|
||||
.menu__item--active {
|
||||
color: mat-color($accent);
|
||||
}
|
||||
|
||||
.menu__item--default {
|
||||
color: mat-color($primary, 0.87);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -33,39 +33,40 @@ import { takeUntil } from 'rxjs/operators';
|
||||
import { ContentActionRef, NavBarGroupRef } from '@alfresco/adf-extensions';
|
||||
|
||||
@Component({
|
||||
selector: 'app-sidenav',
|
||||
templateUrl: './sidenav.component.html',
|
||||
styleUrls: ['./sidenav.component.scss']
|
||||
selector: 'app-sidenav',
|
||||
templateUrl: './sidenav.component.html',
|
||||
styleUrls: ['./sidenav.component.scss']
|
||||
})
|
||||
export class SidenavComponent implements OnInit, OnDestroy {
|
||||
@Input() showLabel: boolean;
|
||||
@Input()
|
||||
showLabel: boolean;
|
||||
|
||||
groups: Array<NavBarGroupRef> = [];
|
||||
createActions: Array<ContentActionRef> = [];
|
||||
onDestroy$: Subject<boolean> = new Subject<boolean>();
|
||||
groups: Array<NavBarGroupRef> = [];
|
||||
createActions: Array<ContentActionRef> = [];
|
||||
onDestroy$: Subject<boolean> = new Subject<boolean>();
|
||||
|
||||
constructor(
|
||||
private store: Store<AppStore>,
|
||||
private extensions: AppExtensionService
|
||||
) {}
|
||||
constructor(
|
||||
private store: Store<AppStore>,
|
||||
private extensions: AppExtensionService
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.groups = this.extensions.getNavigationGroups();
|
||||
ngOnInit() {
|
||||
this.groups = this.extensions.getNavigationGroups();
|
||||
|
||||
this.store
|
||||
.select(currentFolder)
|
||||
.pipe(takeUntil(this.onDestroy$))
|
||||
.subscribe(() => {
|
||||
this.createActions = this.extensions.getCreateActions();
|
||||
});
|
||||
}
|
||||
this.store
|
||||
.select(currentFolder)
|
||||
.pipe(takeUntil(this.onDestroy$))
|
||||
.subscribe(() => {
|
||||
this.createActions = this.extensions.getCreateActions();
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.onDestroy$.next(true);
|
||||
this.onDestroy$.complete();
|
||||
}
|
||||
ngOnDestroy() {
|
||||
this.onDestroy$.next(true);
|
||||
this.onDestroy$.complete();
|
||||
}
|
||||
|
||||
trackById(index: number, obj: { id: string }) {
|
||||
return obj.id;
|
||||
}
|
||||
trackById(index: number, obj: { id: string }) {
|
||||
return obj.id;
|
||||
}
|
||||
}
|
||||
|
@ -31,26 +31,25 @@ import { documentDisplayMode } from '../../../store/selectors/app.selectors';
|
||||
import { ToggleDocumentDisplayMode } from '../../../store/actions';
|
||||
|
||||
@Component({
|
||||
selector: 'app-document-display-mode',
|
||||
template: `
|
||||
<button
|
||||
mat-icon-button
|
||||
color="primary"
|
||||
(click)="onClick()">
|
||||
<mat-icon *ngIf="(displayMode$ | async) === 'list'">view_comfy</mat-icon>
|
||||
<mat-icon *ngIf="(displayMode$ | async) === 'gallery'">list</mat-icon>
|
||||
</button>
|
||||
`
|
||||
selector: 'app-document-display-mode',
|
||||
template: `
|
||||
<button
|
||||
mat-icon-button
|
||||
color="primary"
|
||||
(click)="onClick()">
|
||||
<mat-icon *ngIf="(displayMode$ | async) === 'list'">view_comfy</mat-icon>
|
||||
<mat-icon *ngIf="(displayMode$ | async) === 'gallery'">list</mat-icon>
|
||||
</button>
|
||||
`
|
||||
})
|
||||
export class DocumentDisplayModeComponent {
|
||||
displayMode$: Observable<string>;
|
||||
|
||||
displayMode$: Observable<string>;
|
||||
constructor(private store: Store<AppStore>) {
|
||||
this.displayMode$ = store.select(documentDisplayMode);
|
||||
}
|
||||
|
||||
constructor(private store: Store<AppStore>) {
|
||||
this.displayMode$ = store.select(documentDisplayMode);
|
||||
}
|
||||
|
||||
onClick() {
|
||||
this.store.dispatch(new ToggleDocumentDisplayMode());
|
||||
}
|
||||
onClick() {
|
||||
this.store.dispatch(new ToggleDocumentDisplayMode());
|
||||
}
|
||||
}
|
||||
|
@ -32,8 +32,8 @@ import { SelectionState } from '@alfresco/adf-extensions';
|
||||
import { ContentManagementService } from '../../../services/content-management.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-toggle-favorite',
|
||||
template: `
|
||||
selector: 'app-toggle-favorite',
|
||||
template: `
|
||||
<button
|
||||
mat-menu-item
|
||||
#favorites="adfFavorite"
|
||||
@ -46,16 +46,16 @@ import { ContentManagementService } from '../../../services/content-management.s
|
||||
`
|
||||
})
|
||||
export class ToggleFavoriteComponent {
|
||||
selection$: Observable<SelectionState>;
|
||||
|
||||
selection$: Observable<SelectionState>;
|
||||
constructor(
|
||||
private store: Store<AppStore>,
|
||||
private content: ContentManagementService
|
||||
) {
|
||||
this.selection$ = this.store.select(appSelection);
|
||||
}
|
||||
|
||||
constructor(
|
||||
private store: Store<AppStore>,
|
||||
private content: ContentManagementService) {
|
||||
this.selection$ = this.store.select(appSelection);
|
||||
}
|
||||
|
||||
onToggleEvent() {
|
||||
this.content.favoriteToggle.next();
|
||||
}
|
||||
onToggleEvent() {
|
||||
this.content.favoriteToggle.next();
|
||||
}
|
||||
}
|
||||
|
@ -31,8 +31,8 @@ import { infoDrawerOpened } from '../../../store/selectors/app.selectors';
|
||||
import { ToggleInfoDrawerAction } from '../../../store/actions';
|
||||
|
||||
@Component({
|
||||
selector: 'app-toggle-info-drawer',
|
||||
template: `
|
||||
selector: 'app-toggle-info-drawer',
|
||||
template: `
|
||||
<button
|
||||
mat-icon-button
|
||||
[color]="(infoDrawerOpened$ | async) ? 'accent' : 'primary'"
|
||||
@ -43,13 +43,13 @@ import { ToggleInfoDrawerAction } from '../../../store/actions';
|
||||
`
|
||||
})
|
||||
export class ToggleInfoDrawerComponent {
|
||||
infoDrawerOpened$: Observable<boolean>;
|
||||
infoDrawerOpened$: Observable<boolean>;
|
||||
|
||||
constructor(private store: Store<AppStore>) {
|
||||
this.infoDrawerOpened$ = this.store.select(infoDrawerOpened);
|
||||
}
|
||||
constructor(private store: Store<AppStore>) {
|
||||
this.infoDrawerOpened$ = this.store.select(infoDrawerOpened);
|
||||
}
|
||||
|
||||
onClick() {
|
||||
this.store.dispatch(new ToggleInfoDrawerAction());
|
||||
}
|
||||
onClick() {
|
||||
this.store.dispatch(new ToggleInfoDrawerAction());
|
||||
}
|
||||
}
|
||||
|
@ -24,10 +24,10 @@
|
||||
*/
|
||||
|
||||
import {
|
||||
Component,
|
||||
ViewEncapsulation,
|
||||
ChangeDetectionStrategy,
|
||||
Input
|
||||
Component,
|
||||
ViewEncapsulation,
|
||||
ChangeDetectionStrategy,
|
||||
Input
|
||||
} from '@angular/core';
|
||||
import { AppStore } from '../../../store/states';
|
||||
import { Store } from '@ngrx/store';
|
||||
@ -35,22 +35,24 @@ import { ContentActionRef } from '@alfresco/adf-extensions';
|
||||
import { AppExtensionService } from '../../../extensions/extension.service';
|
||||
|
||||
@Component({
|
||||
selector: 'aca-toolbar-action',
|
||||
templateUrl: './toolbar-action.component.html',
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
host: { class: 'aca-toolbar-action' }
|
||||
selector: 'aca-toolbar-action',
|
||||
templateUrl: './toolbar-action.component.html',
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
host: { class: 'aca-toolbar-action' }
|
||||
})
|
||||
export class ToolbarActionComponent {
|
||||
@Input() type = 'icon-button';
|
||||
@Input() entry: ContentActionRef;
|
||||
@Input()
|
||||
type = 'icon-button';
|
||||
@Input()
|
||||
entry: ContentActionRef;
|
||||
|
||||
constructor(
|
||||
protected store: Store<AppStore>,
|
||||
protected extensions: AppExtensionService
|
||||
) {}
|
||||
constructor(
|
||||
protected store: Store<AppStore>,
|
||||
protected extensions: AppExtensionService
|
||||
) {}
|
||||
|
||||
trackByActionId(index: number, action: ContentActionRef) {
|
||||
return action.id;
|
||||
}
|
||||
trackByActionId(index: number, action: ContentActionRef) {
|
||||
return action.id;
|
||||
}
|
||||
}
|
||||
|
@ -32,31 +32,33 @@ import { take } from 'rxjs/operators';
|
||||
import { AppExtensionService } from '../../../extensions/extension.service';
|
||||
|
||||
export enum ToolbarButtonType {
|
||||
ICON_BUTTON = 'icon-button',
|
||||
MENU_ITEM = 'menu-item'
|
||||
ICON_BUTTON = 'icon-button',
|
||||
MENU_ITEM = 'menu-item'
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'app-toolbar-button',
|
||||
templateUrl: 'toolbar-button.component.html'
|
||||
selector: 'app-toolbar-button',
|
||||
templateUrl: 'toolbar-button.component.html'
|
||||
})
|
||||
export class ToolbarButtonComponent {
|
||||
@Input() type: ToolbarButtonType = ToolbarButtonType.ICON_BUTTON;
|
||||
@Input() actionRef: ContentActionRef;
|
||||
@Input()
|
||||
type: ToolbarButtonType = ToolbarButtonType.ICON_BUTTON;
|
||||
@Input()
|
||||
actionRef: ContentActionRef;
|
||||
|
||||
constructor(
|
||||
protected store: Store<AppStore>,
|
||||
private extensions: AppExtensionService
|
||||
) {}
|
||||
constructor(
|
||||
protected store: Store<AppStore>,
|
||||
private extensions: AppExtensionService
|
||||
) {}
|
||||
|
||||
runAction() {
|
||||
this.store
|
||||
.select(appSelection)
|
||||
.pipe(take(1))
|
||||
.subscribe(selection => {
|
||||
this.extensions.runActionById(this.actionRef.actions.click, {
|
||||
selection
|
||||
});
|
||||
});
|
||||
}
|
||||
runAction() {
|
||||
this.store
|
||||
.select(appSelection)
|
||||
.pipe(take(1))
|
||||
.subscribe(selection => {
|
||||
this.extensions.runActionById(this.actionRef.actions.click, {
|
||||
selection
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -34,23 +34,19 @@ import { ToolbarActionComponent } from './toolbar-action/toolbar-action.componen
|
||||
import { ExtensionsModule } from '@alfresco/adf-extensions';
|
||||
|
||||
export function components() {
|
||||
return [
|
||||
DocumentDisplayModeComponent,
|
||||
ToggleFavoriteComponent,
|
||||
ToggleInfoDrawerComponent,
|
||||
ToolbarButtonComponent,
|
||||
ToolbarActionComponent
|
||||
];
|
||||
return [
|
||||
DocumentDisplayModeComponent,
|
||||
ToggleFavoriteComponent,
|
||||
ToggleInfoDrawerComponent,
|
||||
ToolbarButtonComponent,
|
||||
ToolbarActionComponent
|
||||
];
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
CommonModule,
|
||||
CoreModule.forChild(),
|
||||
ExtensionsModule.forChild()
|
||||
],
|
||||
declarations: components(),
|
||||
exports: components(),
|
||||
entryComponents: components()
|
||||
imports: [CommonModule, CoreModule.forChild(), ExtensionsModule.forChild()],
|
||||
declarations: components(),
|
||||
exports: components(),
|
||||
entryComponents: components()
|
||||
})
|
||||
export class AppToolbarModule {}
|
||||
|
@ -25,9 +25,12 @@
|
||||
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
||||
import { TestBed, ComponentFixture } from '@angular/core/testing';
|
||||
import {
|
||||
AlfrescoApiService,
|
||||
TimeAgoPipe, NodeNameTooltipPipe,
|
||||
NodeFavoriteDirective, DataTableComponent, AppConfigPipe
|
||||
AlfrescoApiService,
|
||||
TimeAgoPipe,
|
||||
NodeNameTooltipPipe,
|
||||
NodeFavoriteDirective,
|
||||
DataTableComponent,
|
||||
AppConfigPipe
|
||||
} from '@alfresco/adf-core';
|
||||
import { DocumentListComponent } from '@alfresco/adf-content-services';
|
||||
import { ContentManagementService } from '../../services/content-management.service';
|
||||
@ -36,74 +39,76 @@ import { AppTestingModule } from '../../testing/app-testing.module';
|
||||
import { ExperimentalDirective } from '../../directives/experimental.directive';
|
||||
|
||||
describe('TrashcanComponent', () => {
|
||||
let fixture: ComponentFixture<TrashcanComponent>;
|
||||
let component: TrashcanComponent;
|
||||
let alfrescoApi: AlfrescoApiService;
|
||||
let contentService: ContentManagementService;
|
||||
let page;
|
||||
let fixture: ComponentFixture<TrashcanComponent>;
|
||||
let component: TrashcanComponent;
|
||||
let alfrescoApi: AlfrescoApiService;
|
||||
let contentService: ContentManagementService;
|
||||
let page;
|
||||
|
||||
beforeEach(() => {
|
||||
page = {
|
||||
list: {
|
||||
entries: [ { entry: { id: 1 } }, { entry: { id: 2 } } ],
|
||||
pagination: { data: 'data'}
|
||||
}
|
||||
};
|
||||
beforeEach(() => {
|
||||
page = {
|
||||
list: {
|
||||
entries: [{ entry: { id: 1 } }, { entry: { id: 2 } }],
|
||||
pagination: { data: 'data' }
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [AppTestingModule],
|
||||
declarations: [
|
||||
DataTableComponent,
|
||||
TimeAgoPipe,
|
||||
NodeNameTooltipPipe,
|
||||
NodeFavoriteDirective,
|
||||
DocumentListComponent,
|
||||
TrashcanComponent,
|
||||
AppConfigPipe,
|
||||
ExperimentalDirective
|
||||
],
|
||||
schemas: [NO_ERRORS_SCHEMA]
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [ AppTestingModule ],
|
||||
declarations: [
|
||||
DataTableComponent,
|
||||
TimeAgoPipe,
|
||||
NodeNameTooltipPipe,
|
||||
NodeFavoriteDirective,
|
||||
DocumentListComponent,
|
||||
TrashcanComponent,
|
||||
AppConfigPipe,
|
||||
ExperimentalDirective
|
||||
],
|
||||
schemas: [ NO_ERRORS_SCHEMA ]
|
||||
});
|
||||
fixture = TestBed.createComponent(TrashcanComponent);
|
||||
component = fixture.componentInstance;
|
||||
|
||||
fixture = TestBed.createComponent(TrashcanComponent);
|
||||
component = fixture.componentInstance;
|
||||
alfrescoApi = TestBed.get(AlfrescoApiService);
|
||||
alfrescoApi.reset();
|
||||
contentService = TestBed.get(ContentManagementService);
|
||||
|
||||
alfrescoApi = TestBed.get(AlfrescoApiService);
|
||||
alfrescoApi.reset();
|
||||
contentService = TestBed.get(ContentManagementService);
|
||||
component.documentList = <any>{
|
||||
reload: jasmine.createSpy('reload'),
|
||||
resetSelection: jasmine.createSpy('resetSelection')
|
||||
};
|
||||
});
|
||||
|
||||
component.documentList = <any> {
|
||||
reload: jasmine.createSpy('reload'),
|
||||
resetSelection: jasmine.createSpy('resetSelection')
|
||||
};
|
||||
beforeEach(() => {
|
||||
spyOn(alfrescoApi.nodesApi, 'getDeletedNodes').and.returnValue(
|
||||
Promise.resolve(page)
|
||||
);
|
||||
});
|
||||
|
||||
describe('onRestoreNode()', () => {
|
||||
it('should call refresh()', () => {
|
||||
spyOn(component, 'reload');
|
||||
fixture.detectChanges();
|
||||
|
||||
contentService.nodesRestored.next();
|
||||
|
||||
expect(component.reload).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('refresh()', () => {
|
||||
it('calls child component to reload', () => {
|
||||
component.reload();
|
||||
expect(component.documentList.reload).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
spyOn(alfrescoApi.nodesApi, 'getDeletedNodes').and.returnValue(Promise.resolve(page));
|
||||
});
|
||||
|
||||
describe('onRestoreNode()', () => {
|
||||
it('should call refresh()', () => {
|
||||
spyOn(component, 'reload');
|
||||
fixture.detectChanges();
|
||||
|
||||
contentService.nodesRestored.next();
|
||||
|
||||
expect(component.reload).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('refresh()', () => {
|
||||
it('calls child component to reload', () => {
|
||||
component.reload();
|
||||
expect(component.documentList.reload).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('calls child component to reset selection', () => {
|
||||
component.reload();
|
||||
expect(component.documentList.resetSelection).toHaveBeenCalled();
|
||||
});
|
||||
it('calls child component to reset selection', () => {
|
||||
component.reload();
|
||||
expect(component.documentList.resetSelection).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -35,36 +35,35 @@ import { Observable } from 'rxjs';
|
||||
import { ProfileState } from '@alfresco/adf-extensions';
|
||||
|
||||
@Component({
|
||||
templateUrl: './trashcan.component.html'
|
||||
templateUrl: './trashcan.component.html'
|
||||
})
|
||||
export class TrashcanComponent extends PageComponent implements OnInit {
|
||||
isSmallScreen = false;
|
||||
user$: Observable<ProfileState>;
|
||||
isSmallScreen = false;
|
||||
user$: Observable<ProfileState>;
|
||||
|
||||
constructor(content: ContentManagementService,
|
||||
extensions: AppExtensionService,
|
||||
store: Store<AppStore>,
|
||||
private breakpointObserver: BreakpointObserver) {
|
||||
super(store, extensions, content);
|
||||
this.user$ = this.store.select(selectUser);
|
||||
}
|
||||
constructor(
|
||||
content: ContentManagementService,
|
||||
extensions: AppExtensionService,
|
||||
store: Store<AppStore>,
|
||||
private breakpointObserver: BreakpointObserver
|
||||
) {
|
||||
super(store, extensions, content);
|
||||
this.user$ = this.store.select(selectUser);
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
super.ngOnInit();
|
||||
ngOnInit() {
|
||||
super.ngOnInit();
|
||||
|
||||
this.subscriptions.push(
|
||||
this.content.nodesRestored.subscribe(() => this.reload()),
|
||||
this.content.nodesPurged.subscribe(() => this.reload()),
|
||||
this.content.nodesRestored.subscribe(() => this.reload()),
|
||||
this.subscriptions.push(
|
||||
this.content.nodesRestored.subscribe(() => this.reload()),
|
||||
this.content.nodesPurged.subscribe(() => this.reload()),
|
||||
this.content.nodesRestored.subscribe(() => this.reload()),
|
||||
|
||||
this.breakpointObserver
|
||||
.observe([
|
||||
Breakpoints.HandsetPortrait,
|
||||
Breakpoints.HandsetLandscape
|
||||
])
|
||||
.subscribe(result => {
|
||||
this.isSmallScreen = result.matches;
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
this.breakpointObserver
|
||||
.observe([Breakpoints.HandsetPortrait, Breakpoints.HandsetLandscape])
|
||||
.subscribe(result => {
|
||||
this.isSmallScreen = result.matches;
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -19,33 +19,35 @@ import { AbstractControl, FormControl } from '@angular/forms';
|
||||
import { ContentApiService } from '../../services/content-api.service';
|
||||
|
||||
export class SiteIdValidator {
|
||||
static createValidator(contentApiService: ContentApiService) {
|
||||
let timer;
|
||||
static createValidator(contentApiService: ContentApiService) {
|
||||
let timer;
|
||||
|
||||
return (control: AbstractControl) => {
|
||||
if (timer) {
|
||||
clearTimeout(timer);
|
||||
}
|
||||
return (control: AbstractControl) => {
|
||||
if (timer) {
|
||||
clearTimeout(timer);
|
||||
}
|
||||
|
||||
return new Promise(resolve => {
|
||||
timer = setTimeout(() => {
|
||||
return contentApiService
|
||||
.getSite(control.value)
|
||||
.subscribe(
|
||||
() => resolve({ message: 'LIBRARY.ERRORS.EXISTENT_SITE' }),
|
||||
() => resolve(null)
|
||||
);
|
||||
}, 300);
|
||||
});
|
||||
};
|
||||
}
|
||||
return new Promise(resolve => {
|
||||
timer = setTimeout(() => {
|
||||
return contentApiService
|
||||
.getSite(control.value)
|
||||
.subscribe(
|
||||
() => resolve({ message: 'LIBRARY.ERRORS.EXISTENT_SITE' }),
|
||||
() => resolve(null)
|
||||
);
|
||||
}, 300);
|
||||
});
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export function forbidSpecialCharacters({ value }: FormControl) {
|
||||
const validCharacters: RegExp = /[^A-Za-z0-9-]/;
|
||||
const isValid: boolean = !validCharacters.test(value);
|
||||
const validCharacters: RegExp = /[^A-Za-z0-9-]/;
|
||||
const isValid: boolean = !validCharacters.test(value);
|
||||
|
||||
return (isValid) ? null : {
|
||||
return isValid
|
||||
? null
|
||||
: {
|
||||
message: 'LIBRARY.ERRORS.ILLEGAL_CHARACTERS'
|
||||
};
|
||||
};
|
||||
}
|
||||
|
@ -1,20 +1,19 @@
|
||||
|
||||
.mat-radio-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin: 0 0 20px 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin: 0 0 20px 0;
|
||||
}
|
||||
|
||||
.mat-radio-group .mat-radio-button {
|
||||
margin: 10px 0;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.mat-form-field {
|
||||
width: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.actions-buttons {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-end;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
@ -24,129 +24,139 @@ import { ContentApiService } from '../../services/content-api.service';
|
||||
import { SiteIdValidator, forbidSpecialCharacters } from './form.validators';
|
||||
import { debounceTime } from 'rxjs/operators';
|
||||
|
||||
|
||||
@Component({
|
||||
selector: 'app-library-dialog',
|
||||
styleUrls: ['./library.dialog.scss'],
|
||||
templateUrl: './library.dialog.html'
|
||||
selector: 'app-library-dialog',
|
||||
styleUrls: ['./library.dialog.scss'],
|
||||
templateUrl: './library.dialog.html'
|
||||
})
|
||||
export class LibraryDialogComponent implements OnInit {
|
||||
@Output()
|
||||
error: EventEmitter<any> = new EventEmitter<any>();
|
||||
@Output()
|
||||
error: EventEmitter<any> = new EventEmitter<any>();
|
||||
|
||||
@Output()
|
||||
success: EventEmitter<any> = new EventEmitter<any>();
|
||||
@Output()
|
||||
success: EventEmitter<any> = new EventEmitter<any>();
|
||||
|
||||
createTitle = 'LIBRARY.DIALOG.CREATE_TITLE';
|
||||
form: FormGroup;
|
||||
visibilityOption: any;
|
||||
visibilityOptions = [
|
||||
{ value: 'PUBLIC', label: 'LIBRARY.VISIBILITY.PUBLIC', disabled: false },
|
||||
{ value: 'PRIVATE', label: 'LIBRARY.VISIBILITY.PRIVATE', disabled: false },
|
||||
{ value: 'MODERATED', label: 'LIBRARY.VISIBILITY.MODERATED', disabled: false }
|
||||
];
|
||||
|
||||
constructor(
|
||||
private formBuilder: FormBuilder,
|
||||
private dialog: MatDialogRef<LibraryDialogComponent>,
|
||||
private contentApi: ContentApiService
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
const validators = {
|
||||
id: [ Validators.required, Validators.maxLength(72), forbidSpecialCharacters ],
|
||||
title: [ Validators.required, Validators.maxLength(256) ],
|
||||
description: [ Validators.maxLength(512) ]
|
||||
};
|
||||
|
||||
this.form = this.formBuilder.group({
|
||||
title: ['', validators.title ],
|
||||
id: [ '', validators.id, SiteIdValidator.createValidator(this.contentApi) ],
|
||||
description: [ '', validators.description ],
|
||||
});
|
||||
|
||||
this.visibilityOption = this.visibilityOptions[0].value;
|
||||
|
||||
this.form.controls['title'].valueChanges
|
||||
.pipe(debounceTime(300))
|
||||
.subscribe((titleValue: string) => {
|
||||
if (!titleValue.trim().length) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.form.controls['id'].dirty) {
|
||||
this.form.patchValue({ id: this.sanitize(titleValue.trim()) });
|
||||
this.form.controls['id'].markAsTouched();
|
||||
}
|
||||
});
|
||||
createTitle = 'LIBRARY.DIALOG.CREATE_TITLE';
|
||||
form: FormGroup;
|
||||
visibilityOption: any;
|
||||
visibilityOptions = [
|
||||
{ value: 'PUBLIC', label: 'LIBRARY.VISIBILITY.PUBLIC', disabled: false },
|
||||
{ value: 'PRIVATE', label: 'LIBRARY.VISIBILITY.PRIVATE', disabled: false },
|
||||
{
|
||||
value: 'MODERATED',
|
||||
label: 'LIBRARY.VISIBILITY.MODERATED',
|
||||
disabled: false
|
||||
}
|
||||
];
|
||||
|
||||
get title(): string {
|
||||
const { title } = this.form.value;
|
||||
constructor(
|
||||
private formBuilder: FormBuilder,
|
||||
private dialog: MatDialogRef<LibraryDialogComponent>,
|
||||
private contentApi: ContentApiService
|
||||
) {}
|
||||
|
||||
return (title || '').trim();
|
||||
}
|
||||
ngOnInit() {
|
||||
const validators = {
|
||||
id: [
|
||||
Validators.required,
|
||||
Validators.maxLength(72),
|
||||
forbidSpecialCharacters
|
||||
],
|
||||
title: [Validators.required, Validators.maxLength(256)],
|
||||
description: [Validators.maxLength(512)]
|
||||
};
|
||||
|
||||
get id(): string {
|
||||
const { id } = this.form.value;
|
||||
this.form = this.formBuilder.group({
|
||||
title: ['', validators.title],
|
||||
id: ['', validators.id, SiteIdValidator.createValidator(this.contentApi)],
|
||||
description: ['', validators.description]
|
||||
});
|
||||
|
||||
return (id || '').trim();
|
||||
}
|
||||
this.visibilityOption = this.visibilityOptions[0].value;
|
||||
|
||||
get description(): string {
|
||||
const { description } = this.form.value;
|
||||
|
||||
return (description || '').trim();
|
||||
}
|
||||
|
||||
get visibility(): string {
|
||||
return this.visibilityOption || '';
|
||||
}
|
||||
|
||||
submit() {
|
||||
const { form, dialog } = this;
|
||||
|
||||
if (!form.valid) { return; }
|
||||
|
||||
this.create().subscribe(
|
||||
(node: SiteEntry) => {
|
||||
|
||||
this.success.emit(node);
|
||||
dialog.close(node);
|
||||
},
|
||||
(error) => this.handleError(error)
|
||||
);
|
||||
}
|
||||
|
||||
visibilityChangeHandler(event) {
|
||||
this.visibilityOption = event.value;
|
||||
}
|
||||
|
||||
private create(): Observable<SiteEntry> {
|
||||
const { contentApi, title, id, description, visibility } = this;
|
||||
const siteBody = <SiteBody>{
|
||||
id,
|
||||
title,
|
||||
description,
|
||||
visibility
|
||||
};
|
||||
|
||||
return contentApi.createSite(siteBody);
|
||||
}
|
||||
|
||||
private sanitize(input: string) {
|
||||
return input
|
||||
.replace(/[\s]/g, '-')
|
||||
.replace(/[^A-Za-z0-9-]/g, '');
|
||||
}
|
||||
|
||||
private handleError(error: any): any {
|
||||
const { error: { statusCode } } = JSON.parse(error.message);
|
||||
|
||||
if (statusCode === 409) {
|
||||
this.form.controls['id'].setErrors({ message: 'LIBRARY.ERRORS.CONFLICT' });
|
||||
this.form.controls['title'].valueChanges
|
||||
.pipe(debounceTime(300))
|
||||
.subscribe((titleValue: string) => {
|
||||
if (!titleValue.trim().length) {
|
||||
return;
|
||||
}
|
||||
|
||||
return error;
|
||||
if (!this.form.controls['id'].dirty) {
|
||||
this.form.patchValue({ id: this.sanitize(titleValue.trim()) });
|
||||
this.form.controls['id'].markAsTouched();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
get title(): string {
|
||||
const { title } = this.form.value;
|
||||
|
||||
return (title || '').trim();
|
||||
}
|
||||
|
||||
get id(): string {
|
||||
const { id } = this.form.value;
|
||||
|
||||
return (id || '').trim();
|
||||
}
|
||||
|
||||
get description(): string {
|
||||
const { description } = this.form.value;
|
||||
|
||||
return (description || '').trim();
|
||||
}
|
||||
|
||||
get visibility(): string {
|
||||
return this.visibilityOption || '';
|
||||
}
|
||||
|
||||
submit() {
|
||||
const { form, dialog } = this;
|
||||
|
||||
if (!form.valid) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.create().subscribe(
|
||||
(node: SiteEntry) => {
|
||||
this.success.emit(node);
|
||||
dialog.close(node);
|
||||
},
|
||||
error => this.handleError(error)
|
||||
);
|
||||
}
|
||||
|
||||
visibilityChangeHandler(event) {
|
||||
this.visibilityOption = event.value;
|
||||
}
|
||||
|
||||
private create(): Observable<SiteEntry> {
|
||||
const { contentApi, title, id, description, visibility } = this;
|
||||
const siteBody = <SiteBody>{
|
||||
id,
|
||||
title,
|
||||
description,
|
||||
visibility
|
||||
};
|
||||
|
||||
return contentApi.createSite(siteBody);
|
||||
}
|
||||
|
||||
private sanitize(input: string) {
|
||||
return input.replace(/[\s]/g, '-').replace(/[^A-Za-z0-9-]/g, '');
|
||||
}
|
||||
|
||||
private handleError(error: any): any {
|
||||
const {
|
||||
error: { statusCode }
|
||||
} = JSON.parse(error.message);
|
||||
|
||||
if (statusCode === 409) {
|
||||
this.form.controls['id'].setErrors({
|
||||
message: 'LIBRARY.ERRORS.CONFLICT'
|
||||
});
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
}
|
||||
|
@ -27,16 +27,14 @@ import { Component, Inject, ViewEncapsulation } from '@angular/core';
|
||||
import { MAT_DIALOG_DATA } from '@angular/material';
|
||||
|
||||
@Component({
|
||||
templateUrl: './node-permissions.dialog.html',
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
host: { class: 'aca-node-permissions-dialog' }
|
||||
templateUrl: './node-permissions.dialog.html',
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
host: { class: 'aca-node-permissions-dialog' }
|
||||
})
|
||||
export class NodePermissionsDialogComponent {
|
||||
nodeId: string;
|
||||
nodeId: string;
|
||||
|
||||
constructor(
|
||||
@Inject(MAT_DIALOG_DATA) data: any,
|
||||
) {
|
||||
this.nodeId = data.nodeId;
|
||||
}
|
||||
constructor(@Inject(MAT_DIALOG_DATA) data: any) {
|
||||
this.nodeId = data.nodeId;
|
||||
}
|
||||
}
|
||||
|
@ -1,83 +1,81 @@
|
||||
@mixin aca-node-versions-dialog-theme($theme) {
|
||||
$foreground: map-get($theme, foreground);
|
||||
$accent: map-get($theme, accent);
|
||||
$foreground: map-get($theme, foreground);
|
||||
$accent: map-get($theme, accent);
|
||||
|
||||
.adf-version-manager-dialog-panel {
|
||||
height: 400px;
|
||||
.adf-version-manager-dialog-panel {
|
||||
height: 400px;
|
||||
}
|
||||
|
||||
.aca-node-versions-dialog {
|
||||
.mat-dialog-title {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.aca-node-versions-dialog {
|
||||
.mat-dialog-title {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.mat-dialog-content {
|
||||
flex: 1 1 auto;
|
||||
position: relative;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.mat-dialog-actions {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
|
||||
.mat-dialog-title {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
font-style: normal;
|
||||
font-stretch: normal;
|
||||
line-height: 1.6;
|
||||
margin: 0;
|
||||
letter-spacing: -0.5px;
|
||||
color: mat-color($foreground, text, 0.87);
|
||||
}
|
||||
|
||||
|
||||
.mat-dialog-actions {
|
||||
padding: 8px 8px 24px 8px;
|
||||
display: -webkit-box;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-box-pack: end;
|
||||
-ms-flex-pack: end;
|
||||
justify-content: flex-end;
|
||||
color: mat-color($foreground, text, 0.54);
|
||||
|
||||
button {
|
||||
text-transform: uppercase;
|
||||
font-weight: normal;
|
||||
|
||||
&:enabled {
|
||||
color: mat-color($accent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.adf-new-version-container {
|
||||
height: 350px !important;
|
||||
}
|
||||
|
||||
.mat-dialog-content {
|
||||
max-height: 36vh;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.mat-list-item-content {
|
||||
padding: 0;
|
||||
margin: 0 16px;
|
||||
}
|
||||
|
||||
.adf-version-list-container {
|
||||
.adf-version-list {
|
||||
height: 180px;
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.mat-list.adf-version-list {
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
||||
.mat-dialog-content {
|
||||
flex: 1 1 auto;
|
||||
position: relative;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.mat-dialog-actions {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.mat-dialog-title {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
font-style: normal;
|
||||
font-stretch: normal;
|
||||
line-height: 1.6;
|
||||
margin: 0;
|
||||
letter-spacing: -0.5px;
|
||||
color: mat-color($foreground, text, 0.87);
|
||||
}
|
||||
|
||||
.mat-dialog-actions {
|
||||
padding: 8px 8px 24px 8px;
|
||||
display: -webkit-box;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-box-pack: end;
|
||||
-ms-flex-pack: end;
|
||||
justify-content: flex-end;
|
||||
color: mat-color($foreground, text, 0.54);
|
||||
|
||||
button {
|
||||
text-transform: uppercase;
|
||||
font-weight: normal;
|
||||
|
||||
&:enabled {
|
||||
color: mat-color($accent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.adf-new-version-container {
|
||||
height: 350px !important;
|
||||
}
|
||||
|
||||
.mat-dialog-content {
|
||||
max-height: 36vh;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.mat-list-item-content {
|
||||
padding: 0;
|
||||
margin: 0 16px;
|
||||
}
|
||||
|
||||
.adf-version-list-container {
|
||||
.adf-version-list {
|
||||
height: 180px;
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.mat-list.adf-version-list {
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -31,21 +31,21 @@ import { AppStore } from '../../store/states/app.state';
|
||||
import { SnackbarErrorAction } from '../../store/actions';
|
||||
|
||||
@Component({
|
||||
templateUrl: './node-versions.dialog.html',
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
host: { class: 'aca-node-versions-dialog' }
|
||||
templateUrl: './node-versions.dialog.html',
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
host: { class: 'aca-node-versions-dialog' }
|
||||
})
|
||||
export class NodeVersionsDialogComponent {
|
||||
node: MinimalNodeEntryEntity;
|
||||
node: MinimalNodeEntryEntity;
|
||||
|
||||
constructor(
|
||||
@Inject(MAT_DIALOG_DATA) data: any,
|
||||
private store: Store<AppStore>
|
||||
) {
|
||||
this.node = data.node;
|
||||
}
|
||||
constructor(
|
||||
@Inject(MAT_DIALOG_DATA) data: any,
|
||||
private store: Store<AppStore>
|
||||
) {
|
||||
this.node = data.node;
|
||||
}
|
||||
|
||||
uploadError(errorMessage: string) {
|
||||
this.store.dispatch(new SnackbarErrorAction(errorMessage));
|
||||
}
|
||||
uploadError(errorMessage: string) {
|
||||
this.store.dispatch(new SnackbarErrorAction(errorMessage));
|
||||
}
|
||||
}
|
||||
|
@ -29,15 +29,11 @@ import { DocumentListDirective } from './document-list.directive';
|
||||
import { PaginationDirective } from './pagination.directive';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
ExperimentalDirective,
|
||||
DocumentListDirective,
|
||||
PaginationDirective
|
||||
],
|
||||
exports: [
|
||||
ExperimentalDirective,
|
||||
DocumentListDirective,
|
||||
PaginationDirective
|
||||
]
|
||||
declarations: [
|
||||
ExperimentalDirective,
|
||||
DocumentListDirective,
|
||||
PaginationDirective
|
||||
],
|
||||
exports: [ExperimentalDirective, DocumentListDirective, PaginationDirective]
|
||||
})
|
||||
export class DirectivesModule {}
|
||||
|
@ -34,133 +34,130 @@ import { SetSelectedNodesAction } from '../store/actions';
|
||||
import { MinimalNodeEntryEntity } from 'alfresco-js-api';
|
||||
|
||||
@Directive({
|
||||
selector: '[acaDocumentList]'
|
||||
selector: '[acaDocumentList]'
|
||||
})
|
||||
export class DocumentListDirective implements OnInit, OnDestroy {
|
||||
private subscriptions: Subscription[] = [];
|
||||
private isLibrary = false;
|
||||
private subscriptions: Subscription[] = [];
|
||||
private isLibrary = false;
|
||||
|
||||
get sortingPreferenceKey(): string {
|
||||
return this.route.snapshot.data.sortingPreferenceKey;
|
||||
get sortingPreferenceKey(): string {
|
||||
return this.route.snapshot.data.sortingPreferenceKey;
|
||||
}
|
||||
|
||||
constructor(
|
||||
private store: Store<AppStore>,
|
||||
private documentList: DocumentListComponent,
|
||||
private preferences: UserPreferencesService,
|
||||
private route: ActivatedRoute
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.documentList.includeFields = ['isFavorite', 'aspectNames'];
|
||||
this.documentList.allowDropFiles = false;
|
||||
this.isLibrary = this.documentList.currentFolderId === '-mysites-';
|
||||
|
||||
if (this.sortingPreferenceKey) {
|
||||
const current = this.documentList.sorting;
|
||||
|
||||
const key = this.preferences.get(
|
||||
`${this.sortingPreferenceKey}.sorting.key`,
|
||||
current[0]
|
||||
);
|
||||
const direction = this.preferences.get(
|
||||
`${this.sortingPreferenceKey}.sorting.direction`,
|
||||
current[1]
|
||||
);
|
||||
|
||||
this.documentList.sorting = [key, direction];
|
||||
// TODO: bug in ADF, the `sorting` binding is not updated when changed from code
|
||||
this.documentList.data.setSorting({ key, direction });
|
||||
}
|
||||
|
||||
constructor(
|
||||
private store: Store<AppStore>,
|
||||
private documentList: DocumentListComponent,
|
||||
private preferences: UserPreferencesService,
|
||||
private route: ActivatedRoute
|
||||
) {}
|
||||
this.subscriptions.push(
|
||||
this.documentList.ready.subscribe(() => this.onReady())
|
||||
);
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.documentList.includeFields = ['isFavorite', 'aspectNames'];
|
||||
this.documentList.allowDropFiles = false;
|
||||
this.isLibrary = this.documentList.currentFolderId === '-mysites-';
|
||||
ngOnDestroy() {
|
||||
this.subscriptions.forEach(subscription => subscription.unsubscribe());
|
||||
this.subscriptions = [];
|
||||
}
|
||||
|
||||
if (this.sortingPreferenceKey) {
|
||||
const current = this.documentList.sorting;
|
||||
|
||||
const key = this.preferences.get(
|
||||
`${this.sortingPreferenceKey}.sorting.key`,
|
||||
current[0]
|
||||
);
|
||||
const direction = this.preferences.get(
|
||||
`${this.sortingPreferenceKey}.sorting.direction`,
|
||||
current[1]
|
||||
);
|
||||
|
||||
this.documentList.sorting = [key, direction];
|
||||
// TODO: bug in ADF, the `sorting` binding is not updated when changed from code
|
||||
this.documentList.data.setSorting({ key, direction });
|
||||
}
|
||||
|
||||
this.subscriptions.push(
|
||||
this.documentList.ready.subscribe(() => this.onReady())
|
||||
);
|
||||
@HostListener('sorting-changed', ['$event'])
|
||||
onSortingChanged(event: CustomEvent) {
|
||||
if (this.sortingPreferenceKey) {
|
||||
this.preferences.set(
|
||||
`${this.sortingPreferenceKey}.sorting.key`,
|
||||
event.detail.key
|
||||
);
|
||||
this.preferences.set(
|
||||
`${this.sortingPreferenceKey}.sorting.direction`,
|
||||
event.detail.direction
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.subscriptions.forEach(subscription => subscription.unsubscribe());
|
||||
this.subscriptions = [];
|
||||
@HostListener('node-select', ['$event'])
|
||||
onNodeSelect(event: CustomEvent) {
|
||||
if (!!event.detail && !!event.detail.node) {
|
||||
const node: MinimalNodeEntryEntity = event.detail.node.entry;
|
||||
if (node && this.isLockedNode(node)) {
|
||||
this.unSelectLockedNodes(this.documentList);
|
||||
}
|
||||
|
||||
this.updateSelection();
|
||||
}
|
||||
}
|
||||
|
||||
@HostListener('sorting-changed', ['$event'])
|
||||
onSortingChanged(event: CustomEvent) {
|
||||
if (this.sortingPreferenceKey) {
|
||||
this.preferences.set(
|
||||
`${this.sortingPreferenceKey}.sorting.key`,
|
||||
event.detail.key
|
||||
);
|
||||
this.preferences.set(
|
||||
`${this.sortingPreferenceKey}.sorting.direction`,
|
||||
event.detail.direction
|
||||
);
|
||||
}
|
||||
}
|
||||
@HostListener('node-unselect')
|
||||
onNodeUnselect() {
|
||||
this.updateSelection();
|
||||
}
|
||||
|
||||
@HostListener('node-select', ['$event'])
|
||||
onNodeSelect(event: CustomEvent) {
|
||||
if (!!event.detail && !!event.detail.node) {
|
||||
const node: MinimalNodeEntryEntity = event.detail.node.entry;
|
||||
if (node && this.isLockedNode(node)) {
|
||||
this.unSelectLockedNodes(this.documentList);
|
||||
}
|
||||
onReady() {
|
||||
this.updateSelection();
|
||||
}
|
||||
|
||||
this.updateSelection();
|
||||
}
|
||||
}
|
||||
private updateSelection() {
|
||||
const selection = this.documentList.selection.map(entry => {
|
||||
entry['isLibrary'] = this.isLibrary;
|
||||
return entry;
|
||||
});
|
||||
|
||||
@HostListener('node-unselect')
|
||||
onNodeUnselect() {
|
||||
this.updateSelection();
|
||||
}
|
||||
this.store.dispatch(new SetSelectedNodesAction(selection));
|
||||
}
|
||||
|
||||
onReady() {
|
||||
this.updateSelection();
|
||||
}
|
||||
private isLockedNode(node): boolean {
|
||||
return (
|
||||
node.isLocked ||
|
||||
(node.properties && node.properties['cm:lockType'] === 'READ_ONLY_LOCK')
|
||||
);
|
||||
}
|
||||
|
||||
private updateSelection() {
|
||||
const selection = this.documentList.selection.map(entry => {
|
||||
entry['isLibrary'] = this.isLibrary;
|
||||
return entry;
|
||||
private isLockedRow(row): boolean {
|
||||
return (
|
||||
row.getValue('isLocked') ||
|
||||
(row.getValue('properties') &&
|
||||
row.getValue('properties')['cm:lockType'] === 'READ_ONLY_LOCK')
|
||||
);
|
||||
}
|
||||
|
||||
private unSelectLockedNodes(documentList: DocumentListComponent) {
|
||||
documentList.selection = documentList.selection.filter(
|
||||
item => !this.isLockedNode(item.entry)
|
||||
);
|
||||
|
||||
const dataTable = documentList.dataTable;
|
||||
if (dataTable && dataTable.data) {
|
||||
const rows = dataTable.data.getRows();
|
||||
|
||||
if (rows && rows.length > 0) {
|
||||
rows.forEach(r => {
|
||||
if (this.isLockedRow(r)) {
|
||||
r.isSelected = false;
|
||||
}
|
||||
});
|
||||
|
||||
this.store.dispatch(
|
||||
new SetSelectedNodesAction(selection)
|
||||
);
|
||||
}
|
||||
|
||||
private isLockedNode(node): boolean {
|
||||
return (
|
||||
node.isLocked ||
|
||||
(node.properties &&
|
||||
node.properties['cm:lockType'] === 'READ_ONLY_LOCK')
|
||||
);
|
||||
}
|
||||
|
||||
private isLockedRow(row): boolean {
|
||||
return (
|
||||
row.getValue('isLocked') ||
|
||||
(row.getValue('properties') &&
|
||||
row.getValue('properties')['cm:lockType'] === 'READ_ONLY_LOCK')
|
||||
);
|
||||
}
|
||||
|
||||
private unSelectLockedNodes(documentList: DocumentListComponent) {
|
||||
documentList.selection = documentList.selection.filter(
|
||||
item => !this.isLockedNode(item.entry)
|
||||
);
|
||||
|
||||
const dataTable = documentList.dataTable;
|
||||
if (dataTable && dataTable.data) {
|
||||
const rows = dataTable.data.getRows();
|
||||
|
||||
if (rows && rows.length > 0) {
|
||||
rows.forEach(r => {
|
||||
if (this.isLockedRow(r)) {
|
||||
r.isSelected = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -23,73 +23,83 @@
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Directive, TemplateRef, ViewContainerRef, Input, EmbeddedViewRef } from '@angular/core';
|
||||
import {
|
||||
Directive,
|
||||
TemplateRef,
|
||||
ViewContainerRef,
|
||||
Input,
|
||||
EmbeddedViewRef
|
||||
} from '@angular/core';
|
||||
import { AppConfigService, StorageService } from '@alfresco/adf-core';
|
||||
import { environment } from '../../environments/environment';
|
||||
|
||||
@Directive({
|
||||
// tslint:disable-next-line:directive-selector
|
||||
selector: '[ifExperimental]'
|
||||
// tslint:disable-next-line:directive-selector
|
||||
selector: '[ifExperimental]'
|
||||
})
|
||||
export class ExperimentalDirective {
|
||||
private elseTemplateRef: TemplateRef<any>;
|
||||
private elseViewRef: EmbeddedViewRef<any>;
|
||||
private shouldRender: boolean;
|
||||
private elseTemplateRef: TemplateRef<any>;
|
||||
private elseViewRef: EmbeddedViewRef<any>;
|
||||
private shouldRender: boolean;
|
||||
|
||||
constructor(
|
||||
private templateRef: TemplateRef<any>,
|
||||
private viewContainerRef: ViewContainerRef,
|
||||
private storage: StorageService,
|
||||
private config: AppConfigService
|
||||
) {}
|
||||
constructor(
|
||||
private templateRef: TemplateRef<any>,
|
||||
private viewContainerRef: ViewContainerRef,
|
||||
private storage: StorageService,
|
||||
private config: AppConfigService
|
||||
) {}
|
||||
|
||||
@Input() set ifExperimental(featureKey: string) {
|
||||
const key = `experimental.${featureKey}`;
|
||||
@Input()
|
||||
set ifExperimental(featureKey: string) {
|
||||
const key = `experimental.${featureKey}`;
|
||||
|
||||
const override = this.storage.getItem(key);
|
||||
const override = this.storage.getItem(key);
|
||||
|
||||
if (override === 'true') {
|
||||
this.shouldRender = true;
|
||||
}
|
||||
|
||||
if (!environment.production) {
|
||||
const value = this.config.get(key);
|
||||
if (value === true || value === 'true') {
|
||||
this.shouldRender = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (override !== 'true' && environment.production) {
|
||||
this.shouldRender = false;
|
||||
}
|
||||
|
||||
this.updateView();
|
||||
if (override === 'true') {
|
||||
this.shouldRender = true;
|
||||
}
|
||||
|
||||
@Input() set ifExperimentalElse(templateRef: TemplateRef<any>) {
|
||||
this.elseTemplateRef = templateRef;
|
||||
this.elseViewRef = null;
|
||||
this.updateView();
|
||||
if (!environment.production) {
|
||||
const value = this.config.get(key);
|
||||
if (value === true || value === 'true') {
|
||||
this.shouldRender = true;
|
||||
}
|
||||
}
|
||||
|
||||
private updateView() {
|
||||
if (this.shouldRender) {
|
||||
this.viewContainerRef.clear();
|
||||
this.elseViewRef = null;
|
||||
|
||||
if (this.templateRef) {
|
||||
this.viewContainerRef.createEmbeddedView(this.templateRef);
|
||||
}
|
||||
} else {
|
||||
if (this.elseViewRef) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.viewContainerRef.clear();
|
||||
|
||||
if (this.elseTemplateRef) {
|
||||
this.elseViewRef = this.viewContainerRef.createEmbeddedView(this.elseTemplateRef);
|
||||
}
|
||||
}
|
||||
if (override !== 'true' && environment.production) {
|
||||
this.shouldRender = false;
|
||||
}
|
||||
|
||||
this.updateView();
|
||||
}
|
||||
|
||||
@Input()
|
||||
set ifExperimentalElse(templateRef: TemplateRef<any>) {
|
||||
this.elseTemplateRef = templateRef;
|
||||
this.elseViewRef = null;
|
||||
this.updateView();
|
||||
}
|
||||
|
||||
private updateView() {
|
||||
if (this.shouldRender) {
|
||||
this.viewContainerRef.clear();
|
||||
this.elseViewRef = null;
|
||||
|
||||
if (this.templateRef) {
|
||||
this.viewContainerRef.createEmbeddedView(this.templateRef);
|
||||
}
|
||||
} else {
|
||||
if (this.elseViewRef) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.viewContainerRef.clear();
|
||||
|
||||
if (this.elseTemplateRef) {
|
||||
this.elseViewRef = this.viewContainerRef.createEmbeddedView(
|
||||
this.elseTemplateRef
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -25,41 +25,39 @@
|
||||
|
||||
import { Directive, OnInit, OnDestroy } from '@angular/core';
|
||||
import {
|
||||
PaginationComponent,
|
||||
UserPreferencesService,
|
||||
PaginationModel,
|
||||
AppConfigService
|
||||
PaginationComponent,
|
||||
UserPreferencesService,
|
||||
PaginationModel,
|
||||
AppConfigService
|
||||
} from '@alfresco/adf-core';
|
||||
import { Subscription } from 'rxjs';
|
||||
|
||||
@Directive({
|
||||
selector: '[acaPagination]'
|
||||
selector: '[acaPagination]'
|
||||
})
|
||||
export class PaginationDirective implements OnInit, OnDestroy {
|
||||
private subscriptions: Subscription[] = [];
|
||||
private subscriptions: Subscription[] = [];
|
||||
|
||||
constructor(
|
||||
private pagination: PaginationComponent,
|
||||
private preferences: UserPreferencesService,
|
||||
private config: AppConfigService
|
||||
) {}
|
||||
constructor(
|
||||
private pagination: PaginationComponent,
|
||||
private preferences: UserPreferencesService,
|
||||
private config: AppConfigService
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.pagination.supportedPageSizes = this.config.get(
|
||||
'pagination.supportedPageSizes'
|
||||
);
|
||||
ngOnInit() {
|
||||
this.pagination.supportedPageSizes = this.config.get(
|
||||
'pagination.supportedPageSizes'
|
||||
);
|
||||
|
||||
this.subscriptions.push(
|
||||
this.pagination.changePageSize.subscribe(
|
||||
(event: PaginationModel) => {
|
||||
this.preferences.paginationSize = event.maxItems;
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
this.subscriptions.push(
|
||||
this.pagination.changePageSize.subscribe((event: PaginationModel) => {
|
||||
this.preferences.paginationSize = event.maxItems;
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.subscriptions.forEach(subscription => subscription.unsubscribe());
|
||||
this.subscriptions = [];
|
||||
}
|
||||
ngOnDestroy() {
|
||||
this.subscriptions.forEach(subscription => subscription.unsubscribe());
|
||||
this.subscriptions = [];
|
||||
}
|
||||
}
|
||||
|
@ -7,15 +7,15 @@ import { MyExtensionModule } from 'my-extension';
|
||||
// For any application-specific code use CoreExtensionsModule instead.
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
CodeEditorModule.forRoot({
|
||||
// use local Monaco installation
|
||||
baseUrl: 'assets/monaco',
|
||||
// use local Typings Worker
|
||||
typingsWorkerUrl: 'assets/workers/typings-worker.js'
|
||||
}),
|
||||
AcaDevToolsModule,
|
||||
MyExtensionModule
|
||||
]
|
||||
imports: [
|
||||
CodeEditorModule.forRoot({
|
||||
// use local Monaco installation
|
||||
baseUrl: 'assets/monaco',
|
||||
// use local Typings Worker
|
||||
typingsWorkerUrl: 'assets/workers/typings-worker.js'
|
||||
}),
|
||||
AcaDevToolsModule,
|
||||
MyExtensionModule
|
||||
]
|
||||
})
|
||||
export class AppExtensionsModule {}
|
||||
|
@ -39,82 +39,78 @@ import { VersionsTabComponent } from '../components/info-drawer/versions-tab/ver
|
||||
import { ExtensionsModule, ExtensionService } from '@alfresco/adf-extensions';
|
||||
|
||||
export function setupExtensions(service: AppExtensionService): Function {
|
||||
return () => service.load();
|
||||
return () => service.load();
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
CommonModule,
|
||||
CoreModule.forChild(),
|
||||
ExtensionsModule.forChild()
|
||||
]
|
||||
imports: [CommonModule, CoreModule.forChild(), ExtensionsModule.forChild()]
|
||||
})
|
||||
export class CoreExtensionsModule {
|
||||
static forRoot(): ModuleWithProviders {
|
||||
return {
|
||||
ngModule: CoreExtensionsModule,
|
||||
providers: [
|
||||
AppExtensionService,
|
||||
{
|
||||
provide: APP_INITIALIZER,
|
||||
useFactory: setupExtensions,
|
||||
deps: [AppExtensionService],
|
||||
multi: true
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
static forRoot(): ModuleWithProviders {
|
||||
return {
|
||||
ngModule: CoreExtensionsModule,
|
||||
providers: [
|
||||
AppExtensionService,
|
||||
{
|
||||
provide: APP_INITIALIZER,
|
||||
useFactory: setupExtensions,
|
||||
deps: [AppExtensionService],
|
||||
multi: true
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
static forChild(): ModuleWithProviders {
|
||||
return {
|
||||
ngModule: CoreExtensionsModule
|
||||
};
|
||||
}
|
||||
static forChild(): ModuleWithProviders {
|
||||
return {
|
||||
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
|
||||
});
|
||||
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.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,
|
||||
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,
|
||||
'app.navigation.isPreview': nav.isPreview
|
||||
});
|
||||
}
|
||||
'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,
|
||||
'app.navigation.isPreview': nav.isPreview
|
||||
});
|
||||
}
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user