mirror of
https://github.com/Alfresco/alfresco-ng2-components.git
synced 2025-07-24 17:32:15 +00:00
[ADF-5316] - Content Type (#6602)
* [ADF-5316] - added content type editing for nodes * [ADF-5316] - added content type editing for nodes * [ADF-5316] - fix unit test #1 * [ADF-5316] - fix unit test #2 * [ADF-5316] - fix unit test - final * Removed failing lint word * [ADF-5316] - added alfresco api real calls * Build fixed * [ADF-5316] - fixed second loop trigger on model * [ADF-5316] - fixed unit tests * [ADF-5316] - removed unused stream * [ADF-5316] - fixed package.json * [ADF-5316] - added missing unit tests * [ADF-5316] - fixed wrong import Co-authored-by: Vito Albano <vitoalbano@vitoalbano-mbp-0120.local>
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
---
|
||||
Title: Content Type Dialog component
|
||||
Added: v2.0.0
|
||||
Status: Active
|
||||
Last reviewed: 2021-01-20
|
||||
---
|
||||
|
||||
# [Content Type Dialog component](../../../lib/content-services/src/lib/content-type/content-type-dialog.component.ts "Defined in content-type-dialog.component.ts")
|
||||
|
||||
Confirm dialog when user changes content type of a node.
|
||||
|
||||
## Details
|
||||
|
||||
The [Content Type Dialog component](content-type-dialog.component.md) works as a dialog showing a confirm message when the user changes the conten type of a node. It is showing the properties of the new content type selected.
|
||||
|
||||
### Showing the dialog
|
||||
|
||||
Unlike most components, the [Content Type Dialog component](content-type-dialog.component.md) is typically shown in a dialog box
|
||||
rather than the main page and you are responsible for opening the dialog yourself. You can use the
|
||||
[Angular Material Dialog](https://material.angular.io/components/dialog/overview) for this,
|
||||
as shown in the usage example. ADF provides the [`ContentTypeDialogComponentData`](../../../lib/content-services/src/lib/content-type/content-type-metadata.interface.ts) interface
|
||||
to work with the Dialog's
|
||||
[data option](https://material.angular.io/components/dialog/overview#sharing-data-with-the-dialog-component-):
|
||||
|
||||
```ts
|
||||
export interface ContentTypeDialogComponentData {
|
||||
title: string;
|
||||
description: string;
|
||||
confirmMessage: string;
|
||||
select: Subject<boolean>;
|
||||
nodeType?: string;
|
||||
}
|
||||
```
|
||||
|
||||
The properties are described in the table below:
|
||||
|
||||
| Name | Type | Default value | Description |
|
||||
| ---- | ---- | ------------- | ----------- |
|
||||
| title | `string` | "" | Dialog title |
|
||||
| description | `string` | "" | Text to appear as description under the dialog title |
|
||||
| confirmMessage | `string` | "" | Text that will be showed on the top of properties list accordion |
|
||||
| select | [`Subject<Node>`](https://github.com/Alfresco/alfresco-js-api/blob/develop/src/api/content-rest-api/docs/Node.md) | | Event emitted when apply button is clicked |
|
||||
| nodeType | `string` | "" | current prefixed name of the content type selected |
|
||||
|
||||
If you don't want to manage the dialog yourself then it is easier to use the
|
||||
methods of the Content Type Property Service, which create
|
||||
the dialog for you.
|
||||
|
||||
### Usage example
|
||||
|
||||
```ts
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { AspectListDialogComponentData, AspectListDialogComponent} from '@adf/content-services'
|
||||
import { Subject } from 'rxjs/Subject';
|
||||
...
|
||||
constructor(dialog: MatDialog ... ) {}
|
||||
openSelectorDialog() {
|
||||
const data: ContentTypeDialogComponentData = {
|
||||
title: 'CORE.METADATA.CONTENT_TYPE.DIALOG.TITLE',
|
||||
description: 'CORE.METADATA.CONTENT_TYPE.DIALOG.DESCRIPTION',
|
||||
confirmMessage: 'CORE.METADATA.CONTENT_TYPE.DIALOG.CONFIRM',
|
||||
select: select,
|
||||
nodeType
|
||||
};
|
||||
this.dialog.open(
|
||||
ContentTypeDialogComponent,
|
||||
{
|
||||
data, panelClass: 'adf-content-type-dialog',
|
||||
width: '630px'
|
||||
}
|
||||
);
|
||||
data.select.subscribe((selections: Node[]) => {
|
||||
// Use or store selection...
|
||||
},
|
||||
(error)=>{
|
||||
//your error handling
|
||||
},
|
||||
()=>{
|
||||
//action called when an action or cancel is clicked on the dialog
|
||||
this.dialog.closeAll();
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
All the results will be streamed to the select [subject](http://reactivex.io/rxjs/manual/overview.html#subject) present in the [`ContentTypeDialogData`](../../../lib/content-services/src/lib/content-type/content-type-metadata.interface.ts) object passed to the dialog.
|
||||
When the dialog action is selected by clicking, the `data.select` stream will be completed.
|
@@ -24,11 +24,14 @@ import { setupTestBed, AllowableOperationsEnum } from '@alfresco/adf-core';
|
||||
import { ContentTestingModule } from '../../../testing/content.testing.module';
|
||||
import { SimpleChange } from '@angular/core';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { ContentMetadataService } from '../../services/content-metadata.service';
|
||||
import { of } from 'rxjs';
|
||||
|
||||
describe('ContentMetadataCardComponent', () => {
|
||||
|
||||
let component: ContentMetadataCardComponent;
|
||||
let fixture: ComponentFixture<ContentMetadataCardComponent>;
|
||||
let contentMetadataService: ContentMetadataService;
|
||||
let node: Node;
|
||||
const preset = 'custom-preset';
|
||||
|
||||
@@ -41,6 +44,7 @@ describe('ContentMetadataCardComponent', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(ContentMetadataCardComponent);
|
||||
contentMetadataService = TestBed.inject(ContentMetadataService);
|
||||
component = fixture.componentInstance;
|
||||
node = <Node> {
|
||||
aspectNames: [],
|
||||
@@ -53,6 +57,7 @@ describe('ContentMetadataCardComponent', () => {
|
||||
|
||||
component.node = node;
|
||||
component.preset = preset;
|
||||
spyOn(contentMetadataService, 'getContentTypeProperty').and.returnValue(of([]));
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
|
@@ -9,7 +9,6 @@
|
||||
{{ 'CORE.METADATA.BASIC.HEADER' | translate }}
|
||||
</mat-panel-title>
|
||||
</mat-expansion-panel-header>
|
||||
|
||||
<adf-card-view
|
||||
(keydown)="keyDown($event)"
|
||||
[properties]="basicProperties$ | async"
|
||||
|
@@ -58,7 +58,7 @@ describe('ContentMetadataComponent', () => {
|
||||
node = <Node> {
|
||||
id: 'node-id',
|
||||
aspectNames: [],
|
||||
nodeType: '',
|
||||
nodeType: 'cm:node',
|
||||
content: {},
|
||||
properties: {},
|
||||
createdByUser: {},
|
||||
@@ -75,6 +75,7 @@ describe('ContentMetadataComponent', () => {
|
||||
|
||||
component.node = node;
|
||||
component.preset = preset;
|
||||
spyOn(contentMetadataService, 'getContentTypeProperty').and.returnValue(of([]));
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
@@ -169,10 +170,33 @@ describe('ContentMetadataComponent', () => {
|
||||
saveButton.nativeElement.click();
|
||||
fixture.detectChanges();
|
||||
}));
|
||||
|
||||
it('should open the confirm dialog when content type is changed', fakeAsync(() => {
|
||||
component.editable = true;
|
||||
const property = <CardViewBaseItemModel> { key: 'nodeType', value: 'ft:sbiruli' };
|
||||
const expectedNode = Object.assign({}, node, { nodeType: 'ft:sbiruli' });
|
||||
spyOn(contentMetadataService, 'openConfirmDialog').and.returnValue(of(true));
|
||||
spyOn(nodesApiService, 'updateNode').and.callFake(() => {
|
||||
return of(expectedNode);
|
||||
});
|
||||
|
||||
updateService.update(property, 'ft:poppoli');
|
||||
tick(600);
|
||||
|
||||
fixture.detectChanges();
|
||||
tick(100);
|
||||
const saveButton = fixture.debugElement.query(By.css('[data-automation-id="save-metadata"]'));
|
||||
saveButton.nativeElement.click();
|
||||
|
||||
tick(100);
|
||||
expect(component.node).toEqual(expectedNode);
|
||||
expect(contentMetadataService.openConfirmDialog).toHaveBeenCalledWith({nodeType: 'ft:poppoli'});
|
||||
expect(nodesApiService.updateNode).toHaveBeenCalled();
|
||||
}));
|
||||
});
|
||||
|
||||
describe('Reseting', () => {
|
||||
it('should reset changedProperties on reset click', async(async () => {
|
||||
it('should reset changedProperties on reset click', async () => {
|
||||
component.changedProperties = { properties: { 'property-key': 'updated-value' } };
|
||||
component.hasMetadataChanged = true;
|
||||
component.editable = true;
|
||||
@@ -189,7 +213,7 @@ describe('ContentMetadataComponent', () => {
|
||||
fixture.detectChanges();
|
||||
expect(component.changedProperties).toEqual({});
|
||||
expect(nodesApiService.updateNode).not.toHaveBeenCalled();
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
describe('Properties loading', () => {
|
||||
@@ -205,6 +229,7 @@ describe('ContentMetadataComponent', () => {
|
||||
|
||||
component.ngOnChanges({ node: new SimpleChange(node, expectedNode, false) });
|
||||
|
||||
expect(contentMetadataService.getContentTypeProperty).toHaveBeenCalledWith(node.nodeType);
|
||||
expect(contentMetadataService.getBasicProperties).toHaveBeenCalledWith(expectedNode);
|
||||
});
|
||||
|
||||
@@ -221,7 +246,7 @@ describe('ContentMetadataComponent', () => {
|
||||
component.basicProperties$.subscribe(() => {
|
||||
fixture.detectChanges();
|
||||
const basicPropertiesComponent = fixture.debugElement.query(By.directive(CardViewComponent)).componentInstance;
|
||||
expect(basicPropertiesComponent.properties).toBe(expectedProperties);
|
||||
expect(basicPropertiesComponent.properties.length).toBe(expectedProperties.length);
|
||||
});
|
||||
}));
|
||||
|
||||
|
@@ -17,7 +17,7 @@
|
||||
|
||||
import { Component, Input, OnChanges, OnDestroy, OnInit, SimpleChanges, ViewEncapsulation } from '@angular/core';
|
||||
import { Node } from '@alfresco/js-api';
|
||||
import { Observable, Subject, of } from 'rxjs';
|
||||
import { Observable, Subject, of, zip } from 'rxjs';
|
||||
import {
|
||||
CardViewItem,
|
||||
NodesApiService,
|
||||
@@ -30,7 +30,7 @@ import {
|
||||
} from '@alfresco/adf-core';
|
||||
import { ContentMetadataService } from '../../services/content-metadata.service';
|
||||
import { CardViewGroup } from '../../interfaces/content-metadata.interfaces';
|
||||
import { takeUntil, debounceTime, catchError } from 'rxjs/operators';
|
||||
import { takeUntil, debounceTime, catchError, map } from 'rxjs/operators';
|
||||
|
||||
@Component({
|
||||
selector: 'adf-content-metadata',
|
||||
@@ -155,11 +155,18 @@ export class ContentMetadataComponent implements OnChanges, OnInit, OnDestroy {
|
||||
|
||||
private loadProperties(node: Node) {
|
||||
if (node) {
|
||||
this.basicProperties$ = this.contentMetadataService.getBasicProperties(node);
|
||||
this.basicProperties$ = this.getProperties(node);
|
||||
this.groupedProperties$ = this.contentMetadataService.getGroupedProperties(node, this.preset);
|
||||
}
|
||||
}
|
||||
|
||||
private getProperties(node: Node) {
|
||||
const properties$ = this.contentMetadataService.getBasicProperties(node);
|
||||
const contentTypeProperty$ = this.contentMetadataService.getContentTypeProperty(node.nodeType);
|
||||
return zip(properties$, contentTypeProperty$)
|
||||
.pipe(map(([properties, contentTypeProperty]) => [...properties, ...contentTypeProperty]));
|
||||
}
|
||||
|
||||
updateChanges(updatedNodeChanges) {
|
||||
Object.keys(updatedNodeChanges).map((propertyGroup: string) => {
|
||||
if (typeof updatedNodeChanges[propertyGroup] === 'object') {
|
||||
@@ -174,6 +181,16 @@ export class ContentMetadataComponent implements OnChanges, OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
saveChanges() {
|
||||
if (this.hasContentTypeChanged(this.changedProperties)) {
|
||||
this.contentMetadataService.openConfirmDialog(this.changedProperties).subscribe(() => {
|
||||
this.updateNode();
|
||||
});
|
||||
} else {
|
||||
this.updateNode();
|
||||
}
|
||||
}
|
||||
|
||||
private updateNode() {
|
||||
this.nodesApiService.updateNode(this.node.id, this.changedProperties).pipe(
|
||||
catchError((err) => {
|
||||
this.cardViewUpdateService.updateElement(this.targetProperty);
|
||||
@@ -189,6 +206,10 @@ export class ContentMetadataComponent implements OnChanges, OnInit, OnDestroy {
|
||||
});
|
||||
}
|
||||
|
||||
private hasContentTypeChanged(changedProperties): boolean {
|
||||
return !!changedProperties?.nodeType;
|
||||
}
|
||||
|
||||
revertChanges() {
|
||||
this.changedProperties = {};
|
||||
this.hasMetadataChanged = false;
|
||||
|
@@ -22,6 +22,7 @@ export * from './services/content-metadata.service';
|
||||
export * from './services/property-descriptors.service';
|
||||
export * from './services/property-groups-translator.service';
|
||||
export * from './services/config/content-metadata-config.factory';
|
||||
export * from './services/content-type-property.service';
|
||||
|
||||
export * from './services/config/indifferent-config.service';
|
||||
export * from './services/config/layout-oriented-config.service';
|
||||
|
@@ -18,7 +18,6 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Node } from '@alfresco/js-api';
|
||||
import { CardViewDateItemModel, CardViewTextItemModel, FileSizePipe } from '@alfresco/adf-core';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
|
@@ -18,17 +18,19 @@
|
||||
import { AlfrescoApiService, AppConfigService, setupTestBed } from '@alfresco/adf-core';
|
||||
import { ClassesApi, Node } from '@alfresco/js-api';
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import { ContentTestingModule } from '../../testing/content.testing.module';
|
||||
import { ContentMetadataService } from './content-metadata.service';
|
||||
import { of } from 'rxjs';
|
||||
import { PropertyGroup } from '../interfaces/property-group.interface';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { ContentTypePropertiesService } from './content-type-property.service';
|
||||
import { ContentTestingModule } from '../../testing/content.testing.module';
|
||||
|
||||
describe('ContentMetaDataService', () => {
|
||||
|
||||
let service: ContentMetadataService;
|
||||
let classesApi: ClassesApi;
|
||||
let appConfig: AppConfigService;
|
||||
let contentPropertyService: ContentTypePropertiesService;
|
||||
|
||||
const exifResponse: PropertyGroup = {
|
||||
name: 'exif:exif',
|
||||
@@ -64,6 +66,7 @@ describe('ContentMetaDataService', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
service = TestBed.inject(ContentMetadataService);
|
||||
contentPropertyService = TestBed.inject(ContentTypePropertiesService);
|
||||
const alfrescoApiService = TestBed.inject(AlfrescoApiService);
|
||||
classesApi = alfrescoApiService.classesApi;
|
||||
appConfig = TestBed.inject(AppConfigService);
|
||||
@@ -89,6 +92,28 @@ describe('ContentMetaDataService', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('should return the content type property', () => {
|
||||
spyOn(contentPropertyService, 'getContentTypeCardItem').and.returnValue(of({ label: 'hello i am a weird content type'}));
|
||||
|
||||
service.getContentTypeProperty('fn:fakenode').subscribe(
|
||||
(res: any) => {
|
||||
expect(res).toBeDefined();
|
||||
expect(res).not.toBeNull();
|
||||
expect(res.label).toBe('hello i am a weird content type');
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('should trigger the opening of the content type dialog', () => {
|
||||
spyOn(contentPropertyService, 'openContentTypeDialogConfirm').and.returnValue(of());
|
||||
|
||||
service.openConfirmDialog('fn:fakenode').subscribe(
|
||||
() => {
|
||||
expect(contentPropertyService.openContentTypeDialogConfirm).toHaveBeenCalledWith('fn:fakenode');
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
describe('AspectOriented preset', () => {
|
||||
|
||||
it('should return response with exif property', (done) => {
|
||||
|
@@ -25,7 +25,7 @@ import { CardViewGroup, OrganisedPropertyGroup } from '../interfaces/content-met
|
||||
import { ContentMetadataConfigFactory } from './config/content-metadata-config.factory';
|
||||
import { PropertyDescriptorsService } from './property-descriptors.service';
|
||||
import { map, switchMap } from 'rxjs/operators';
|
||||
|
||||
import { ContentTypePropertiesService } from './content-type-property.service';
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
@@ -36,13 +36,22 @@ export class ContentMetadataService {
|
||||
constructor(private basicPropertiesService: BasicPropertiesService,
|
||||
private contentMetadataConfigFactory: ContentMetadataConfigFactory,
|
||||
private propertyGroupTranslatorService: PropertyGroupTranslatorService,
|
||||
private propertyDescriptorsService: PropertyDescriptorsService) {
|
||||
private propertyDescriptorsService: PropertyDescriptorsService,
|
||||
private contentTypePropertyService: ContentTypePropertiesService) {
|
||||
}
|
||||
|
||||
getBasicProperties(node: Node): Observable<CardViewItem[]> {
|
||||
return of(this.basicPropertiesService.getProperties(node));
|
||||
}
|
||||
|
||||
getContentTypeProperty(nodeType: string): Observable<CardViewItem[]> {
|
||||
return this.contentTypePropertyService.getContentTypeCardItem(nodeType);
|
||||
}
|
||||
|
||||
openConfirmDialog(changedProperties): Observable<any> {
|
||||
return this.contentTypePropertyService.openContentTypeDialogConfirm(changedProperties.nodeType);
|
||||
}
|
||||
|
||||
getGroupedProperties(node: Node, presetName: string = 'default'): Observable<CardViewGroup[]> {
|
||||
let groupedProperties = of([]);
|
||||
|
||||
|
@@ -0,0 +1,86 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2019 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Node } from '@alfresco/js-api';
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import { ContentMetadataService } from './content-metadata.service';
|
||||
import { of } from 'rxjs';
|
||||
import { ContentTypePropertiesService } from './content-type-property.service';
|
||||
import { setupTestBed } from 'core';
|
||||
import { ContentTestingModule } from '../../testing/content.testing.module';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
|
||||
describe('ContentMetaDataService', () => {
|
||||
|
||||
let service: ContentMetadataService;
|
||||
let contentPropertyService: ContentTypePropertiesService;
|
||||
|
||||
setupTestBed({
|
||||
imports: [
|
||||
TranslateModule.forRoot(),
|
||||
ContentTestingModule
|
||||
]
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
service = TestBed.inject(ContentMetadataService);
|
||||
contentPropertyService = TestBed.inject(ContentTypePropertiesService);
|
||||
});
|
||||
|
||||
it('should return all the properties of the node', () => {
|
||||
const fakeNode: Node = <Node> {
|
||||
name: 'Node',
|
||||
id: 'fake-id',
|
||||
isFile: true,
|
||||
aspectNames: ['exif:exif'],
|
||||
createdByUser: {displayName: 'test-user'},
|
||||
modifiedByUser: {displayName: 'test-user-modified'}
|
||||
};
|
||||
|
||||
service.getBasicProperties(fakeNode).subscribe(
|
||||
(res) => {
|
||||
expect(res.length).toEqual(10);
|
||||
expect(res[0].value).toEqual('Node');
|
||||
expect(res[1].value).toBeFalsy();
|
||||
expect(res[2].value).toBe('test-user');
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('should return the content type property', () => {
|
||||
spyOn(contentPropertyService, 'getContentTypeCardItem').and.returnValue(of({ label: 'hello i am a weird content type'}));
|
||||
|
||||
service.getContentTypeProperty('fn:fakenode').subscribe(
|
||||
(res: any) => {
|
||||
expect(res).toBeDefined();
|
||||
expect(res).not.toBeNull();
|
||||
expect(res.label).toBe('hello i am a weird content type');
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('should trigger the opening of the content type dialog', () => {
|
||||
spyOn(contentPropertyService, 'openContentTypeDialogConfirm').and.returnValue(of());
|
||||
|
||||
service.openConfirmDialog('fn:fakenode').subscribe(
|
||||
() => {
|
||||
expect(contentPropertyService.openContentTypeDialogConfirm).toHaveBeenCalledWith('fn:fakenode');
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
});
|
@@ -0,0 +1,106 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2019 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Injectable } from '@angular/core';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { CardViewItem, CardViewSelectItemModel, CardViewSelectItemOption } from '@alfresco/adf-core';
|
||||
import { Observable, of, Subject, zip } from 'rxjs';
|
||||
import { distinctUntilChanged, map } from 'rxjs/operators';
|
||||
import { ContentTypeDialogComponent } from '../../content-type/content-type-dialog.component';
|
||||
import { ContentTypeDialogComponentData } from '../../content-type/content-type-metadata.interface';
|
||||
import { ContentTypeService } from '../../content-type/content-type.service';
|
||||
import { TypeEntry } from '@alfresco/js-api';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class ContentTypePropertiesService {
|
||||
|
||||
constructor(private contentTypeService: ContentTypeService, private dialog: MatDialog) {
|
||||
}
|
||||
|
||||
getContentTypeCardItem(nodeType: string): Observable<CardViewItem[]> {
|
||||
return this.contentTypeService.getContentTypeByPrefix(nodeType).
|
||||
pipe(
|
||||
map((contentType) => {
|
||||
const contentTypesOptions$ = this.getContentTypesAsSelectOption(contentType);
|
||||
const contentTypeCard = this.buildContentTypeSelectCardModel(contentType.entry.id, contentTypesOptions$);
|
||||
return [contentTypeCard];
|
||||
}));
|
||||
}
|
||||
|
||||
private buildContentTypeSelectCardModel(currentValue: string, options$: Observable<CardViewSelectItemOption<string>[]>): CardViewSelectItemModel<string> {
|
||||
const contentTypeCard = new CardViewSelectItemModel({
|
||||
label: 'CORE.METADATA.BASIC.CONTENT_TYPE',
|
||||
value: currentValue,
|
||||
key: 'nodeType',
|
||||
editable: true,
|
||||
options$: options$
|
||||
});
|
||||
|
||||
return contentTypeCard;
|
||||
}
|
||||
|
||||
private getContentTypesAsSelectOption(currentType: TypeEntry): Observable<CardViewSelectItemOption<string>[]> {
|
||||
const childrenTypes$ = this.contentTypeService.getContentTypeChildren(currentType.entry.id);
|
||||
return zip(childrenTypes$, of(currentType)).pipe(
|
||||
distinctUntilChanged(),
|
||||
map(([contentTypesEntries, currentContentType]) => {
|
||||
const updatedTypes = this.appendCurrentType(currentContentType, contentTypesEntries);
|
||||
return updatedTypes.map((contentType) => <CardViewSelectItemOption<string>> { key: contentType.entry.id, label: contentType.entry.title ?? contentType.entry.id});
|
||||
}));
|
||||
}
|
||||
|
||||
private appendCurrentType(currentType: TypeEntry, contentTypesEntries: TypeEntry[]): TypeEntry[] {
|
||||
const resultTypes = contentTypesEntries;
|
||||
if (contentTypesEntries.indexOf(currentType) === -1) {
|
||||
resultTypes.push(currentType);
|
||||
}
|
||||
return resultTypes;
|
||||
}
|
||||
|
||||
openContentTypeDialogConfirm(nodeType): Observable<boolean> {
|
||||
const select = new Subject<boolean>();
|
||||
select.subscribe({
|
||||
complete: this.close.bind(this)
|
||||
});
|
||||
|
||||
const data: ContentTypeDialogComponentData = {
|
||||
title: 'CORE.METADATA.CONTENT_TYPE.DIALOG.TITLE',
|
||||
description: 'CORE.METADATA.CONTENT_TYPE.DIALOG.DESCRIPTION',
|
||||
confirmMessage: 'CORE.METADATA.CONTENT_TYPE.DIALOG.CONFIRM',
|
||||
select: select,
|
||||
nodeType
|
||||
};
|
||||
|
||||
this.openDialog(data, 'adf-content-type-dialog', '600px');
|
||||
return select;
|
||||
}
|
||||
|
||||
close() {
|
||||
this.dialog.closeAll();
|
||||
}
|
||||
|
||||
private openDialog(data: ContentTypeDialogComponentData, panelClass: string, width: string) {
|
||||
this.dialog.open(ContentTypeDialogComponent, {
|
||||
data,
|
||||
panelClass,
|
||||
width,
|
||||
disableClose: true
|
||||
});
|
||||
}
|
||||
}
|
@@ -0,0 +1,45 @@
|
||||
<div class="adf-content-type-dialog">
|
||||
<h2 mat-dialog-title class="adf-content-type-dialog-title" data-automation-id="content-type-dialog-title">{{title |
|
||||
translate}}</h2>
|
||||
<mat-dialog-content class="mat-typography" class="adf-content-type-dialog-content"
|
||||
data-automation-id="content-type-dialog-content">
|
||||
<h4 data-automation-id="content-type-dialog-description">{{description | translate}}</h4>
|
||||
<p data-automation-id="content-type-dialog-confirm-message">{{confirmMessage | translate}}</p>
|
||||
<mat-accordion>
|
||||
<mat-expansion-panel class="adf-content-type-accordion">
|
||||
<mat-expansion-panel-header>
|
||||
<mat-panel-title>
|
||||
{{'CORE.METADATA.CONTENT_TYPE.DIALOG.VIEW_DETAILS' | translate}}
|
||||
</mat-panel-title>
|
||||
</mat-expansion-panel-header>
|
||||
<table mat-table [dataSource]="currentContentType?.entry?.properties"
|
||||
*ngIf="currentContentType?.entry?.properties?.length > 0" class="adf-content-type-table">
|
||||
<ng-container matColumnDef="name">
|
||||
<th mat-header-cell *matHeaderCellDef> {{'CORE.METADATA.CONTENT_TYPE.DIALOG.PROPERTY.NAME' |
|
||||
translate}} </th>
|
||||
<td mat-cell *matCellDef="let property"> {{property.id}} </td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="title">
|
||||
<th mat-header-cell *matHeaderCellDef>
|
||||
{{'CORE.METADATA.CONTENT_TYPE.DIALOG.PROPERTY.DESCRIPTION' | translate}} </th>
|
||||
<td mat-cell *matCellDef="let property"> {{property.title}} </td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="dataType">
|
||||
<th mat-header-cell *matHeaderCellDef> {{'CORE.METADATA.CONTENT_TYPE.DIALOG.PROPERTY.DATA_TYPE'
|
||||
| translate}} </th>
|
||||
<td mat-cell *matCellDef="let property"> {{property.dataType}} </td>
|
||||
</ng-container>
|
||||
<tr mat-header-row *matHeaderRowDef="propertyColumns"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: propertyColumns;"></tr>
|
||||
</table>
|
||||
</mat-expansion-panel>
|
||||
</mat-accordion>
|
||||
</mat-dialog-content>
|
||||
<mat-dialog-actions align="end">
|
||||
<button mat-button mat-dialog-close
|
||||
id="conten-type-dialog-actions-cancel">{{'CORE.METADATA.CONTENT_TYPE.DIALOG.CANCEL' | translate }}</button>
|
||||
<button mat-button class="adf-content-type-dialog-apply-button" id="content-type-dialog-apply-button"
|
||||
[mat-dialog-close]="true" cdkFocusInitial (click)="onApply()">{{'CORE.METADATA.CONTENT_TYPE.DIALOG.APPLY' |
|
||||
translate}}</button>
|
||||
</mat-dialog-actions>
|
||||
</div>
|
@@ -0,0 +1,41 @@
|
||||
@mixin adf-content-type-dialog-theme($theme) {
|
||||
$primary: map-get($theme, primary);
|
||||
$accent: map-get($theme, accent);
|
||||
$warn: map-get($theme, warn);
|
||||
$foreground: map-get($theme, foreground);
|
||||
$background: map-get($theme, background);
|
||||
|
||||
|
||||
.adf {
|
||||
|
||||
&-content-type-dialog {
|
||||
.mat-expansion-panel {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
&-content-type-accordion {
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
&-content-type-dialog-title {
|
||||
font-size: large;
|
||||
font-weight: 200;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
&-content-type-dialog-description {
|
||||
font-size: small;
|
||||
line-height: normal;
|
||||
}
|
||||
|
||||
&-content-type-table {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&-content-type-dialog-apply-button {
|
||||
color: mat-color($primary);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@@ -0,0 +1,158 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2019 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 { MAT_DIALOG_DATA, MatDialogModule, MatDialogRef } from '@angular/material/dialog';
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { of, Subject } from 'rxjs';
|
||||
import { ContentTestingModule } from '../testing/content.testing.module';
|
||||
import { ContentTypeDialogComponent } from './content-type-dialog.component';
|
||||
import { ContentTypeService } from './content-type.service';
|
||||
import { ContentTypeDialogComponentData } from './content-type-metadata.interface';
|
||||
import { TypeEntry } from '@alfresco/js-api';
|
||||
|
||||
const elementCustom: TypeEntry = {
|
||||
entry: {
|
||||
id: 'ck:pippobaudo',
|
||||
title: 'PIPPO-BAUDO',
|
||||
description: 'Doloro reaepgfihawpefih peahfa powfj p[qwofhjaq[ fq[owfj[qowjf[qowfgh[qowh f[qowhfj [qwohf',
|
||||
parentId: 'cm:content',
|
||||
properties: [
|
||||
{
|
||||
id: 'ck:PropA',
|
||||
dataType: 'ck:propA',
|
||||
defaultValue: 'HERE I AM',
|
||||
description: 'A property',
|
||||
isMandatory: false,
|
||||
isMandatoryEnforced: false,
|
||||
isMultiValued: false,
|
||||
title: 'PropertyA'
|
||||
},
|
||||
{
|
||||
id: 'ck:PropB',
|
||||
dataType: 'ck:propB',
|
||||
defaultValue: 'HERE I AM',
|
||||
description: 'A property',
|
||||
|
||||
isMandatory: false,
|
||||
isMandatoryEnforced: false,
|
||||
isMultiValued: false,
|
||||
title: 'PropertyB'
|
||||
},
|
||||
{
|
||||
id: 'ck:PropC',
|
||||
dataType: 'ck:propC',
|
||||
defaultValue: 'HERE I AM',
|
||||
description: 'A property',
|
||||
isMandatory: false,
|
||||
isMandatoryEnforced: false,
|
||||
isMultiValued: false,
|
||||
title: 'PropertyC'
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
describe('Content Type Dialog Component', () => {
|
||||
let fixture: ComponentFixture<ContentTypeDialogComponent>;
|
||||
let contentTypeService: ContentTypeService;
|
||||
let data: ContentTypeDialogComponentData;
|
||||
|
||||
beforeEach(async () => {
|
||||
data = <ContentTypeDialogComponentData> {
|
||||
title: 'Title',
|
||||
description: 'Description that can be longer or shorter',
|
||||
nodeType: 'fk:fakeNode',
|
||||
confirmMessage: 'Do you want to jump? Y/N',
|
||||
select: new Subject<boolean>()
|
||||
};
|
||||
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
TranslateModule.forRoot(),
|
||||
ContentTestingModule,
|
||||
MatDialogModule
|
||||
],
|
||||
providers: [
|
||||
{ provide: MAT_DIALOG_DATA, useValue: data },
|
||||
{
|
||||
provide: MatDialogRef,
|
||||
useValue: {
|
||||
keydownEvents: () => of(null),
|
||||
backdropClick: () => of(null),
|
||||
close: jasmine.createSpy('close')
|
||||
}
|
||||
}
|
||||
]
|
||||
}).compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
contentTypeService = TestBed.inject(ContentTypeService);
|
||||
spyOn(contentTypeService, 'getContentTypeByPrefix').and.returnValue(of(elementCustom));
|
||||
fixture = TestBed.createComponent(ContentTypeDialogComponent);
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
fixture.destroy();
|
||||
});
|
||||
|
||||
it('should show basic information for the dialog', () => {
|
||||
const dialogTitle = fixture.nativeElement.querySelector('[data-automation-id="content-type-dialog-title"]');
|
||||
expect(dialogTitle).not.toBeNull();
|
||||
expect(dialogTitle.innerText).toBe(data.title);
|
||||
|
||||
const description = fixture.nativeElement.querySelector('[data-automation-id="content-type-dialog-description"]');
|
||||
expect(description).not.toBeNull();
|
||||
expect(description.innerText).toBe(data.description);
|
||||
|
||||
const confirmMessage = fixture.nativeElement.querySelector('[data-automation-id="content-type-dialog-confirm-message"]');
|
||||
expect(confirmMessage).not.toBeNull();
|
||||
expect(confirmMessage.innerText).toBe(data.confirmMessage);
|
||||
|
||||
});
|
||||
|
||||
it('should complete the select stream Cancel button is clicked', (done) => {
|
||||
data.select.subscribe(() => { }, () => { }, () => done());
|
||||
const cancelButton: HTMLButtonElement = fixture.nativeElement.querySelector('#conten-type-dialog-actions-cancel');
|
||||
expect(cancelButton).toBeDefined();
|
||||
cancelButton.click();
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should show the property of the aspect', async () => {
|
||||
const showPropertyAccordon: HTMLButtonElement = fixture.nativeElement.querySelector('.adf-content-type-accordion .mat-expansion-panel-header');
|
||||
expect(showPropertyAccordon).toBeDefined();
|
||||
showPropertyAccordon.click();
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
const propertyShowed: NodeList = fixture.nativeElement.querySelectorAll('.adf-content-type-table .mat-row');
|
||||
expect(propertyShowed.length).toBe(3);
|
||||
});
|
||||
|
||||
it('should emit true when apply is clicked', (done) => {
|
||||
data.select.subscribe((value) => {
|
||||
expect(value).toBe(true);
|
||||
}, () => { }, () => done());
|
||||
const applyButton: HTMLButtonElement = fixture.nativeElement.querySelector('#content-type-dialog-apply-button');
|
||||
expect(applyButton).toBeDefined();
|
||||
applyButton.click();
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
});
|
@@ -0,0 +1,72 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2019 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 { TypeEntry } from '@alfresco/js-api';
|
||||
import { Component, Inject, OnInit, ViewEncapsulation } from '@angular/core';
|
||||
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
||||
import { ContentTypeDialogComponentData } from './content-type-metadata.interface';
|
||||
import { ContentTypeService } from './content-type.service';
|
||||
|
||||
@Component({
|
||||
selector: 'adf-content-type-dialog',
|
||||
templateUrl: './content-type-dialog.component.html',
|
||||
styleUrls: ['./content-type-dialog.component.scss'],
|
||||
encapsulation: ViewEncapsulation.None
|
||||
})
|
||||
export class ContentTypeDialogComponent implements OnInit {
|
||||
|
||||
title: string;
|
||||
description: string;
|
||||
nodeType: string;
|
||||
confirmMessage: string;
|
||||
|
||||
currentContentType: TypeEntry;
|
||||
|
||||
propertyColumns: string[] = ['name', 'title', 'dataType'];
|
||||
|
||||
constructor(private dialog: MatDialogRef<ContentTypeDialogComponent>,
|
||||
@Inject(MAT_DIALOG_DATA) public data: ContentTypeDialogComponentData,
|
||||
private contentTypeService: ContentTypeService) {
|
||||
this.title = data.title;
|
||||
this.description = data.description;
|
||||
this.confirmMessage = data.confirmMessage;
|
||||
this.nodeType = data.nodeType;
|
||||
|
||||
this.contentTypeService.getContentTypeByPrefix(this.nodeType).subscribe((contentTypeEntry) => {
|
||||
this.currentContentType = contentTypeEntry;
|
||||
});
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.dialog.backdropClick().subscribe(() => {
|
||||
this.close();
|
||||
});
|
||||
}
|
||||
|
||||
close() {
|
||||
this.data.select.complete();
|
||||
}
|
||||
|
||||
onCancel() {
|
||||
this.close();
|
||||
}
|
||||
|
||||
onApply() {
|
||||
this.data.select.next(true);
|
||||
this.close();
|
||||
}
|
||||
}
|
@@ -0,0 +1,26 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2019 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 { Subject } from 'rxjs';
|
||||
|
||||
export interface ContentTypeDialogComponentData {
|
||||
title: string;
|
||||
description: string;
|
||||
confirmMessage: string;
|
||||
select: Subject<boolean>;
|
||||
nodeType?: string;
|
||||
}
|
@@ -0,0 +1,45 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2019 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 { CommonModule } from '@angular/common';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { MatTableModule } from '@angular/material/table';
|
||||
import { MatExpansionModule } from '@angular/material/expansion';
|
||||
import { MatCheckboxModule } from '@angular/material/checkbox';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { MatDialogModule } from '@angular/material/dialog';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { ContentTypeDialogComponent } from './content-type-dialog.component';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
CommonModule,
|
||||
MatTableModule,
|
||||
MatExpansionModule,
|
||||
MatCheckboxModule,
|
||||
TranslateModule,
|
||||
MatDialogModule,
|
||||
MatButtonModule
|
||||
],
|
||||
exports: [
|
||||
ContentTypeDialogComponent
|
||||
],
|
||||
declarations: [
|
||||
ContentTypeDialogComponent
|
||||
]
|
||||
})
|
||||
export class ContentTypeModule { }
|
@@ -0,0 +1,66 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2019 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 { TypeEntry } from '@alfresco/js-api';
|
||||
import { AlfrescoApiService } from 'core';
|
||||
import { of } from 'rxjs';
|
||||
import { ContentTypeService } from './content-type.service';
|
||||
|
||||
describe('ContentTypeService', () => {
|
||||
|
||||
const fakeEntryMock: TypeEntry = {
|
||||
entry: {
|
||||
id : 'fake-type-id',
|
||||
title: 'fake-title',
|
||||
description: 'optional-fake-description',
|
||||
parentId: 'cm:parent',
|
||||
properties: []
|
||||
}
|
||||
};
|
||||
|
||||
const mockTypesApi = jasmine.createSpyObj('TypesApi', ['getType', 'listTypes']);
|
||||
const alfrescoApiService: AlfrescoApiService = new AlfrescoApiService(null, null);
|
||||
const contentTypeService: ContentTypeService = new ContentTypeService(alfrescoApiService);
|
||||
|
||||
beforeEach(() => {
|
||||
spyOnProperty(alfrescoApiService, 'typesApi').and.returnValue(mockTypesApi);
|
||||
});
|
||||
|
||||
it('should get a node type info', (done) => {
|
||||
mockTypesApi.getType.and.returnValue(of(fakeEntryMock));
|
||||
contentTypeService.getContentTypeByPrefix('whatever-whenever').subscribe((result) => {
|
||||
expect(result).toBeDefined();
|
||||
expect(result).not.toBeNull();
|
||||
expect(result.entry.id).toBe('fake-type-id');
|
||||
expect(mockTypesApi.getType).toHaveBeenCalledWith('whatever-whenever');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should get the list of children types', (done) => {
|
||||
mockTypesApi.listTypes.and.returnValue(of({ list: {entries: [fakeEntryMock]}}));
|
||||
contentTypeService.getContentTypeChildren('whatever-whenever').subscribe((results: TypeEntry[]) => {
|
||||
expect(results).toBeDefined();
|
||||
expect(results).not.toBeNull();
|
||||
expect(results.length).toBe(1);
|
||||
expect(results[0].entry.id).toBe('fake-type-id');
|
||||
expect(mockTypesApi.listTypes).toHaveBeenCalledWith({ where: '(parentIds in (\'whatever-whenever\') and not namespaceUri matches(\'http://www.alfresco.org/model.*\'))' });
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
@@ -0,0 +1,41 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2019 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 { TypeEntry, TypePaging } from '@alfresco/js-api';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { AlfrescoApiService } from '@alfresco/adf-core';
|
||||
import { from, Observable } from 'rxjs';
|
||||
import { map } from 'rxjs/operators';
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class ContentTypeService {
|
||||
|
||||
constructor(private alfrescoApiService: AlfrescoApiService) {
|
||||
}
|
||||
|
||||
getContentTypeByPrefix(prefixedType: string): Observable<TypeEntry> {
|
||||
return from(this.alfrescoApiService.typesApi.getType(prefixedType));
|
||||
}
|
||||
|
||||
getContentTypeChildren(nodeType: string): Observable<TypeEntry[]> {
|
||||
const opts = {where : `(parentIds in ('${nodeType}') and not namespaceUri matches('http://www.alfresco.org/model.*'))`};
|
||||
return from(this.alfrescoApiService.typesApi.listTypes(opts)).pipe(
|
||||
map((result: TypePaging) => result.list.entries)
|
||||
);
|
||||
}
|
||||
}
|
23
lib/content-services/src/lib/content-type/index.ts
Normal file
23
lib/content-services/src/lib/content-type/index.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2019 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export * from './content-type.service';
|
||||
// export * from './content-type.model';
|
||||
export * from './content-type-metadata.interface';
|
||||
export * from './content-type-dialog.component';
|
||||
|
||||
export * from './content-type.module';
|
18
lib/content-services/src/lib/content-type/public-api.ts
Normal file
18
lib/content-services/src/lib/content-type/public-api.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2019 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export * from './public-api';
|
@@ -39,6 +39,7 @@ import { FolderDirectiveModule } from './folder-directive/folder-directive.modul
|
||||
import { ContentMetadataModule } from './content-metadata/content-metadata.module';
|
||||
import { PermissionManagerModule } from './permission-manager/permission-manager.module';
|
||||
import { TreeViewModule } from './tree-view/tree-view.module';
|
||||
import { ContentTypeModule } from './content-type/content-type.module';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
@@ -63,7 +64,8 @@ import { TreeViewModule } from './tree-view/tree-view.module';
|
||||
ContentDirectiveModule,
|
||||
PermissionManagerModule,
|
||||
VersionManagerModule,
|
||||
TreeViewModule
|
||||
TreeViewModule,
|
||||
ContentTypeModule
|
||||
],
|
||||
providers: [
|
||||
{
|
||||
@@ -92,7 +94,8 @@ import { TreeViewModule } from './tree-view/tree-view.module';
|
||||
ContentDirectiveModule,
|
||||
PermissionManagerModule,
|
||||
VersionManagerModule,
|
||||
TreeViewModule
|
||||
TreeViewModule,
|
||||
ContentTypeModule
|
||||
]
|
||||
})
|
||||
export class ContentModule {
|
||||
|
@@ -334,7 +334,23 @@
|
||||
"CREATOR": "Creator",
|
||||
"CREATED_DATE": "Created Date",
|
||||
"MODIFIER": "Modifier",
|
||||
"MODIFIED_DATE": "Modified Date"
|
||||
"MODIFIED_DATE": "Modified Date",
|
||||
"CONTENT_TYPE": "Content Type"
|
||||
},
|
||||
"CONTENT_TYPE": {
|
||||
"DIALOG" :{
|
||||
"TITLE" : "Change content type",
|
||||
"DESCRIPTION": "Making this change to the content type will permanently add some properties and stored metadata to the document.",
|
||||
"CONFIRM": "Are you sure you want to change the content type?",
|
||||
"CANCEL": "CANCEL",
|
||||
"APPLY": "SAVE CHANGES",
|
||||
"VIEW_DETAILS": "View details",
|
||||
"PROPERTY" :{
|
||||
"NAME" : "Name",
|
||||
"DESCRIPTION": "Description",
|
||||
"DATA_TYPE": "Data type"
|
||||
}
|
||||
}
|
||||
},
|
||||
"ERRORS": {
|
||||
"GENERIC": "Error updating property",
|
||||
|
@@ -28,7 +28,6 @@ export const mockContentModelTextProperty = {
|
||||
defaultValue: '',
|
||||
mandatoryEnforced: false,
|
||||
indexed: false,
|
||||
facetable: 'FALSE',
|
||||
indexTokenisationMode: '',
|
||||
constraints: []
|
||||
};
|
||||
@@ -44,7 +43,6 @@ export const mockContentModelDateProperty = {
|
||||
defaultValue: '',
|
||||
mandatoryEnforced: false,
|
||||
indexed: false,
|
||||
facetable: 'FALSE',
|
||||
indexTokenisationMode: '',
|
||||
constraints: []
|
||||
};
|
||||
|
@@ -26,6 +26,7 @@
|
||||
@import '../permission-manager/components/add-permission/add-permission-panel.component';
|
||||
@import '../tree-view/components/tree-view.component';
|
||||
@import '../version-manager/version-comparison.component';
|
||||
@import '../content-type/content-type-dialog.component';
|
||||
|
||||
@mixin adf-content-services-theme($theme) {
|
||||
@include adf-breadcrumb-theme($theme);
|
||||
@@ -52,4 +53,5 @@
|
||||
@include adf-search-filter-theme($theme);
|
||||
@include adf-search-chip-list-theme($theme);
|
||||
@include adf-version-comparison-theme($theme);
|
||||
@include adf-content-type-dialog-theme($theme);
|
||||
}
|
||||
|
@@ -33,5 +33,6 @@ export * from './lib/permission-manager/index';
|
||||
export * from './lib/content-node-share/index';
|
||||
export * from './lib/tree-view/index';
|
||||
export * from './lib/group/index';
|
||||
export * from './lib/content-type/index';
|
||||
|
||||
export * from './lib/content.module';
|
||||
|
@@ -15,7 +15,7 @@
|
||||
<adf-select-filter-input *ngIf="showInputFilter" (change)="onFilterInputChange($event)"></adf-select-filter-input>
|
||||
|
||||
<mat-option *ngIf="showNoneOption()">{{ 'CORE.CARDVIEW.NONE' | translate }}</mat-option>
|
||||
<mat-option *ngFor="let option of getList() | async"
|
||||
<mat-option *ngFor="let option of list$ | async"
|
||||
[value]="option.key">
|
||||
{{ option.label | translate }}
|
||||
</mat-option>
|
||||
|
@@ -18,7 +18,7 @@
|
||||
import { Component, Input, OnChanges, OnDestroy } from '@angular/core';
|
||||
import { CardViewSelectItemModel } from '../../models/card-view-selectitem.model';
|
||||
import { CardViewUpdateService } from '../../services/card-view-update.service';
|
||||
import { Observable, Subject } from 'rxjs';
|
||||
import { BehaviorSubject, combineLatest, Observable, Subject } from 'rxjs';
|
||||
import { CardViewSelectItemOption } from '../../interfaces/card-view.interfaces';
|
||||
import { MatSelectChange } from '@angular/material/select';
|
||||
import { BaseCardView } from '../base-card-view';
|
||||
@@ -44,11 +44,13 @@ export class CardViewSelectItemComponent extends BaseCardView<CardViewSelectItem
|
||||
displayEmpty: boolean = true;
|
||||
|
||||
value: string | number;
|
||||
filter: string = '';
|
||||
filter$: BehaviorSubject<string> = new BehaviorSubject('');
|
||||
showInputFilter: boolean = false;
|
||||
|
||||
private onDestroy$ = new Subject<void>();
|
||||
|
||||
list$: Observable<CardViewSelectItemOption<string | number>[]> = null;
|
||||
|
||||
constructor(cardViewUpdateService: CardViewUpdateService, private appConfig: AppConfigService) {
|
||||
super(cardViewUpdateService);
|
||||
}
|
||||
@@ -63,10 +65,12 @@ export class CardViewSelectItemComponent extends BaseCardView<CardViewSelectItem
|
||||
.subscribe((options: CardViewSelectItemOption<string>[]) => {
|
||||
this.showInputFilter = options.length > this.optionsLimit;
|
||||
});
|
||||
|
||||
this.list$ = this.getList();
|
||||
}
|
||||
|
||||
onFilterInputChange(value: string) {
|
||||
this.filter = value.toString();
|
||||
this.filter$.next(value.toString());
|
||||
}
|
||||
|
||||
isEditable(): boolean {
|
||||
@@ -77,12 +81,12 @@ export class CardViewSelectItemComponent extends BaseCardView<CardViewSelectItem
|
||||
return this.options$ || this.property.options$;
|
||||
}
|
||||
|
||||
getList(): Observable<CardViewSelectItemOption<string>[]> {
|
||||
return this.getOptions()
|
||||
getList(): Observable<CardViewSelectItemOption<string | number>[]> {
|
||||
return combineLatest([this.getOptions(), this.filter$])
|
||||
.pipe(
|
||||
map((items: CardViewSelectItemOption<string>[]) => items.filter(
|
||||
(item: CardViewSelectItemOption<string>) =>
|
||||
item.label.toLowerCase().includes(this.filter.toLowerCase()))),
|
||||
map(([items, filter]) => items.filter((item: CardViewSelectItemOption<string>) =>
|
||||
filter ? item.label.toLowerCase().includes(filter.toLowerCase())
|
||||
: true)),
|
||||
takeUntil(this.onDestroy$)
|
||||
);
|
||||
}
|
||||
|
@@ -26,18 +26,25 @@ export class CardViewSelectItemModel<T> extends CardViewBaseItemModel implements
|
||||
type: string = 'select';
|
||||
options$: Observable<CardViewSelectItemOption<T>[]>;
|
||||
|
||||
valueFetch$: Observable<string> = null;
|
||||
|
||||
constructor(cardViewSelectItemProperties: CardViewSelectItemProperties<T>) {
|
||||
super(cardViewSelectItemProperties);
|
||||
|
||||
this.options$ = cardViewSelectItemProperties.options$;
|
||||
}
|
||||
|
||||
get displayValue() {
|
||||
return this.options$.pipe(
|
||||
this.valueFetch$ = this.options$.pipe(
|
||||
switchMap((options) => {
|
||||
const option = options.find((o) => o.key === this.value?.toString());
|
||||
return of(option ? option.label : '');
|
||||
})
|
||||
);
|
||||
}));
|
||||
}
|
||||
|
||||
get displayValue() {
|
||||
return this.valueFetch$;
|
||||
}
|
||||
|
||||
setValue(value: any) {
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
|
@@ -199,7 +199,23 @@
|
||||
"CREATOR": "Creator",
|
||||
"CREATED_DATE": "Created Date",
|
||||
"MODIFIER": "Modifier",
|
||||
"MODIFIED_DATE": "Modified Date"
|
||||
"MODIFIED_DATE": "Modified Date",
|
||||
"CONTENT_TYPE": "Content Type"
|
||||
},
|
||||
"CONTENT_TYPE": {
|
||||
"DIALOG" :{
|
||||
"TITLE" : "Change content type",
|
||||
"DESCRIPTION": "Making this change to the content type will permanently add some properties and stored metadata to the document.",
|
||||
"CONFIRM": "Are you sure you want to change the content type?",
|
||||
"CANCEL": "CANCEL",
|
||||
"APPLY": "SAVE CHANGES",
|
||||
"VIEW_DETAILS": "View details",
|
||||
"PROPERTY" :{
|
||||
"NAME" : "Name",
|
||||
"DESCRIPTION": "Description",
|
||||
"DATA_TYPE": "Data type"
|
||||
}
|
||||
}
|
||||
},
|
||||
"ACTIONS": {
|
||||
"EDIT": "Edit",
|
||||
|
@@ -23,7 +23,7 @@ import {
|
||||
SearchApi,
|
||||
Node,
|
||||
GroupsApi,
|
||||
AlfrescoApiCompatibility, AlfrescoApiConfig
|
||||
AlfrescoApiCompatibility, AlfrescoApiConfig, TypesApi
|
||||
} from '@alfresco/js-api';
|
||||
import { AppConfigService, AppConfigValues } from '../app-config/app-config.service';
|
||||
import { Subject, Observable, BehaviorSubject } from 'rxjs';
|
||||
@@ -102,6 +102,10 @@ export class AlfrescoApiService {
|
||||
return new GroupsApi(this.getInstance());
|
||||
}
|
||||
|
||||
get typesApi(): TypesApi {
|
||||
return new TypesApi(this.getInstance());
|
||||
}
|
||||
|
||||
constructor(
|
||||
protected appConfig: AppConfigService,
|
||||
protected storageService: StorageService) {
|
||||
|
Reference in New Issue
Block a user