mirror of
https://github.com/Alfresco/alfresco-content-app.git
synced 2025-07-31 17:38:28 +00:00
[ACA-1545] Library - create (#506)
* create site implementation * lint * update validation * reuse existent service and renamed site to library
This commit is contained in:
committed by
Denys Vuika
parent
19021c8b51
commit
0504b28b3c
@@ -61,6 +61,7 @@ import { NodePermanentDeleteDirective } from './common/directives/node-permanent
|
||||
import { NodeUnshareDirective } from './common/directives/node-unshare.directive';
|
||||
import { NodeVersionsDirective } from './common/directives/node-versions.directive';
|
||||
import { NodeVersionsDialogComponent } from './dialogs/node-versions/node-versions.dialog';
|
||||
import { LibraryDialogComponent } from './dialogs/library/library.dialog';
|
||||
import { ContentManagementService } from './common/services/content-management.service';
|
||||
import { NodeActionsService } from './common/services/node-actions.service';
|
||||
import { NodePermissionService } from './common/services/node-permission.service';
|
||||
@@ -132,6 +133,7 @@ import { PermissionsManagerComponent } from './components/permission-manager/per
|
||||
NodeVersionsDirective,
|
||||
NodePermissionsDirective,
|
||||
NodeVersionsDialogComponent,
|
||||
LibraryDialogComponent,
|
||||
NodePermissionsDialogComponent,
|
||||
PermissionsManagerComponent,
|
||||
SearchResultsComponent,
|
||||
@@ -162,6 +164,7 @@ import { PermissionsManagerComponent } from './components/permission-manager/per
|
||||
ExtensionService
|
||||
],
|
||||
entryComponents: [
|
||||
LibraryDialogComponent,
|
||||
NodeVersionsDialogComponent,
|
||||
NodePermissionsDialogComponent
|
||||
],
|
||||
|
@@ -27,6 +27,7 @@ import { Subject } from 'rxjs/Rx';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { MatDialog } from '@angular/material';
|
||||
import { FolderDialogComponent } from '@alfresco/adf-content-services';
|
||||
import { LibraryDialogComponent } from '../../dialogs/library/library.dialog';
|
||||
import { SnackbarErrorAction } from '../../store/actions';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { AppStore } from '../../store/states';
|
||||
@@ -45,7 +46,8 @@ export class ContentManagementService {
|
||||
nodesRestored = new Subject<any>();
|
||||
folderEdited = new Subject<any>();
|
||||
folderCreated = new Subject<any>();
|
||||
siteDeleted = new Subject<string>();
|
||||
libraryDeleted = new Subject<string>();
|
||||
libraryCreated = new Subject<string>();
|
||||
linksUnshared = new Subject<any>();
|
||||
|
||||
constructor(
|
||||
@@ -98,6 +100,22 @@ export class ContentManagementService {
|
||||
});
|
||||
}
|
||||
|
||||
createLibrary() {
|
||||
const dialogInstance = this.dialogRef.open(LibraryDialogComponent, {
|
||||
width: '400px'
|
||||
});
|
||||
|
||||
dialogInstance.componentInstance.error.subscribe(message => {
|
||||
this.store.dispatch(new SnackbarErrorAction(message));
|
||||
});
|
||||
|
||||
dialogInstance.afterClosed().subscribe(node => {
|
||||
if (node) {
|
||||
this.libraryCreated.next(node);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
canDeleteNode(node: MinimalNodeEntity | Node): boolean {
|
||||
return this.permission.check(node, ['delete']);
|
||||
}
|
||||
|
@@ -12,6 +12,14 @@
|
||||
<mat-icon *ngIf="displayMode === 'gallery'" matTooltip="{{ 'APP.DOCUMENT_LIST.TOOLBAR.LISTVIEW' | translate }}">list</mat-icon>
|
||||
</button>
|
||||
|
||||
<button
|
||||
mat-icon-button
|
||||
color="primary"
|
||||
*ifExperimental="'libraries'"
|
||||
(click)="createLibrary()">
|
||||
<mat-icon>create_new_folder</mat-icon>
|
||||
</button>
|
||||
|
||||
<ng-container *ngIf="!selection.isEmpty">
|
||||
<ng-container *ifExperimental="'libraries'">
|
||||
<button
|
||||
|
@@ -30,7 +30,7 @@ import { ShareDataRow } from '@alfresco/adf-content-services';
|
||||
import { PageComponent } from '../page.component';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { AppStore } from '../../store/states/app.state';
|
||||
import { DeleteLibraryAction } from '../../store/actions';
|
||||
import { DeleteLibraryAction, CreateLibraryAction } from '../../store/actions';
|
||||
import { SiteEntry } from 'alfresco-js-api';
|
||||
import { ContentManagementService } from '../../common/services/content-management.service';
|
||||
import { ContentApiService } from '../../services/content-api.service';
|
||||
@@ -54,7 +54,8 @@ export class LibrariesComponent extends PageComponent implements OnInit {
|
||||
super.ngOnInit();
|
||||
|
||||
this.subscriptions.push(
|
||||
this.content.siteDeleted.subscribe(() => this.reload())
|
||||
this.content.libraryDeleted.subscribe(() => this.reload()),
|
||||
this.content.libraryCreated.subscribe(() => this.reload())
|
||||
);
|
||||
}
|
||||
|
||||
@@ -105,4 +106,8 @@ export class LibrariesComponent extends PageComponent implements OnInit {
|
||||
this.store.dispatch(new DeleteLibraryAction(node.entry.id));
|
||||
}
|
||||
}
|
||||
|
||||
createLibrary() {
|
||||
this.store.dispatch(new CreateLibraryAction());
|
||||
}
|
||||
}
|
||||
|
51
src/app/dialogs/library/form.validators.ts
Normal file
51
src/app/dialogs/library/form.validators.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
/*!
|
||||
* @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 { AbstractControl, FormControl } from '@angular/forms';
|
||||
import { ContentApiService } from '../../services/content-api.service';
|
||||
|
||||
export class SiteIdValidator {
|
||||
static createValidator(contentApiService: ContentApiService) {
|
||||
let 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);
|
||||
});
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export function forbidSpecialCharacters({ value }: FormControl) {
|
||||
const validCharacters: RegExp = /[^A-Za-z0-9-]/;
|
||||
const isValid: boolean = !validCharacters.test(value);
|
||||
|
||||
return (isValid) ? null : {
|
||||
message: 'LIBRARY.ERRORS.ILLEGAL_CHARACTERS'
|
||||
};
|
||||
}
|
79
src/app/dialogs/library/library.dialog.html
Normal file
79
src/app/dialogs/library/library.dialog.html
Normal file
@@ -0,0 +1,79 @@
|
||||
<h2 mat-dialog-title>
|
||||
{{ createTitle | translate }}
|
||||
</h2>
|
||||
|
||||
<mat-dialog-content>
|
||||
<form novalidate [formGroup]="form" (submit)="submit()">
|
||||
<mat-input-container>
|
||||
<input
|
||||
placeholder="{{ 'LIBRARY.DIALOG.FORM.NAME' | translate }}"
|
||||
required
|
||||
matInput
|
||||
[formControl]="form.controls['title']"
|
||||
/>
|
||||
|
||||
<mat-error *ngIf="form.controls['title'].hasError('maxlength')">
|
||||
{{ 'LIBRARY.ERRORS.DESCRIPTION_TOO_LONG' | translate }}
|
||||
</mat-error>
|
||||
</mat-input-container>
|
||||
|
||||
<mat-input-container>
|
||||
<input
|
||||
required
|
||||
placeholder="{{ 'LIBRARY.DIALOG.FORM.SITE_ID' | translate }}"
|
||||
matInput
|
||||
[formControl]="form.controls['id']"
|
||||
/>
|
||||
|
||||
<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-input-container>
|
||||
|
||||
<mat-input-container>
|
||||
<textarea
|
||||
matInput
|
||||
placeholder="{{ 'LIBRARY.DIALOG.FORM.DESCRIPTION' | translate }}"
|
||||
rows="3"
|
||||
[formControl]="form.controls['description']"></textarea>
|
||||
|
||||
<mat-error *ngIf="form.controls['description'].hasError('maxlength')">
|
||||
{{ 'LIBRARY.ERRORS.DESCRIPTION_TOO_LONG' | translate }}
|
||||
</mat-error>
|
||||
</mat-input-container>
|
||||
|
||||
<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>
|
20
src/app/dialogs/library/library.dialog.scss
Normal file
20
src/app/dialogs/library/library.dialog.scss
Normal file
@@ -0,0 +1,20 @@
|
||||
|
||||
.mat-radio-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin: 0 0 20px 0;
|
||||
}
|
||||
|
||||
.mat-radio-group .mat-radio-button {
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.mat-input-container {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.actions-buttons {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-end;
|
||||
}
|
140
src/app/dialogs/library/library.dialog.ts
Normal file
140
src/app/dialogs/library/library.dialog.ts
Normal file
@@ -0,0 +1,140 @@
|
||||
/*!
|
||||
* @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 } from 'rxjs/Observable';
|
||||
import { Component, OnInit, Output, EventEmitter } from '@angular/core';
|
||||
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||
import { MatDialogRef } from '@angular/material';
|
||||
import { SiteBody } from 'alfresco-js-api';
|
||||
import { ContentApiService } from '../../services/content-api.service';
|
||||
import { SiteIdValidator, forbidSpecialCharacters } from './form.validators';
|
||||
|
||||
|
||||
@Component({
|
||||
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()
|
||||
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
|
||||
.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();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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(
|
||||
(folder: any) => {
|
||||
this.success.emit(folder);
|
||||
dialog.close(folder);
|
||||
},
|
||||
(error) => this.error.emit('LIBRARY.ERRORS.GENERIC')
|
||||
);
|
||||
}
|
||||
|
||||
visibilityChangeHandler(event) {
|
||||
this.visibilityOption = event.value;
|
||||
}
|
||||
|
||||
private create(): Observable<any> {
|
||||
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, '');
|
||||
}
|
||||
}
|
@@ -37,7 +37,9 @@ import {
|
||||
FavoritePaging,
|
||||
SharedLinkPaging,
|
||||
SearchRequest,
|
||||
ResultSetPaging
|
||||
ResultSetPaging,
|
||||
SiteBody,
|
||||
SiteEntry
|
||||
} from 'alfresco-js-api';
|
||||
|
||||
@Injectable()
|
||||
@@ -226,4 +228,16 @@ export class ContentApiService {
|
||||
this.api.sitesApi.deleteSite(siteId, opts)
|
||||
);
|
||||
}
|
||||
|
||||
createSite(siteBody: SiteBody, opts?: {skipConfiguration?: boolean, skipAddToFavorites?: boolean}): Observable<SiteEntry> {
|
||||
return Observable.fromPromise(
|
||||
this.api.sitesApi.createSite(siteBody, opts)
|
||||
);
|
||||
}
|
||||
|
||||
getSite(siteId?: string, opts?: { relations?: Array<string>, fields?: Array<string> }): Observable<SiteEntry> {
|
||||
return Observable.fromPromise(
|
||||
this.api.sitesApi.getSite(siteId, opts)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@@ -26,8 +26,14 @@
|
||||
import { Action } from '@ngrx/store';
|
||||
|
||||
export const DELETE_LIBRARY = 'DELETE_LIBRARY';
|
||||
export const CREATE_LIBRARY = 'CREATE_LIBRARY';
|
||||
|
||||
export class DeleteLibraryAction implements Action {
|
||||
readonly type = DELETE_LIBRARY;
|
||||
constructor(public payload: string) {}
|
||||
}
|
||||
|
||||
export class CreateLibraryAction implements Action {
|
||||
readonly type = CREATE_LIBRARY;
|
||||
constructor() {}
|
||||
}
|
||||
|
@@ -26,7 +26,10 @@
|
||||
import { Effect, Actions, ofType } from '@ngrx/effects';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { map } from 'rxjs/operators';
|
||||
import { DeleteLibraryAction, DELETE_LIBRARY } from '../actions';
|
||||
import {
|
||||
DeleteLibraryAction, DELETE_LIBRARY,
|
||||
CreateLibraryAction, CREATE_LIBRARY
|
||||
} from '../actions';
|
||||
import {
|
||||
SnackbarInfoAction,
|
||||
SnackbarErrorAction
|
||||
@@ -49,23 +52,41 @@ export class SiteEffects {
|
||||
deleteLibrary$ = this.actions$.pipe(
|
||||
ofType<DeleteLibraryAction>(DELETE_LIBRARY),
|
||||
map(action => {
|
||||
this.contentApi.deleteSite(action.payload).subscribe(
|
||||
() => {
|
||||
this.content.siteDeleted.next(action.payload);
|
||||
this.store.dispatch(
|
||||
new SnackbarInfoAction(
|
||||
'APP.MESSAGES.INFO.LIBRARY_DELETED'
|
||||
)
|
||||
);
|
||||
},
|
||||
() => {
|
||||
this.store.dispatch(
|
||||
new SnackbarErrorAction(
|
||||
'APP.MESSAGES.ERRORS.DELETE_LIBRARY_FAILED'
|
||||
)
|
||||
);
|
||||
}
|
||||
);
|
||||
if (action.payload) {
|
||||
this.deleteLibrary(action.payload);
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
@Effect({ dispatch: false })
|
||||
createLibrary$ = this.actions$.pipe(
|
||||
ofType<CreateLibraryAction>(CREATE_LIBRARY),
|
||||
map(action => {
|
||||
this.createLibrary();
|
||||
})
|
||||
);
|
||||
|
||||
private deleteLibrary(id: string) {
|
||||
this.contentApi.deleteSite(id).subscribe(
|
||||
() => {
|
||||
this.content.libraryDeleted.next(id);
|
||||
this.store.dispatch(
|
||||
new SnackbarInfoAction(
|
||||
'APP.MESSAGES.INFO.LIBRARY_DELETED'
|
||||
)
|
||||
);
|
||||
},
|
||||
() => {
|
||||
this.store.dispatch(
|
||||
new SnackbarErrorAction(
|
||||
'APP.MESSAGES.ERRORS.DELETE_LIBRARY_FAILED'
|
||||
)
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private createLibrary() {
|
||||
this.content.createLibrary();
|
||||
}
|
||||
}
|
||||
|
@@ -269,6 +269,32 @@
|
||||
"NO_PERMISSION": "You don't have permission to manage the versions of this content."
|
||||
}
|
||||
},
|
||||
"LIBRARY": {
|
||||
"DIALOG": {
|
||||
"CREATE_TITLE": "Create Site",
|
||||
"CREATE": "Create",
|
||||
"CANCEL": "Cancel",
|
||||
"FORM": {
|
||||
"DESCRIPTION": "Description",
|
||||
"SITE_ID": "Site ID",
|
||||
"NAME": "Name"
|
||||
}
|
||||
},
|
||||
"VISIBILITY": {
|
||||
"PRIVATE": "Private",
|
||||
"PUBLIC": "Public",
|
||||
"MODERATED": "Moderated"
|
||||
},
|
||||
"ERRORS": {
|
||||
"GENERIC": "There was an error",
|
||||
"EXISTENT_SITE": "ID already used (it might be in the trashcan).",
|
||||
"ID_TOO_LONG": "Use 72 characters or less for the URL name",
|
||||
"DESCRIPTION_TOO_LONG": "Use 512 characters or less for description",
|
||||
"TITLE_TOO_LONG": "Use 256 characters or less for title",
|
||||
"ILLEGAL_CHARACTERS": "Use characters a-z, A-Z, 0-9 and - only"
|
||||
}
|
||||
},
|
||||
|
||||
"SEARCH": {
|
||||
"SORT": {
|
||||
"RELEVANCE": "Relevance",
|
||||
|
Reference in New Issue
Block a user