[ACA] Create Library - switch to ADF component (#893)

* use adf component

* fix component name locator
This commit is contained in:
Cilibiu Bogdan
2019-01-09 10:19:09 +02:00
committed by Denys Vuika
parent 299516a410
commit 71074b2bf1
7 changed files with 5 additions and 634 deletions

View File

@@ -30,7 +30,7 @@ import { Utils } from '../../utilities/utils';
export class CreateLibraryDialog extends Component { export class CreateLibraryDialog extends Component {
private static selectors = { private static selectors = {
root: 'app-library-dialog', root: 'adf-library-dialog',
title: '.mat-dialog-title', title: '.mat-dialog-title',
nameInput: 'input[placeholder="Name" i]', nameInput: 'input[placeholder="Name" i]',

View File

@@ -39,6 +39,7 @@ import {
TranslateLoaderService TranslateLoaderService
} from '@alfresco/adf-core'; } from '@alfresco/adf-core';
import { import {
LibraryDialogComponent,
ContentModule, ContentModule,
CustomResourcesService CustomResourcesService
} from '@alfresco/adf-content-services'; } from '@alfresco/adf-content-services';
@@ -50,7 +51,6 @@ import { FilesComponent } from './components/files/files.component';
import { LibrariesComponent } from './components/libraries/libraries.component'; import { LibrariesComponent } from './components/libraries/libraries.component';
import { FavoriteLibrariesComponent } from './components/favorite-libraries/favorite-libraries.component'; import { FavoriteLibrariesComponent } from './components/favorite-libraries/favorite-libraries.component';
import { NodeVersionsDialogComponent } from './dialogs/node-versions/node-versions.dialog'; import { NodeVersionsDialogComponent } from './dialogs/node-versions/node-versions.dialog';
import { LibraryDialogComponent } from './dialogs/library/library.dialog';
import { AppStoreModule } from './store/app-store.module'; import { AppStoreModule } from './store/app-store.module';
import { MaterialModule } from './material.module'; import { MaterialModule } from './material.module';
@@ -118,8 +118,7 @@ import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
FilesComponent, FilesComponent,
LibrariesComponent, LibrariesComponent,
FavoriteLibrariesComponent, FavoriteLibrariesComponent,
NodeVersionsDialogComponent, NodeVersionsDialogComponent
LibraryDialogComponent
], ],
providers: [ providers: [
{ provide: RouteReuseStrategy, useClass: AppRouteReuseStrategy }, { provide: RouteReuseStrategy, useClass: AppRouteReuseStrategy },

View File

@@ -1,89 +0,0 @@
<h2 mat-dialog-title>{{ createTitle | translate }}</h2>
<mat-dialog-content>
<form novalidate [formGroup]="form" (submit)="submit()">
<mat-form-field>
<input
placeholder="{{ 'LIBRARY.DIALOG.FORM.NAME' | translate }}"
required
matInput
formControlName="title"
autocomplete="off"
/>
<mat-hint *ngIf="libraryTitleExists"
>{{ 'LIBRARY.HINTS.SITE_TITLE_EXISTS' | translate }}</mat-hint
>
<mat-error *ngIf="form.controls['title'].hasError('maxlength')">
{{ 'LIBRARY.ERRORS.TITLE_TOO_LONG' | translate }}
</mat-error>
<mat-error *ngIf="form.controls['title'].errors?.message">
{{ form.controls['title'].errors?.message | translate }}
</mat-error>
</mat-form-field>
<mat-form-field>
<input
required
placeholder="{{ 'LIBRARY.DIALOG.FORM.SITE_ID' | translate }}"
matInput
formControlName="id"
autocomplete="off"
/>
<mat-error *ngIf="form.controls['id'].errors?.message">
{{ form.controls['id'].errors?.message | translate }}
</mat-error>
<mat-error *ngIf="form.controls['id'].hasError('maxlength')">
{{ 'LIBRARY.ERRORS.ID_TOO_LONG' | translate }}
</mat-error>
</mat-form-field>
<mat-form-field>
<textarea
matInput
placeholder="{{ 'LIBRARY.DIALOG.FORM.DESCRIPTION' | translate }}"
rows="3"
formControlName="description"
></textarea>
<mat-error *ngIf="form.controls['description'].hasError('maxlength')">
{{ 'LIBRARY.ERRORS.DESCRIPTION_TOO_LONG' | translate }}
</mat-error>
</mat-form-field>
<mat-radio-group
[ngModelOptions]="{ standalone: true }"
[(ngModel)]="visibilityOption"
(change)="visibilityChangeHandler($event)"
>
<mat-radio-button
color="primary"
[disabled]="option.disabled"
*ngFor="let option of visibilityOptions"
[value]="option.value"
[checked]="visibilityOption.value === option.value"
>
{{ option.label | translate }}
</mat-radio-button>
</mat-radio-group>
</form>
</mat-dialog-content>
<mat-dialog-actions class="actions-buttons">
<button mat-button mat-dialog-close>
{{ 'LIBRARY.DIALOG.CANCEL' | translate }}
</button>
<button
color="primary"
mat-button
(click)="submit()"
[disabled]="!form.valid"
>
{{ 'LIBRARY.DIALOG.CREATE' | translate }}
</button>
</mat-dialog-actions>

View File

@@ -1,29 +0,0 @@
.app-library-dialog {
.mat-radio-group {
display: flex;
flex-direction: column;
margin: 0 0 20px 0;
}
.mat-radio-group .mat-radio-button {
margin: 10px 0;
}
.mat-form-field {
width: 100%;
}
mat-form-field {
padding-top: 20px;
}
.actions-buttons {
display: flex;
flex-direction: row;
justify-content: flex-end;
.mat-button {
text-transform: uppercase;
}
}
}

View File

@@ -1,248 +0,0 @@
/*!
* @license
* Alfresco Example Content Application
*
* Copyright (C) 2005 - 2018 Alfresco Software Limited
*
* This file is part of the Alfresco Example Content Application.
* If the software was purchased under a paid Alfresco license, the terms of
* the paid license agreement will prevail. Otherwise, the software is
* provided under the following open source license terms:
*
* The Alfresco Example Content Application is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* The Alfresco Example Content Application is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { ReactiveFormsModule } from '@angular/forms';
import { CoreModule } from '@alfresco/adf-core';
import { LibraryDialogComponent } from './library.dialog';
import { TestBed, fakeAsync, tick, flush } from '@angular/core/testing';
import { NO_ERRORS_SCHEMA } from '@angular/core';
import { MatDialogRef } from '@angular/material';
import {
AlfrescoApiService,
AlfrescoApiServiceMock,
setupTestBed
} from '@alfresco/adf-core';
describe('LibraryDialogComponent', () => {
let fixture;
let component;
let alfrescoApi;
const dialogRef = {
close: jasmine.createSpy('close')
};
setupTestBed({
imports: [NoopAnimationsModule, CoreModule, ReactiveFormsModule],
declarations: [LibraryDialogComponent],
providers: [
{
provide: AlfrescoApiService,
useClass: AlfrescoApiServiceMock
},
{ provide: MatDialogRef, useValue: dialogRef }
],
schemas: [NO_ERRORS_SCHEMA]
});
beforeEach(() => {
fixture = TestBed.createComponent(LibraryDialogComponent);
component = fixture.componentInstance;
alfrescoApi = TestBed.get(AlfrescoApiService);
spyOn(
alfrescoApi.getInstance().core.queriesApi,
'findSites'
).and.returnValue(
Promise.resolve({
list: { entries: [] }
})
);
});
it('should set library id automatically on title input', fakeAsync(() => {
spyOn(alfrescoApi.sitesApi, 'getSite').and.callFake(() => {
return new Promise((resolve, reject) => reject());
});
fixture.detectChanges();
component.form.controls.title.setValue('libraryTitle');
tick(500);
flush();
fixture.detectChanges();
expect(component.form.controls.id.value).toBe('libraryTitle');
}));
it('should translate library title space character to dash for library id', fakeAsync(() => {
spyOn(alfrescoApi.sitesApi, 'getSite').and.callFake(() => {
return new Promise((resolve, reject) => reject());
});
fixture.detectChanges();
component.form.controls.title.setValue('library title');
tick(500);
flush();
fixture.detectChanges();
expect(component.form.controls.id.value).toBe('library-title');
}));
it('should not translate library title if value is not a valid id', fakeAsync(() => {
spyOn(alfrescoApi.sitesApi, 'getSite').and.callFake(() => {
return new Promise((resolve, reject) => reject());
});
fixture.detectChanges();
component.form.controls.title.setValue('@@@####');
tick(500);
flush();
fixture.detectChanges();
expect(component.form.controls.id.value).toBe(null);
}));
it('should translate library title partially for library id', fakeAsync(() => {
spyOn(alfrescoApi.sitesApi, 'getSite').and.callFake(() => {
return new Promise((resolve, reject) => reject());
});
fixture.detectChanges();
component.form.controls.title.setValue('@@@####library');
tick(500);
flush();
fixture.detectChanges();
expect(component.form.controls.id.value).toBe('library');
}));
it('should translate library title multiple space character to one dash for library id', fakeAsync(() => {
spyOn(alfrescoApi.sitesApi, 'getSite').and.callFake(() => {
return new Promise((resolve, reject) => reject());
});
fixture.detectChanges();
component.form.controls.title.setValue('library title');
tick(500);
flush();
fixture.detectChanges();
expect(component.form.controls.id.value).toBe('library-title');
}));
it('should not change custom library id on title input', fakeAsync(() => {
spyOn(alfrescoApi.sitesApi, 'getSite').and.callFake(() => {
return new Promise((resolve, reject) => reject());
});
fixture.detectChanges();
component.form.controls.id.setValue('custom-id');
component.form.controls.id.markAsDirty();
tick(500);
flush();
fixture.detectChanges();
component.form.controls.title.setValue('library title');
tick(500);
flush();
fixture.detectChanges();
expect(component.form.controls.id.value).toBe('custom-id');
}));
it('should invalidate form when library id already exists', fakeAsync(() => {
spyOn(alfrescoApi.sitesApi, 'getSite').and.returnValue(Promise.resolve());
fixture.detectChanges();
component.form.controls.id.setValue('existingLibrary');
tick(500);
flush();
fixture.detectChanges();
expect(component.form.controls.id.errors).toEqual({
message: 'LIBRARY.ERRORS.EXISTENT_SITE'
});
expect(component.form.valid).toBe(false);
}));
it('should create site when form is valid', fakeAsync(() => {
spyOn(alfrescoApi.sitesApi, 'createSite').and.returnValue(
Promise.resolve()
);
spyOn(alfrescoApi.sitesApi, 'getSite').and.callFake(() => {
return new Promise((resolve, reject) => reject());
});
fixture.detectChanges();
component.form.controls.title.setValue('library title');
tick(500);
flush();
fixture.detectChanges();
component.submit();
fixture.detectChanges();
flush();
expect(alfrescoApi.sitesApi.createSite).toHaveBeenCalledWith({
id: 'library-title',
title: 'library title',
description: '',
visibility: 'PUBLIC'
});
}));
it('should not create site when form is invalid', fakeAsync(() => {
spyOn(alfrescoApi.sitesApi, 'createSite').and.returnValue(
Promise.resolve({})
);
spyOn(alfrescoApi.sitesApi, 'getSite').and.returnValue(Promise.resolve());
fixture.detectChanges();
component.form.controls.title.setValue('existingLibrary');
tick(500);
flush();
fixture.detectChanges();
component.submit();
fixture.detectChanges();
flush();
expect(alfrescoApi.sitesApi.createSite).not.toHaveBeenCalled();
}));
it('should notify on 409 conflict error (might be in trash)', fakeAsync(() => {
const error = { message: '{ "error": { "statusCode": 409 } }' };
spyOn(alfrescoApi.sitesApi, 'createSite').and.callFake(() => {
return new Promise((resolve, reject) => reject(error));
});
spyOn(alfrescoApi.sitesApi, 'getSite').and.callFake(() => {
return new Promise((resolve, reject) => reject());
});
fixture.detectChanges();
component.form.controls.title.setValue('test');
tick(500);
flush();
fixture.detectChanges();
component.submit();
fixture.detectChanges();
flush();
expect(component.form.controls.id.errors).toEqual({
message: 'LIBRARY.ERRORS.CONFLICT'
});
}));
});

View File

@@ -1,262 +0,0 @@
/*!
* @license
* Copyright 2016 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Observable, Subject, from } from 'rxjs';
import {
Component,
OnInit,
Output,
EventEmitter,
OnDestroy,
ViewEncapsulation
} from '@angular/core';
import {
FormBuilder,
FormGroup,
Validators,
FormControl,
AbstractControl
} from '@angular/forms';
import { MatDialogRef } from '@angular/material';
import { SiteBody, SiteEntry, SitePaging } from 'alfresco-js-api';
import { AlfrescoApiService } from '@alfresco/adf-core';
import { debounceTime, mergeMap, takeUntil } from 'rxjs/operators';
@Component({
selector: 'app-library-dialog',
styleUrls: ['./library.dialog.scss'],
templateUrl: './library.dialog.html',
encapsulation: ViewEncapsulation.None,
host: { class: 'app-library-dialog' }
})
export class LibraryDialogComponent implements OnInit, OnDestroy {
@Output()
error: EventEmitter<any> = new EventEmitter<any>();
@Output()
success: EventEmitter<any> = new EventEmitter<any>();
onDestroy$: Subject<boolean> = new Subject<boolean>();
createTitle = 'LIBRARY.DIALOG.CREATE_TITLE';
libraryTitleExists = false;
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 alfrescoApiService: AlfrescoApiService,
private formBuilder: FormBuilder,
private dialog: MatDialogRef<LibraryDialogComponent>
) {}
ngOnInit() {
const validators = {
id: [
Validators.required,
Validators.maxLength(72),
this.forbidSpecialCharacters
],
title: [
Validators.required,
this.forbidOnlySpaces,
Validators.maxLength(256)
],
description: [Validators.maxLength(512)]
};
this.form = this.formBuilder.group({
title: [null, validators.title],
id: [null, validators.id, this.createSiteIdValidator()],
description: ['', validators.description]
});
this.visibilityOption = this.visibilityOptions[0].value;
this.form.controls['title'].valueChanges
.pipe(
debounceTime(300),
mergeMap(title => this.checkLibraryNameExists(title), title => title),
takeUntil(this.onDestroy$)
)
.subscribe((title: string) => {
if (!this.form.controls['id'].dirty && this.canGenerateId(title)) {
this.form.patchValue({ id: this.sanitize(title.trim()) });
this.form.controls['id'].markAsTouched();
}
});
}
ngOnDestroy() {
this.onDestroy$.next(true);
this.onDestroy$.complete();
}
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 { title, id, description, visibility } = this;
const siteBody = <SiteBody>{
id,
title,
description,
visibility
};
return from(this.alfrescoApiService.sitesApi.createSite(siteBody));
}
private sanitize(input: string) {
return input.replace(/[\s\s]+/g, '-').replace(/[^A-Za-z0-9-]/g, '');
}
private canGenerateId(title) {
return Boolean(title.replace(/[^A-Za-z0-9-]/g, '').length);
}
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;
}
private async checkLibraryNameExists(libraryTitle: string) {
const { entries } = (await this.findLibraryByTitle(libraryTitle)).list;
if (entries.length) {
this.libraryTitleExists = entries[0].entry.title === libraryTitle;
} else {
this.libraryTitleExists = false;
}
}
private findLibraryByTitle(libraryTitle: string): Promise<SitePaging> {
return this.alfrescoApiService
.getInstance()
.core.queriesApi.findSites(libraryTitle, {
maxItems: 1,
fields: ['title']
})
.catch(() => ({ list: { entries: [] } }));
}
private forbidSpecialCharacters({ value }: FormControl) {
if (value === null || value.length === 0) {
return null;
}
const validCharacters: RegExp = /[^A-Za-z0-9-]/;
const isValid: boolean = !validCharacters.test(value);
return isValid
? null
: {
message: 'LIBRARY.ERRORS.ILLEGAL_CHARACTERS'
};
}
private forbidOnlySpaces({ value }: FormControl) {
if (value === null || value.length === 0) {
return null;
}
const isValid: boolean = !!(value || '').trim();
return isValid
? null
: {
message: 'LIBRARY.ERRORS.ONLY_SPACES'
};
}
private createSiteIdValidator() {
let timer;
return (control: AbstractControl) => {
if (timer) {
clearTimeout(timer);
}
return new Promise(resolve => {
timer = setTimeout(() => {
return from(
this.alfrescoApiService.sitesApi.getSite(control.value)
).subscribe(
() => resolve({ message: 'LIBRARY.ERRORS.EXISTENT_SITE' }),
() => resolve(null)
);
}, 300);
});
};
}
}

View File

@@ -28,9 +28,9 @@ import { Injectable } from '@angular/core';
import { MatDialog, MatSnackBar } from '@angular/material'; import { MatDialog, MatSnackBar } from '@angular/material';
import { import {
FolderDialogComponent, FolderDialogComponent,
ConfirmDialogComponent ConfirmDialogComponent,
LibraryDialogComponent
} from '@alfresco/adf-content-services'; } from '@alfresco/adf-content-services';
import { LibraryDialogComponent } from '../dialogs/library/library.dialog';
import { import {
SnackbarErrorAction, SnackbarErrorAction,
SnackbarInfoAction, SnackbarInfoAction,