diff --git a/demo-shell/package.json b/demo-shell/package.json index 63cd453c4b..5339ea939e 100644 --- a/demo-shell/package.json +++ b/demo-shell/package.json @@ -60,6 +60,10 @@ ], "private": true, "dependencies": { + "@alfresco/adf-content-services": "2.0.0", + "@alfresco/adf-core": "2.0.0", + "@alfresco/adf-insights": "2.0.1", + "@alfresco/adf-process-services": "2.0.0", "@angular/animations": "5.1.1", "@angular/cdk": "5.0.1", "@angular/common": "5.1.1", @@ -72,12 +76,10 @@ "@angular/platform-browser": "5.1.1", "@angular/platform-browser-dynamic": "5.1.1", "@angular/router": "5.1.1", + "@mat-datetimepicker/core": "^1.0.1", + "@mat-datetimepicker/moment": "^1.0.1", "@ngx-translate/core": "8.0.0", "alfresco-js-api": "2.0.0", - "@alfresco/adf-content-services": "2.0.0", - "@alfresco/adf-process-services": "2.0.0", - "@alfresco/adf-core": "2.0.0", - "@alfresco/adf-insights": "2.0.1", "chart.js": "2.5.0", "classlist.js": "1.1.20150312", "core-js": "2.4.1", diff --git a/demo-shell/src/app.config.json b/demo-shell/src/app.config.json index 9df5bdbb1d..ccb85fd352 100644 --- a/demo-shell/src/app.config.json +++ b/demo-shell/src/app.config.json @@ -359,5 +359,12 @@ } ] } + }, + "content-metadata": { + "presets": { + "default": { + "exif:exif": "*" + } + } } } diff --git a/docs/card-view.component.md b/docs/card-view.component.md index f06848068d..04c78b180e 100644 --- a/docs/card-view.component.md +++ b/docs/card-view.component.md @@ -38,10 +38,11 @@ Displays a configurable property list renderer. ### Properties -| Name | Type | Description | -| --- | --- | --- | -| properties | [CardViewItem](#cardviewitem)[] | (**required**) The custom view to render | -| editable | boolean | If the component editable or not | +| Name | Type | Default | Description | +| --- | --- | --- | --- | +| properties | [CardViewItem](#cardviewitem)[] | - | (**required**) The custom view to render | +| editable | boolean | - | If the component editable or not | +| displayEmpty | boolean | true | Whether to show empty properties in non-editable mode | ## Details @@ -75,11 +76,15 @@ export interface CardViewItem { } ``` -At the moment three models are defined out of the box: +At the moment these are the models supported: - **CardViewTextItemModel** - *for text items* - **CardViewMapItemModel** - *for map items* - **CardViewDateItemModel** - *for date items* +- **CardViewDatetimeItemModel** - *for datetime items* +- **CardViewBoolItemModel** - *for bool items (checkbox)* +- **CardViewIntItemModel** - *for integers items* +- **CardViewFloatItemModel** - *for float items* Each of them extends the abstract CardViewBaseItemModel class to add some custom functionality to the basic behaviour. @@ -100,13 +105,39 @@ Each of them extends the abstract CardViewBaseItemModel class to add some custom clickable: true }), new CardViewDateItemModel({ - label: 'Birth of date', + label: 'Date of birth', value: someDate, - key: 'birth-of-date', + key: 'date-of-birth', default: new Date(), format: '', editable: true }), + new CardViewDatetimeItemModel({ + label: 'Datetime of birth', + value: someDatetime, + key: 'datetime-of-birth', + default: new Date(), + format: '', + editable: true + }), + new CardViewBoolItemModel({ + label: 'Vulcanian', + value: true, + key: 'vulcanian', + default: false + }), + new CardViewIntItemModel({ + label: 'Intelligence', + value: 213, + key: 'intelligence', + default: 1 + }), + new CardViewFloatItemModel({ + label: 'Mental stability', + value: 9.9, + key: 'mental-stability', + default: 0.0 + }), ... ] ``` @@ -162,7 +193,7 @@ const mapItemProperty = new CardViewMapItemModel(options); | --- | --- | --- | --- | | label* | string | --- | The label to render | | value* | Map | --- | A map that contains the key value pairs | -| key* | string | --- | the key of the property. Have an important role when editing the property. | +| key* | string | --- | the key of the property. | | default | any | --- | The default value to render in case the value is empty | | displayValue* | string | --- | The value to render | | clickable | boolean | false | Whether the property clickable or not | @@ -179,12 +210,81 @@ const dateItemProperty = new CardViewDateItemModel(options); | --- | --- | --- | --- | | label* | string | --- | The label to render | | value* | any | --- | The original value | -| key* | string | --- | the key of the property. Have an important role when editing the property. | +| key* | string | --- | the key of the property. | | default | any | --- | The default value to render in case the value is empty | -| displayValue* | string | --- | The value to render | +| displayValue* | any | --- | The value to render | | editable | boolean | false | Whether the property editable or not | | format | boolean | "MMM DD YYYY" | any format that momentjs accepts | +#### Card Datetime Item + +CardViewDatetimeItemModel is a property type for datetime properties. + +```js +const datetimeItemProperty = new CardViewDatetimeItemModel(options); +``` + +| Name | Type | Default | Description | +| --- | --- | --- | --- | +| label* | string | --- | The label to render | +| value* | any | --- | The original value | +| key* | string | --- | the key of the property. | +| default | any | any | The default value to render in case the value is empty | +| displayValue* | string | --- | The value to render | +| editable | boolean | false | Whether the property editable or not | +| format | boolean | "MMM DD YYYY HH:mm" | any format that momentjs accepts | + +#### Card Bool Item + +CardViewBoolItemModel is a property type for boolean properties. + +```js +const boolItemProperty = new CardViewBoolItemModel(options); +``` + +| Name | Type | Default | Description | +| --- | --- | --- | --- | +| label* | string | --- | The label to render | +| value* | boolean | --- | The original value | +| key* | string | --- | the key of the property. | +| default | boolean | false | The default value to render in case the value is empty | +| displayValue* | boolean | --- | The value to render | +| editable | boolean | false | Whether the property editable or not | + +#### Card Int Item + +CardViewIntItemModel is a property type for integer properties. + +```js +const intItemProperty = new CardViewIntItemModel(options); +``` + +| Name | Type | Default | Description | +| --- | --- | --- | --- | +| label* | string | --- | The label to render | +| value* | integer | --- | The original value | +| key* | string | --- | the key of the property. | +| default | integer | --- | The default value to render in case the value is empty | +| displayValue* | integer | --- | The value to render | +| editable | boolean | false | Whether the property editable or not | + +#### Card Float Item + +CardViewFloatItemModel is a property type for float properties. + +```js +const floatItemProperty = new CardViewFloatItemModel(options); +``` + +| Name | Type | Default | Description | +| --- | --- | --- | --- | +| label* | string | --- | The label to render | +| value* | float | --- | The original value | +| key* | string | --- | the key of the property. | +| default | float | --- | The default value to render in case the value is empty | +| displayValue* | float | --- | The value to render | +| editable | boolean | false | Whether the property editable or not | + ### Defining your custom card Item Card item components are loaded dynamically, which makes you able to define your own custom component for the custom card item type. diff --git a/docs/content-metadata.component.md b/docs/content-metadata.component.md new file mode 100644 index 0000000000..5a679ab077 --- /dev/null +++ b/docs/content-metadata.component.md @@ -0,0 +1,113 @@ +# Content Metadata Card component + + + + + + +Allows a user to display and edit metadata related to a node. + + + +## Properties + +| Name | Type | Default | Description | +| --- | --- | --- | --- | +| node | MinimalNodeEntryEntity | - | (**required**) The node entity to fetch metadata about | +| displayEmpty | boolean | false | Display empty values in card view or not | +| preset | string | "*" | The metadata preset's name, which defines aspects and their properties | + +## Basic Usage + +The component shows metadata related to the given node. The component uses the card view component to render the properties of metadata aspects. +The different aspects and their properties to be shown can be configured as application config preset, for details about it see the related section below. By default the component only shows the basic properties of the node. Clicking on the pencil icon at the bottom, renders the underlying card view component in edit mode enabling to edit and update the metadata properties. + +```html + + +``` + +## Application config presets + +In the application config file you can define different presets for the metadata component or override the default preset. The **default** preset is "*" if not set, meaning the component will display every aspects and properties of the nodes without filtering. One can think about presets as **whitelist filters** for the content metadata component. + +Beside the default preset you can define as many presets as you want, if you'd like to use different metadata components with different presets. + +To understand presets better, you can have a look at on the following different example configurations. + +### Mimicking the default "default" preset + +If you don't have any preset configured manually in you application config, this would be equivalent as if you had the application config as defined below: + +```json +... +"content-metadata": { + "presets": { + "default": "*" + } +} +... +``` + +### Whitelisting only a few aspects in the default preset + +If you want to restrict to only a few aspects (e.g.: exif, your-custom-aspect), you have to use the name of that particular aspect to be able to whitelist it. In case of exif aspect this is "exif:exif". + +```json +... +"content-metadata": { + "presets": { + "default": { + "custom:aspect": "*", + "exif:exif": "*" + } + } +} +... +``` + +### Whitelisting only a few properties of a few aspects in the default preset + +If you want to filter more, you can do this on property level also. For this, you have to list the names of whitelisted aspect properties in an array of strings. Again, for identifying a property, you have to use its name. + +```json +... +"content-metadata": { + "presets": { + "default": { + "custom:aspect": "*", + "exif:exif": [ "exif:width", "exif:height"] + } + } +} +... +``` + +### Whitelisting only a few properties of a few aspects in a custom preset + +And finally, you can create any custom aspect following the same rules. + +```json +... +"content-metadata": { + "presets": { + "default": "*", + "kitten-images": { + "custom:aspect": "*", + "exif:exif": [ "exif:width", "exif:height"] + } + } +} +... +``` + +## What happens when there is a whitelisted aspect in the config but the given node doesn't relate to that aspect + +Nothing, this aspect (as it is not related to the node) will be simply ignored and not be displayed. The aspects to be displayed are calculated as an intersection of the preset's aspects and the aspects related to the node. + + + + diff --git a/docs/docassets/images/ContentMetadata.png b/docs/docassets/images/ContentMetadata.png new file mode 100644 index 0000000000..f643f199e4 Binary files /dev/null and b/docs/docassets/images/ContentMetadata.png differ diff --git a/lib/content-services/content-metadata/components/content-metadata-card/content-metadata-card.component.html b/lib/content-services/content-metadata/components/content-metadata-card/content-metadata-card.component.html new file mode 100644 index 0000000000..f0a23fc436 --- /dev/null +++ b/lib/content-services/content-metadata/components/content-metadata-card/content-metadata-card.component.html @@ -0,0 +1,28 @@ + + + + + + + diff --git a/lib/content-services/content-metadata/content-metadata-card.component.scss b/lib/content-services/content-metadata/components/content-metadata-card/content-metadata-card.component.scss similarity index 68% rename from lib/content-services/content-metadata/content-metadata-card.component.scss rename to lib/content-services/content-metadata/components/content-metadata-card/content-metadata-card.component.scss index 9426378da6..81a5a73fd4 100644 --- a/lib/content-services/content-metadata/content-metadata-card.component.scss +++ b/lib/content-services/content-metadata/components/content-metadata-card/content-metadata-card.component.scss @@ -3,9 +3,18 @@ $background: map-get($theme, background); $foreground: map-get($theme, foreground); - .adf-viewer-default-sidebar { + .adf-content-metadata-card { - &-card-footer.mat-card-footer { + .mat-card { + padding: 0; + + .mat-card-content { + margin-bottom: 0; + } + } + + &-footer.mat-card-footer { + margin: 0; padding: 8px 12px; border-top: 1px solid mat-color($foreground, text, 0.07); diff --git a/lib/content-services/content-metadata/components/content-metadata-card/content-metadata-card.component.spec.ts b/lib/content-services/content-metadata/components/content-metadata-card/content-metadata-card.component.spec.ts new file mode 100644 index 0000000000..04522dacc0 --- /dev/null +++ b/lib/content-services/content-metadata/components/content-metadata-card/content-metadata-card.component.spec.ts @@ -0,0 +1,172 @@ +/*! + * @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. + */ + +/*tslint:disable: ban*/ + +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { By } from '@angular/platform-browser'; +import { MinimalNodeEntryEntity } from 'alfresco-js-api'; +import { ContentMetadataCardComponent } from './content-metadata-card.component'; +import { ContentMetadataComponent } from '../content-metadata/content-metadata.component'; +import { MatExpansionModule, MatCardModule, MatButtonModule, MatIconModule } from '@angular/material'; +import { ContentMetadataService } from '../../services/content-metadata.service'; +import { BasicPropertiesService } from '../../services/basic-properties.service'; +import { PropertyDescriptorLoaderService } from '../../services/properties-loader.service'; +import { PropertyDescriptorsService } from '../../services/property-descriptors.service'; +import { AspectWhiteListService } from '../../services/aspect-whitelist.service'; +import { AlfrescoApiService } from '@alfresco/adf-core'; + +describe('ContentMetadataCardComponent', () => { + + let component: ContentMetadataCardComponent, + fixture: ComponentFixture, + node: MinimalNodeEntryEntity, + preset = 'custom-preset'; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [ + MatExpansionModule, + MatCardModule, + MatButtonModule, + MatIconModule + ], + declarations: [ + ContentMetadataCardComponent, + ContentMetadataComponent + ], + providers: [ + ContentMetadataService, + BasicPropertiesService, + PropertyDescriptorLoaderService, + PropertyDescriptorsService, + AspectWhiteListService, + AlfrescoApiService + ] + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(ContentMetadataCardComponent); + component = fixture.componentInstance; + node = { + aspectNames: [], + content: {}, + properties: {}, + createdByUser: {}, + modifiedByUser: {} + }; + + component.node = node; + component.preset = preset; + fixture.detectChanges(); + }); + + afterEach(() => { + fixture.destroy(); + TestBed.resetTestingModule(); + }); + + it('should have displayEmpty input param as false by default', () => { + expect(component.displayEmpty).toBe(false); + }); + + it('should pass through the node to the underlying component', () => { + const contentMetadataComponent = fixture.debugElement.query(By.directive(ContentMetadataComponent)).componentInstance; + + expect(contentMetadataComponent.node).toBe(node); + }); + + it('should pass through the preset to the underlying component', () => { + const contentMetadataComponent = fixture.debugElement.query(By.directive(ContentMetadataComponent)).componentInstance; + + expect(contentMetadataComponent.preset).toBe(preset); + }); + + it('should pass through the preset to the underlying component', () => { + component.displayEmpty = true; + fixture.detectChanges(); + const contentMetadataComponent = fixture.debugElement.query(By.directive(ContentMetadataComponent)).componentInstance; + + expect(contentMetadataComponent.displayEmpty).toBe(true); + }); + + it('should pass through the editable to the underlying component', () => { + component.editable = true; + fixture.detectChanges(); + const contentMetadataComponent = fixture.debugElement.query(By.directive(ContentMetadataComponent)).componentInstance; + + expect(contentMetadataComponent.editable).toBe(true); + }); + + it('should pass through the expanded to the underlying component', () => { + component.expanded = true; + fixture.detectChanges(); + const contentMetadataComponent = fixture.debugElement.query(By.directive(ContentMetadataComponent)).componentInstance; + + expect(contentMetadataComponent.expanded).toBe(true); + }); + + it('should not show anything if node is not present', () => { + component.node = undefined; + fixture.detectChanges(); + + const contentMetadataComponent = fixture.debugElement.query(By.directive(ContentMetadataComponent)); + + expect(contentMetadataComponent).toBeNull(); + }); + + it('should toggle editable by clicking on the button', () => { + component.editable = true; + fixture.detectChanges(); + + const button = fixture.debugElement.query(By.css('[data-automation-id="mata-data-card-toggle-edit"]')); + button.triggerEventHandler('click', {}); + fixture.detectChanges(); + + expect(component.editable).toBe(false); + }); + + it('should toggle expanded by clicking on the button', () => { + component.expanded = true; + fixture.detectChanges(); + + const button = fixture.debugElement.query(By.css('[data-automation-id="mata-data-card-toggle-expand"]')); + button.triggerEventHandler('click', {}); + fixture.detectChanges(); + + expect(component.expanded).toBe(false); + }); + + it('should have the proper text on button while collapsed', () => { + component.expanded = false; + fixture.detectChanges(); + + const buttonLabel = fixture.debugElement.query(By.css('[data-automation-id="mata-data-card-toggle-expand-label"]')); + + expect(buttonLabel.nativeElement.innerText.trim()).toBe('ADF_VIEWER.SIDEBAR.METADATA.MORE_INFORMATION'); + }); + + it('should have the proper text on button while collapsed', () => { + component.expanded = true; + fixture.detectChanges(); + + const buttonLabel = fixture.debugElement.query(By.css('[data-automation-id="mata-data-card-toggle-expand-label"]')); + + expect(buttonLabel.nativeElement.innerText.trim()).toBe('ADF_VIEWER.SIDEBAR.METADATA.LESS_INFORMATION'); + }); +}); diff --git a/lib/content-services/content-metadata/content-metadata-card.component.ts b/lib/content-services/content-metadata/components/content-metadata-card/content-metadata-card.component.ts similarity index 85% rename from lib/content-services/content-metadata/content-metadata-card.component.ts rename to lib/content-services/content-metadata/components/content-metadata-card/content-metadata-card.component.ts index 5021a57c01..fc1a6b5f23 100644 --- a/lib/content-services/content-metadata/content-metadata-card.component.ts +++ b/lib/content-services/content-metadata/components/content-metadata-card/content-metadata-card.component.ts @@ -18,19 +18,23 @@ import { Component, Input, ViewEncapsulation } from '@angular/core'; import { MinimalNodeEntryEntity } from 'alfresco-js-api'; -const PROPERTY_COUNTER_WHILE_COLLAPSED = 5; - @Component({ selector: 'adf-content-metadata-card', templateUrl: './content-metadata-card.component.html', styleUrls: ['./content-metadata-card.component.scss'], encapsulation: ViewEncapsulation.None, - host: { 'class': 'adf-viewer-default-sidebar' } + host: { 'class': 'adf-content-metadata-card' } }) export class ContentMetadataCardComponent { @Input() node: MinimalNodeEntryEntity; + @Input() + displayEmpty: boolean = false; + + @Input() + preset: string; + editable: boolean = false; expanded: boolean = false; @@ -41,8 +45,4 @@ export class ContentMetadataCardComponent { toggleExpanded(): void { this.expanded = !this.expanded; } - - get maxPropertiesToShow(): number { - return this.expanded ? Infinity : PROPERTY_COUNTER_WHILE_COLLAPSED; - } } diff --git a/lib/content-services/content-metadata/components/content-metadata/content-metadata.component.html b/lib/content-services/content-metadata/components/content-metadata/content-metadata.component.html new file mode 100644 index 0000000000..81cab09477 --- /dev/null +++ b/lib/content-services/content-metadata/components/content-metadata/content-metadata.component.html @@ -0,0 +1,43 @@ + \ No newline at end of file diff --git a/lib/content-services/content-metadata/components/content-metadata/content-metadata.component.scss b/lib/content-services/content-metadata/components/content-metadata/content-metadata.component.scss new file mode 100644 index 0000000000..ef15fefd80 --- /dev/null +++ b/lib/content-services/content-metadata/components/content-metadata/content-metadata.component.scss @@ -0,0 +1,9 @@ +@mixin adf-content-metadata-theme($theme) { + .adf { + &-metadata-properties { + .mat-expansion-panel:not([class*=mat-elevation-z]) { + box-shadow: none; + } + } + } +} diff --git a/lib/content-services/content-metadata/components/content-metadata/content-metadata.component.spec.ts b/lib/content-services/content-metadata/components/content-metadata/content-metadata.component.spec.ts new file mode 100644 index 0000000000..d7f6ea3014 --- /dev/null +++ b/lib/content-services/content-metadata/components/content-metadata/content-metadata.component.spec.ts @@ -0,0 +1,241 @@ +/*! + * @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. + */ + +/*tslint:disable: ban*/ + +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { SimpleChange } from '@angular/core'; +import { By } from '@angular/platform-browser'; +import { MinimalNodeEntryEntity } from 'alfresco-js-api'; +import { ContentMetadataComponent } from './content-metadata.component'; +import { MatExpansionModule, MatButtonModule, MatIconModule } from '@angular/material'; +import { ContentMetadataService } from '../../services/content-metadata.service'; +import { BasicPropertiesService } from '../../services/basic-properties.service'; +import { PropertyDescriptorLoaderService } from '../../services/properties-loader.service'; +import { PropertyDescriptorsService } from '../../services/property-descriptors.service'; +import { AspectWhiteListService } from '../../services/aspect-whitelist.service'; +import { AlfrescoApiService } from '@alfresco/adf-core'; +import { CardViewBaseItemModel, CardViewComponent, CardViewUpdateService, NodesApiService, LogService } from '@alfresco/adf-core'; +import { ErrorObservable } from 'rxjs/observable/ErrorObservable'; +import { Observable } from 'rxjs/Observable'; + +describe('ContentMetadataComponent', () => { + + let component: ContentMetadataComponent, + fixture: ComponentFixture, + node: MinimalNodeEntryEntity, + preset = 'custom-preset'; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [ + MatExpansionModule, + MatButtonModule, + MatIconModule + ], + declarations: [ + ContentMetadataComponent + ], + providers: [ + ContentMetadataService, + BasicPropertiesService, + PropertyDescriptorLoaderService, + PropertyDescriptorsService, + AspectWhiteListService, + AlfrescoApiService, + NodesApiService, + { provide: LogService, useValue: { error: jasmine.createSpy('error') } } + ] + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(ContentMetadataComponent); + component = fixture.componentInstance; + node = { + id: 'node-id', + aspectNames: [], + content: {}, + properties: {}, + createdByUser: {}, + modifiedByUser: {} + }; + + component.node = node; + component.preset = preset; + fixture.detectChanges(); + }); + + afterEach(() => { + fixture.destroy(); + TestBed.resetTestingModule(); + }); + + describe('Default input param values', () => { + + it('should have editable input param as false by default', () => { + expect(component.editable).toBe(false); + }); + + it('should have displayEmpty input param as false by default', () => { + expect(component.displayEmpty).toBe(false); + }); + + it('should have expanded input param as false by default', () => { + expect(component.expanded).toBe(false); + }); + }); + + describe('Saving', () => { + + it('should save the node on itemUpdate', () => { + const property = { key: 'property-key', value: 'original-value' }, + updateService: CardViewUpdateService = fixture.debugElement.injector.get(CardViewUpdateService), + nodesApiService: NodesApiService = TestBed.get(NodesApiService); + spyOn(nodesApiService, 'updateNode'); + + updateService.update(property, 'updated-value'); + + expect(nodesApiService.updateNode).toHaveBeenCalledWith('node-id', { + 'property-key': 'updated-value' + }); + }); + + it('should update the node on successful save', async(() => { + const property = { key: 'property-key', value: 'original-value' }, + updateService: CardViewUpdateService = fixture.debugElement.injector.get(CardViewUpdateService), + nodesApiService: NodesApiService = TestBed.get(NodesApiService), + expectedNode = Object.assign({}, node, { name: 'some-modified-value' }); + + spyOn(nodesApiService, 'updateNode').and.callFake(() => { + return Observable.of(expectedNode); + }); + + updateService.update(property, 'updated-value'); + + fixture.whenStable().then(() => { + expect(component.node).toBe(expectedNode); + }); + })); + + it('should throw error on unsuccessful save', () => { + const property = { key: 'property-key', value: 'original-value' }, + updateService: CardViewUpdateService = fixture.debugElement.injector.get(CardViewUpdateService), + nodesApiService: NodesApiService = TestBed.get(NodesApiService), + logService: LogService = TestBed.get(LogService); + + spyOn(nodesApiService, 'updateNode').and.callFake(() => { + return ErrorObservable.create(new Error('My bad')); + }); + + updateService.update(property, 'updated-value'); + + expect(logService.error).toHaveBeenCalledWith(new Error('My bad')); + }); + }); + + describe('Properties loading', () => { + + let expectedNode, + contentMetadataService: ContentMetadataService; + + beforeEach(() => { + expectedNode = Object.assign({}, node, { name: 'some-modified-value' }); + contentMetadataService = TestBed.get(ContentMetadataService); + fixture.detectChanges(); + }); + + it('should load the basic properties on node change', () => { + spyOn(contentMetadataService, 'getBasicProperties'); + + component.ngOnChanges({ node: new SimpleChange(node, expectedNode, false) }); + + expect(contentMetadataService.getBasicProperties).toHaveBeenCalledWith(expectedNode); + }); + + it('should pass through the loaded basic properties to the card view', async(() => { + const expectedProperties = []; + component.expanded = false; + fixture.detectChanges(); + spyOn(contentMetadataService, 'getBasicProperties').and.callFake(() => { + return Observable.of(expectedProperties); + }); + + component.ngOnChanges({ node: new SimpleChange(node, expectedNode, false) }); + + component.basicProperties$.subscribe(() => { + fixture.detectChanges(); + const basicPropertiesComponent = fixture.debugElement.query(By.directive(CardViewComponent)).componentInstance; + expect(basicPropertiesComponent.properties).toBe(expectedProperties); + }); + })); + + it('should pass through the displayEmpty to the card view of basic properties', async(() => { + component.displayEmpty = false; + fixture.detectChanges(); + spyOn(contentMetadataService, 'getBasicProperties').and.returnValue(Observable.of([])); + + component.ngOnChanges({ node: new SimpleChange(node, expectedNode, false) }); + + component.basicProperties$.subscribe(() => { + fixture.detectChanges(); + const basicPropertiesComponent = fixture.debugElement.query(By.directive(CardViewComponent)).componentInstance; + expect(basicPropertiesComponent.displayEmpty).toBe(false); + }); + })); + + it('should load the aspect properties on node change', () => { + spyOn(contentMetadataService, 'getAspectProperties'); + + component.ngOnChanges({ node: new SimpleChange(node, expectedNode, false) }); + + expect(contentMetadataService.getAspectProperties).toHaveBeenCalledWith(expectedNode, 'custom-preset'); + }); + + it('should pass through the loaded aspect properties to the card view', async(() => { + const expectedProperties = []; + component.expanded = true; + fixture.detectChanges(); + spyOn(contentMetadataService, 'getAspectProperties').and.callFake(() => { + return Observable.of([{ properties: expectedProperties }]); + }); + + component.ngOnChanges({ node: new SimpleChange(node, expectedNode, false) }); + + component.basicProperties$.subscribe(() => { + fixture.detectChanges(); + const firstAspectPropertiesComponent = fixture.debugElement.query(By.css('.adf-metadata-properties-aspect adf-card-view')).componentInstance; + expect(firstAspectPropertiesComponent.properties).toBe(expectedProperties); + }); + })); + + it('should pass through the displayEmpty to the card view of aspect properties', async(() => { + component.expanded = true; + component.displayEmpty = false; + fixture.detectChanges(); + spyOn(contentMetadataService, 'getAspectProperties').and.returnValue(Observable.of([{ properties: [] }])); + + component.ngOnChanges({ node: new SimpleChange(node, expectedNode, false) }); + + component.basicProperties$.subscribe(() => { + fixture.detectChanges(); + const basicPropertiesComponent = fixture.debugElement.query(By.css('.adf-metadata-properties-aspect adf-card-view')).componentInstance; + expect(basicPropertiesComponent.displayEmpty).toBe(false); + }); + })); + }); +}); diff --git a/lib/content-services/content-metadata/content-metadata.component.ts b/lib/content-services/content-metadata/components/content-metadata/content-metadata.component.ts similarity index 58% rename from lib/content-services/content-metadata/content-metadata.component.ts rename to lib/content-services/content-metadata/components/content-metadata/content-metadata.component.ts index a39afedc53..2ce38327c6 100644 --- a/lib/content-services/content-metadata/content-metadata.component.ts +++ b/lib/content-services/content-metadata/components/content-metadata/content-metadata.component.ts @@ -15,21 +15,20 @@ * limitations under the License. */ -import { ChangeDetectionStrategy, Component, Input, OnChanges, OnInit, ViewEncapsulation } from '@angular/core'; +import { Component, Input, OnChanges, OnInit, SimpleChanges, SimpleChange, ViewEncapsulation } from '@angular/core'; import { MinimalNodeEntryEntity } from 'alfresco-js-api'; import { Observable } from 'rxjs/Observable'; -import { CardViewItem, CardViewUpdateService, FileSizePipe, NodesApiService } from '@alfresco/adf-core'; -import { ContentMetadataService } from './content-metadata.service'; +import { CardViewItem, CardViewUpdateService, NodesApiService, LogService } from '@alfresco/adf-core'; +import { ContentMetadataService } from '../../services/content-metadata.service'; +import { CardViewAspect } from '../../interfaces/content-metadata.interfaces'; @Component({ selector: 'adf-content-metadata', templateUrl: './content-metadata.component.html', styleUrls: ['./content-metadata.component.scss'], host: { 'class': 'adf-content-metadata' }, - changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, - providers: [ CardViewUpdateService ], - viewProviders: [ ContentMetadataService, FileSizePipe ] + providers: [ CardViewUpdateService ] }) export class ContentMetadataComponent implements OnChanges, OnInit { @@ -40,43 +39,41 @@ export class ContentMetadataComponent implements OnChanges, OnInit { editable: boolean = false; @Input() - maxPropertiesToShow: number = Infinity; + displayEmpty: boolean = false; - properties: CardViewItem[] = []; + @Input() + expanded: boolean = false; + + @Input() + preset: string; + + basicProperties$: Observable; + aspects$: Observable; constructor(private contentMetadataService: ContentMetadataService, private cardViewUpdateService: CardViewUpdateService, - private nodesApi: NodesApiService) {} + private nodesApi: NodesApiService, + private logService: LogService) {} - ngOnInit(): void { + ngOnInit() { this.cardViewUpdateService.itemUpdated$ .switchMap(this.saveNode.bind(this)) .subscribe( node => this.node = node, - error => this.handleError(error) + error => this.logService.error(error) ); } - ngOnChanges(): void { - this.recalculateProperties(); + ngOnChanges(changes: SimpleChanges) { + const nodeChange: SimpleChange = changes['node']; + if (nodeChange) { + const node = nodeChange.currentValue; + this.basicProperties$ = this.contentMetadataService.getBasicProperties(node); + this.aspects$ = this.contentMetadataService.getAspectProperties(node, this.preset); + } } private saveNode({ changed: nodeBody }): Observable { return this.nodesApi.updateNode(this.node.id, nodeBody); } - - private handleError(error): void { - /*tslint:disable-next-line*/ - console.log(error); - } - - private recalculateProperties(): void { - let basicProperties = this.contentMetadataService.getBasicProperties(this.node); - - if (this.maxPropertiesToShow) { - basicProperties = basicProperties.slice(0, this.maxPropertiesToShow); - } - - this.properties = [...basicProperties]; - } } diff --git a/lib/content-services/content-metadata/content-metadata-card.component.html b/lib/content-services/content-metadata/content-metadata-card.component.html deleted file mode 100644 index 5dea07f211..0000000000 --- a/lib/content-services/content-metadata/content-metadata-card.component.html +++ /dev/null @@ -1,25 +0,0 @@ - - - - - -
- - -
- -
-
diff --git a/lib/content-services/content-metadata/content-metadata.component.html b/lib/content-services/content-metadata/content-metadata.component.html deleted file mode 100644 index b9eae6c36d..0000000000 --- a/lib/content-services/content-metadata/content-metadata.component.html +++ /dev/null @@ -1,3 +0,0 @@ - \ No newline at end of file diff --git a/lib/content-services/content-metadata/content-metadata.component.scss b/lib/content-services/content-metadata/content-metadata.component.scss deleted file mode 100644 index 25222c4a69..0000000000 --- a/lib/content-services/content-metadata/content-metadata.component.scss +++ /dev/null @@ -1,29 +0,0 @@ -@mixin adf-content-metadata-theme($theme) { - $primary: map-get($theme, primary); - $background: map-get($theme, background); - $foreground: map-get($theme, foreground); - - .adf { - &-metadata-properties { - - .adf-property { - display: block; - margin-bottom: 20px; - - .adf-property-label { - display: block; - padding: 0; - font-size: 12px; - color: mat-color($foreground, text, 0.4); - } - - .adf-property-value { - display: block; - padding: 0; - font-size: 14px; - color: mat-color($foreground, text, 0.87); - } - } - } - } -} diff --git a/lib/content-services/content-metadata/content-metadata.module.scss b/lib/content-services/content-metadata/content-metadata.module.scss new file mode 100644 index 0000000000..4c043f0d19 --- /dev/null +++ b/lib/content-services/content-metadata/content-metadata.module.scss @@ -0,0 +1,7 @@ +@import './components/content-metadata/content-metadata.component'; +@import './components/content-metadata-card/content-metadata-card.component'; + +@mixin adf-content-metadata-module-theme($theme) { + @include adf-content-metadata-theme($theme); + @include adf-content-metadata-card-theme($theme); +} diff --git a/lib/content-services/content-metadata/content-metadata.module.ts b/lib/content-services/content-metadata/content-metadata.module.ts index 5d4e76b863..55f84715bf 100644 --- a/lib/content-services/content-metadata/content-metadata.module.ts +++ b/lib/content-services/content-metadata/content-metadata.module.ts @@ -20,10 +20,14 @@ import { FlexLayoutModule } from '@angular/flex-layout'; import { NgModule } from '@angular/core'; import { TranslateModule } from '@ngx-translate/core'; import { MaterialModule } from '../material.module'; -import { CardViewModule } from '@alfresco/adf-core'; - -import { ContentMetadataComponent } from './content-metadata.component'; -import { ContentMetadataCardComponent } from './content-metadata-card.component'; +import { CardViewModule , FileSizePipe } from '@alfresco/adf-core'; +import { ContentMetadataComponent } from './components/content-metadata/content-metadata.component'; +import { ContentMetadataCardComponent } from './components/content-metadata-card/content-metadata-card.component'; +import { PropertyDescriptorsService } from './services/property-descriptors.service'; +import { PropertyDescriptorLoaderService } from './services/properties-loader.service'; +import { AspectWhiteListService } from './services/aspect-whitelist.service'; +import { BasicPropertiesService } from './services/basic-properties.service'; +import { ContentMetadataService } from './services/content-metadata.service'; @NgModule({ imports: [ @@ -40,6 +44,14 @@ import { ContentMetadataCardComponent } from './content-metadata-card.component' declarations: [ ContentMetadataComponent, ContentMetadataCardComponent + ], + providers: [ + ContentMetadataService, + PropertyDescriptorsService, + PropertyDescriptorLoaderService, + AspectWhiteListService, + BasicPropertiesService, + FileSizePipe ] }) export class ContentMetadataModule {} diff --git a/lib/content-services/content-metadata/interfaces/aspect-property.interface.ts b/lib/content-services/content-metadata/interfaces/aspect-property.interface.ts new file mode 100644 index 0000000000..c88f3d2ba5 --- /dev/null +++ b/lib/content-services/content-metadata/interfaces/aspect-property.interface.ts @@ -0,0 +1,26 @@ +/*! + * @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. + */ + +export interface AspectProperty { + name: string; + title: string; + description?: string; + dataType: string; + defaultValue?: any; + mandatory: boolean; + multiValued: boolean; +} diff --git a/lib/content-services/content-metadata/interfaces/aspect.interface.ts b/lib/content-services/content-metadata/interfaces/aspect.interface.ts new file mode 100644 index 0000000000..5c02e3c886 --- /dev/null +++ b/lib/content-services/content-metadata/interfaces/aspect.interface.ts @@ -0,0 +1,25 @@ +/*! + * @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 { AspectProperty } from "./aspect-property.interface"; + +export interface Aspect { + name: string; + title: string; + description: string; + properties: AspectProperty[] +} diff --git a/lib/content-services/content-metadata/interfaces/card-view-aspect.interface.ts b/lib/content-services/content-metadata/interfaces/card-view-aspect.interface.ts new file mode 100644 index 0000000000..af07068985 --- /dev/null +++ b/lib/content-services/content-metadata/interfaces/card-view-aspect.interface.ts @@ -0,0 +1,23 @@ +/*! + * @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 { CardViewItem} from '@alfresco/adf-core'; + +export interface CardViewAspect { + name: string; + properties: CardViewItem[] +} diff --git a/lib/content-services/content-metadata/interfaces/content-metadata.interfaces.ts b/lib/content-services/content-metadata/interfaces/content-metadata.interfaces.ts new file mode 100644 index 0000000000..d9ec4f5016 --- /dev/null +++ b/lib/content-services/content-metadata/interfaces/content-metadata.interfaces.ts @@ -0,0 +1,20 @@ +/*! + * @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. + */ + +export * from './aspect-property.interface'; +export * from './aspect.interface'; +export * from './card-view-aspect.interface'; \ No newline at end of file diff --git a/lib/content-services/content-metadata/public-api.ts b/lib/content-services/content-metadata/public-api.ts index dd66fdddb0..3067f3e186 100644 --- a/lib/content-services/content-metadata/public-api.ts +++ b/lib/content-services/content-metadata/public-api.ts @@ -15,5 +15,4 @@ * limitations under the License. */ -export * from './content-metadata.component'; -export * from './content-metadata.service'; +export * from './components/content-metadata-card/content-metadata-card.component'; diff --git a/lib/content-services/content-metadata/services/aspect-whitelist.service.ts b/lib/content-services/content-metadata/services/aspect-whitelist.service.ts new file mode 100644 index 0000000000..5c23391c9e --- /dev/null +++ b/lib/content-services/content-metadata/services/aspect-whitelist.service.ts @@ -0,0 +1,75 @@ +/*! + * @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 { Injectable } from '@angular/core'; +import { AppConfigService, LogService } from '@alfresco/adf-core'; + +@Injectable() +export class AspectWhiteListService { + + static readonly DEFAULT_PRESET = '*'; + static readonly DEFAULT_PRESET_NAME = 'default'; + + preset: object | string = AspectWhiteListService.DEFAULT_PRESET; + + constructor(private appConfigService: AppConfigService, + private logService: LogService) {} + + public choosePreset(presetName: string) { + try { + const preset = this.appConfigService.config['content-metadata'].presets[presetName]; + + if (preset) { + this.preset = preset; + } else if (presetName !== AspectWhiteListService.DEFAULT_PRESET_NAME) { + this.logService.error(`No content-metadata preset for: ${presetName}`); + } + } catch (e) { + this.preset = AspectWhiteListService.DEFAULT_PRESET; + } + } + + public isAspectAllowed(aspectName) { + if (this.isEveryAspectAllowed) { + return true; + } + + const aspectNames = Object.keys(this.preset); + return aspectNames.indexOf(aspectName) !== -1; + } + + public isPropertyAllowed(aspectName, propertyName) { + if (this.isEveryAspectAllowed || this.isEveryPropertyAllowedFor(aspectName)) { + return true; + } + + if (this.preset[aspectName]) { + return this.preset[aspectName].indexOf(propertyName) !== -1; + } + + return false; + } + + private get isEveryAspectAllowed(): boolean { + return typeof this.preset === 'string' && this.preset === AspectWhiteListService.DEFAULT_PRESET; + } + + private isEveryPropertyAllowedFor(aspectName): boolean { + const whitedListedProperties = this.preset[aspectName]; + return typeof whitedListedProperties === 'string' && whitedListedProperties === AspectWhiteListService.DEFAULT_PRESET; + } +} diff --git a/lib/content-services/content-metadata/content-metadata.service.ts b/lib/content-services/content-metadata/services/basic-properties.service.ts similarity index 98% rename from lib/content-services/content-metadata/content-metadata.service.ts rename to lib/content-services/content-metadata/services/basic-properties.service.ts index 06d05cd540..a57afd8739 100644 --- a/lib/content-services/content-metadata/content-metadata.service.ts +++ b/lib/content-services/content-metadata/services/basic-properties.service.ts @@ -20,7 +20,7 @@ import { MinimalNodeEntryEntity } from 'alfresco-js-api'; import { CardViewDateItemModel, CardViewTextItemModel, FileSizePipe } from '@alfresco/adf-core'; @Injectable() -export class ContentMetadataService { +export class BasicPropertiesService { constructor(private fileSizePipe: FileSizePipe) {} diff --git a/lib/content-services/content-metadata/services/content-metadata.service.spec.ts b/lib/content-services/content-metadata/services/content-metadata.service.spec.ts new file mode 100644 index 0000000000..a0ec319c14 --- /dev/null +++ b/lib/content-services/content-metadata/services/content-metadata.service.spec.ts @@ -0,0 +1,325 @@ +/*! + * @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 { async, TestBed } from '@angular/core/testing'; +import { ContentMetadataService } from './content-metadata.service'; +import { PropertyDescriptorsService } from './property-descriptors.service'; +import { BasicPropertiesService } from './basic-properties.service'; +import { AspectWhiteListService } from './aspect-whitelist.service'; +import { PropertyDescriptorLoaderService } from './properties-loader.service'; +import { AlfrescoApiService } from '@alfresco/adf-core'; +import { Observable } from 'rxjs/Observable'; +import { Aspect, AspectProperty } from '../interfaces/content-metadata.interfaces'; +import { MinimalNodeEntryEntity } from 'alfresco-js-api'; +import { + CardViewTextItemModel, + CardViewDateItemModel, + CardViewIntItemModel, + CardViewFloatItemModel, + LogService, + CardViewBoolItemModel, + CardViewDatetimeItemModel +} from '@alfresco/adf-core'; + +describe('ContentMetadataService', () => { + + let service: ContentMetadataService, + descriptorsService: PropertyDescriptorsService, + aspects: Aspect[], + aspect: Aspect, + aspectProperty: AspectProperty, + node: MinimalNodeEntryEntity; + + const dummyPreset = 'default'; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + providers: [ + ContentMetadataService, + BasicPropertiesService, + AspectWhiteListService, + PropertyDescriptorLoaderService, + AlfrescoApiService, + { provide: LogService, useValue: { error: () => {} }}, + PropertyDescriptorsService + ] + }).compileComponents(); + })); + + beforeEach(() => { + service = TestBed.get(ContentMetadataService); + descriptorsService = TestBed.get(PropertyDescriptorsService); + + node = { properties: {} }; + aspectProperty = { + name: 'FAS:PLAGUE', + title: 'The Faro Plague', + dataType: '', + defaultValue: '', + mandatory: false, + multiValued: false + }; + aspect = { + name: 'FAS:FAS', + title: 'Faro Automated Solutions', + description: 'Faro Automated Solutions is an old Earth corporation that manufactured robots in the mid-21st century.', + properties: [aspectProperty] + }; + aspects = []; + spyOn(descriptorsService, 'getAspects').and.returnValue(Observable.of(aspects)); + }); + + afterEach(() => { + TestBed.resetTestingModule(); + }); + + describe('General transformation', () => { + + it('should translate more properties in an aspect properly', () => { + aspect.properties = [{ + name: 'FAS:PLAGUE', + title: 'title', + dataType: 'd:text', + defaultValue: 'defaultValue', + mandatory: false, + multiValued: false + }, + { + name: 'FAS:ALOY', + title: 'title', + dataType: 'd:text', + defaultValue: 'defaultValue', + mandatory: false, + multiValued: false + }]; + aspects.push(aspect); + + node.properties = { 'FAS:PLAGUE': 'The Chariot Line' }; + + service.getAspectProperties(node, dummyPreset).subscribe((cardViewAspect) => { + expect(cardViewAspect[0].properties.length).toBe(2); + expect(cardViewAspect[0].properties[0] instanceof CardViewTextItemModel).toBeTruthy('First property should be instance of CardViewTextItemModel'); + expect(cardViewAspect[0].properties[1] instanceof CardViewTextItemModel).toBeTruthy('Second property should be instance of CardViewTextItemModel'); + }); + }); + + it('should translate every property in every aspect properly', () => { + aspects.push( + Object.assign({}, aspect, { + properties: [{ + name: 'FAS:PLAGUE', + title: 'title', + dataType: 'd:text', + defaultValue: 'defaultvalue', + mandatory: false, + multiValued: false + }] + }), + Object.assign({}, aspect, { + properties: [{ + name: 'FAS:ALOY', + title: 'title', + dataType: 'd:text', + defaultValue: 'defaultvalue', + mandatory: false, + multiValued: false + }] + }) + ); + + node.properties = { 'FAS:PLAGUE': 'The Chariot Line' }; + + service.getAspectProperties(node, dummyPreset).subscribe((cardViewAspect) => { + expect(cardViewAspect.length).toBe(2); + expect(cardViewAspect[0].properties[0] instanceof CardViewTextItemModel).toBeTruthy('First aspect\'s property should be instance of CardViewTextItemModel'); + expect(cardViewAspect[1].properties[0] instanceof CardViewTextItemModel).toBeTruthy('Second aspect\'s property should be instance of CardViewTextItemModel'); + }); + }); + + it('should log an error if unrecognised type is found', () => { + const logService = TestBed.get(LogService); + spyOn(logService, 'error').and.stub(); + + aspectProperty.name = 'FAS:PLAGUE'; + aspectProperty.title = 'The Faro Plague'; + aspectProperty.dataType = 'daemonic:scorcher'; + aspectProperty.defaultValue = 'Daemonic beast'; + aspects.push(aspect); + + service.getAspectProperties(node, dummyPreset).subscribe((cardViewAspect) => { + expect(logService.error).toHaveBeenCalledWith('Unknown type for mapping: daemonic:scorcher'); + }); + }); + + it('should fall back to singleline property type if unrecognised type is found', () => { + aspectProperty.name = 'FAS:PLAGUE'; + aspectProperty.title = 'The Faro Plague'; + aspectProperty.dataType = 'daemonic:scorcher'; + aspectProperty.defaultValue = 'Daemonic beast'; + aspects.push(aspect); + + service.getAspectProperties(node, dummyPreset).subscribe((cardViewAspect) => { + const property: CardViewTextItemModel = cardViewAspect[0].properties[0]; + expect(property instanceof CardViewTextItemModel).toBeTruthy('Property should be instance of CardViewTextItemModel'); + }); + }); + }); + + describe('Different types\'s attributes', () => { + + ContentMetadataService.RECOGNISED_ECM_TYPES.forEach((dataType) => { + it(`should translate properly the basic attributes of a property for ${dataType}`, () => { + aspectProperty.name = 'prefix:name'; + aspectProperty.title = 'title'; + aspectProperty.defaultValue = 'default value'; + aspectProperty.dataType = dataType; + aspects.push(aspect); + + node.properties = { 'prefix:name': null }; + + service.getAspectProperties(node, dummyPreset).subscribe((cardViewAspect) => { + const property = cardViewAspect[0].properties[0]; + expect(property.label).toBe(aspectProperty.title); + expect(property.key).toBe('properties.prefix:name'); + expect(property.default).toBe(aspectProperty.defaultValue); + expect(property.editable).toBeTruthy('Property should be editable'); + }); + }); + }); + + it('should translate properly the multiline and value attributes for d:text', () => { + aspectProperty.dataType = 'd:text'; + aspects.push(aspect); + + node.properties = { 'FAS:PLAGUE': 'The Chariot Line' }; + + service.getAspectProperties(node, dummyPreset).subscribe((cardViewAspect) => { + const property: CardViewTextItemModel = cardViewAspect[0].properties[0]; + expect(property instanceof CardViewTextItemModel).toBeTruthy('Property should be instance of CardViewTextItemModel'); + expect(property.value).toBe('The Chariot Line'); + expect(property.multiline).toBeFalsy('Property should be singleline'); + }); + }); + + it('should translate properly the multiline and value attributes for d:mltext', () => { + aspectProperty.dataType = 'd:mltext'; + aspects.push(aspect); + + node.properties = { 'FAS:PLAGUE': 'The Chariot Line' }; + + service.getAspectProperties(node, dummyPreset).subscribe((cardViewAspect) => { + const property: CardViewTextItemModel = cardViewAspect[0].properties[0]; + expect(property instanceof CardViewTextItemModel).toBeTruthy('Property should be instance of CardViewTextItemModel'); + expect(property.value).toBe('The Chariot Line'); + expect(property.multiline).toBeTruthy('Property should be multiline'); + }); + }); + + it('should translate properly the value attribute for d:date', () => { + const expectedValue = new Date().toISOString(); + aspectProperty.dataType = 'd:date'; + aspects.push(aspect); + + node.properties = { 'FAS:PLAGUE': expectedValue }; + + service.getAspectProperties(node, dummyPreset).subscribe((cardViewAspect) => { + const property: CardViewDateItemModel = cardViewAspect[0].properties[0]; + expect(property instanceof CardViewDateItemModel).toBeTruthy('Property should be instance of CardViewDateItemModel'); + expect(property.value).toBe(expectedValue); + }); + }); + + it('should translate properly the value attribute for d:date', () => { + const expectedValue = new Date().toISOString(); + aspectProperty.dataType = 'd:datetime'; + aspects.push(aspect); + + node.properties = { 'FAS:PLAGUE': expectedValue }; + + service.getAspectProperties(node, dummyPreset).subscribe((cardViewAspect) => { + const property: CardViewDatetimeItemModel = cardViewAspect[0].properties[0]; + expect(property instanceof CardViewDatetimeItemModel).toBeTruthy('Property should be instance of CardViewDatetimeItemModel'); + expect(property.value).toBe(expectedValue); + }); + }); + + it('should translate properly the value attribute for d:int', () => { + aspectProperty.dataType = 'd:int'; + aspects.push(aspect); + + node.properties = { 'FAS:PLAGUE': '1024' }; + + service.getAspectProperties(node, dummyPreset).subscribe((cardViewAspect) => { + const property: CardViewIntItemModel = cardViewAspect[0].properties[0]; + expect(property instanceof CardViewIntItemModel).toBeTruthy('Property should be instance of CardViewIntItemModel'); + expect(property.value).toBe(1024); + }); + }); + + it('should translate properly the value attribute for d:long', () => { + aspectProperty.dataType = 'd:long'; + aspects.push(aspect); + + node.properties = { 'FAS:PLAGUE': '1024' }; + + service.getAspectProperties(node, dummyPreset).subscribe((cardViewAspect) => { + const property: CardViewIntItemModel = cardViewAspect[0].properties[0]; + expect(property instanceof CardViewIntItemModel).toBeTruthy('Property should be instance of CardViewIntItemModel'); + expect(property.value).toBe(1024); + }); + }); + + it('should translate properly the value attribute for d:float', () => { + aspectProperty.dataType = 'd:float'; + aspects.push(aspect); + + node.properties = { 'FAS:PLAGUE': '1024.24' }; + + service.getAspectProperties(node, dummyPreset).subscribe((cardViewAspect) => { + const property: CardViewFloatItemModel = cardViewAspect[0].properties[0]; + expect(property instanceof CardViewFloatItemModel).toBeTruthy('Property should be instance of CardViewFloatItemModel'); + expect(property.value).toBe(1024.24); + }); + }); + + it('should translate properly the value attribute for d:double', () => { + aspectProperty.dataType = 'd:double'; + aspects.push(aspect); + + node.properties = { 'FAS:PLAGUE': '1024.24' }; + + service.getAspectProperties(node, dummyPreset).subscribe((cardViewAspect) => { + const property: CardViewFloatItemModel = cardViewAspect[0].properties[0]; + expect(property instanceof CardViewFloatItemModel).toBeTruthy('Property should be instance of CardViewFloatItemModel'); + expect(property.value).toBe(1024.24); + }); + }); + + it('should translate properly the value attribute for d:boolean', () => { + aspectProperty.dataType = 'd:boolean'; + aspects.push(aspect); + + node.properties = { 'FAS:PLAGUE': true }; + + service.getAspectProperties(node, dummyPreset).subscribe((cardViewAspect) => { + const property: CardViewBoolItemModel = cardViewAspect[0].properties[0]; + expect(property instanceof CardViewBoolItemModel).toBeTruthy('Property should be instance of CardViewBoolItemModel'); + expect(property.value).toBe(true); + }); + }); + }); +}); diff --git a/lib/content-services/content-metadata/services/content-metadata.service.ts b/lib/content-services/content-metadata/services/content-metadata.service.ts new file mode 100644 index 0000000000..924aa9ced7 --- /dev/null +++ b/lib/content-services/content-metadata/services/content-metadata.service.ts @@ -0,0 +1,139 @@ +/*! + * @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 { Injectable } from '@angular/core'; +import { MinimalNodeEntryEntity } from 'alfresco-js-api'; +import { PropertyDescriptorsService } from './property-descriptors.service'; +import { BasicPropertiesService } from './basic-properties.service'; +import { + CardViewItemProperties, + CardViewItem, + CardViewTextItemModel, + CardViewBoolItemModel, + CardViewDateItemModel, + CardViewDatetimeItemModel, + CardViewIntItemModel, + CardViewFloatItemModel, + LogService +} from '@alfresco/adf-core'; +import { Observable } from 'rxjs/Observable'; +import { AspectProperty, CardViewAspect, Aspect } from '../interfaces/content-metadata.interfaces'; + +const D_TEXT = 'd:text'; +const D_MLTEXT = 'd:mltext'; +const D_DATE = 'd:date'; +const D_DATETIME = 'd:datetime'; +const D_INT = 'd:int'; +const D_LONG = 'd:long'; +const D_FLOAT = 'd:float'; +const D_DOUBLE = 'd:double'; +const D_BOOLEAN = 'd:boolean'; + +@Injectable() +export class ContentMetadataService { + + static readonly RECOGNISED_ECM_TYPES = [ D_TEXT, D_MLTEXT, D_DATE, D_DATETIME, D_INT, D_LONG , D_FLOAT, D_DOUBLE, D_BOOLEAN ]; + + constructor(private basicPropertiesService: BasicPropertiesService, + private propertyDescriptorsService: PropertyDescriptorsService, + private logService: LogService) {} + + getBasicProperties(node: MinimalNodeEntryEntity): Observable { + return Observable.of(this.basicPropertiesService.getBasicProperties(node)); + } + + getAspectProperties(node: MinimalNodeEntryEntity, preset: string): Observable { + return this.propertyDescriptorsService.getAspects(node, preset) + .map(aspects => this.translateAspects(aspects, node.properties)); + } + + private translateAspects(aspects: Aspect[], nodeProperties): CardViewAspect[] { + return aspects.map(aspect => { + const translatedAspect: any = Object.assign({}, aspect); + translatedAspect.properties = this.translateProperties(aspect.properties, nodeProperties); + return translatedAspect; + }); + } + + private translateProperties(aspectProperties: AspectProperty[], nodeProperties: any): CardViewItem[] { + return aspectProperties.map(aspectProperty => { + return this.translateProperty(aspectProperty, nodeProperties[aspectProperty.name]); + }); + } + + private translateProperty(aspectProperty: AspectProperty, nodeProperty: any): CardViewItem { + this.checkECMTypeValidity(aspectProperty.dataType); + + let propertyDefinition: CardViewItemProperties = { + label: aspectProperty.title, + value: nodeProperty, + key: this.getAspectPropertyKey(aspectProperty.name), + default: aspectProperty.defaultValue, + editable: true + }; + let property; + + switch (aspectProperty.dataType) { + + case D_MLTEXT: + property = new CardViewTextItemModel(Object.assign(propertyDefinition, { + multiline: true + })); + break; + + case D_INT: + case D_LONG: + property = new CardViewIntItemModel(propertyDefinition); + break; + + case D_FLOAT: + case D_DOUBLE: + property = new CardViewFloatItemModel(propertyDefinition); + break; + + case D_DATE: + property = new CardViewDateItemModel(propertyDefinition); + break; + + case D_DATETIME: + property = new CardViewDatetimeItemModel(propertyDefinition); + break; + + case D_BOOLEAN: + property = new CardViewBoolItemModel(propertyDefinition); + break; + + case D_TEXT: + default: + property = new CardViewTextItemModel(Object.assign(propertyDefinition, { + multiline: false + })); + } + + return property; + } + + private checkECMTypeValidity(ecmPropertyType) { + if (ContentMetadataService.RECOGNISED_ECM_TYPES.indexOf(ecmPropertyType) === -1) { + this.logService.error(`Unknown type for mapping: ${ecmPropertyType}`); + } + } + + private getAspectPropertyKey(propertyName) { + return `properties.${propertyName}`; + } +} diff --git a/lib/content-services/content-metadata/services/properties-loader.service.spec.ts b/lib/content-services/content-metadata/services/properties-loader.service.spec.ts new file mode 100644 index 0000000000..5211866aa3 --- /dev/null +++ b/lib/content-services/content-metadata/services/properties-loader.service.spec.ts @@ -0,0 +1,95 @@ +/*! + * @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 { async, TestBed } from '@angular/core/testing'; +import { PropertyDescriptorLoaderService } from './properties-loader.service'; +import { AlfrescoApiService } from '@alfresco/adf-core'; +import { Observable } from 'rxjs/Observable'; +import { ClassesApi } from 'alfresco-js-api'; + +describe('PropertyDescriptorLoaderService', () => { + + let aspectProperties: PropertyDescriptorLoaderService, + classesApi: ClassesApi; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + providers: [ + PropertyDescriptorLoaderService, + AlfrescoApiService + ] + }).compileComponents(); + })); + + beforeEach(() => { + aspectProperties = TestBed.get(PropertyDescriptorLoaderService); + const alfrescoApiService = TestBed.get(AlfrescoApiService); + classesApi = alfrescoApiService.classesApi; + }); + + afterEach(() => { + TestBed.resetTestingModule(); + }); + + it('should load the aspects passed by paramter', () => { + spyOn(classesApi, 'getClass'); + + aspectProperties.load(['exif:exif', 'cm:content', 'custom:custom']) + .subscribe(() => {}); + + expect(classesApi.getClass).toHaveBeenCalledTimes(3); + expect(classesApi.getClass).toHaveBeenCalledWith('exif_exif'); + expect(classesApi.getClass).toHaveBeenCalledWith('cm_content'); + expect(classesApi.getClass).toHaveBeenCalledWith('custom_custom'); + }); + + it('should merge the forked values', (done) => { + + const exifResponse = { + name: 'exif:exif', + id: 1, + properties: { + 'exif:1': { id: 'exif:1:id', name: 'exif:1' }, + 'exif:2': { id: 'exif:2:id', name: 'exif:2' } + } + }; + + const contentResponse = { + name: 'cm:content', + id: 2, + properties: { + 'cm:content': { id: 'cm:content:id', name: 'cm:content' } + } + }; + + const apiResponses = [ exifResponse, contentResponse ]; + let counter = 0; + + spyOn(classesApi, 'getClass').and.callFake(() => { + return Observable.of(apiResponses[counter++]); + }); + + aspectProperties.load(['exif:exif', 'cm:content']) + .subscribe({ + next: (data) => { + expect(data[0]).toBe(exifResponse); + expect(data[1]).toBe(contentResponse); + }, + complete: done + }); + }); +}); diff --git a/lib/content-services/content-metadata/services/properties-loader.service.ts b/lib/content-services/content-metadata/services/properties-loader.service.ts new file mode 100644 index 0000000000..406a1b63a0 --- /dev/null +++ b/lib/content-services/content-metadata/services/properties-loader.service.ts @@ -0,0 +1,36 @@ +/*! + * @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 { Injectable } from '@angular/core'; +import { AlfrescoApiService } from '@alfresco/adf-core'; +import { forkJoin } from 'rxjs/observable/forkJoin'; +import { Observable } from 'rxjs/Observable'; +import { defer } from 'rxjs/observable/defer'; + +@Injectable() +export class PropertyDescriptorLoaderService { + + constructor(private alfrescoApiService: AlfrescoApiService) {} + + load(aspects: string[]): Observable { + const aspectFetchStreams = aspects + .map(aspectName => aspectName.replace(':', '_')) + .map(aspectName => defer( () => this.alfrescoApiService.classesApi.getClass(aspectName)) ); + + return forkJoin(aspectFetchStreams); + } +} diff --git a/lib/content-services/content-metadata/services/property-descriptors.service.spec.ts b/lib/content-services/content-metadata/services/property-descriptors.service.spec.ts new file mode 100644 index 0000000000..9eb76f98f2 --- /dev/null +++ b/lib/content-services/content-metadata/services/property-descriptors.service.spec.ts @@ -0,0 +1,209 @@ +/*! + * @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 { async, TestBed } from '@angular/core/testing'; +import { PropertyDescriptorsService } from './property-descriptors.service'; +import { PropertyDescriptorLoaderService } from './properties-loader.service'; +import { MinimalNodeEntryEntity } from 'alfresco-js-api'; +import { AlfrescoApiService } from '@alfresco/adf-core'; +import { AspectWhiteListService } from './aspect-whitelist.service'; +import { AppConfigService, LogService } from '@alfresco/adf-core'; +import { Observable } from 'rxjs/Observable'; +import { AspectProperty } from '../interfaces/content-metadata.interfaces'; + +describe('PropertyDescriptorsService', () => { + + let contentMetadataService: PropertyDescriptorsService, + aspectProperties: PropertyDescriptorLoaderService, + appConfigService: AppConfigService, + logService: LogService, + node: MinimalNodeEntryEntity, + testPresets: any; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + providers: [ + PropertyDescriptorsService, + PropertyDescriptorLoaderService, + AppConfigService, + AspectWhiteListService, + { provide: LogService, useValue: { error: () => {} }}, + AlfrescoApiService + ] + }).compileComponents(); + })); + + beforeEach(() => { + contentMetadataService = TestBed.get(PropertyDescriptorsService); + aspectProperties = TestBed.get(PropertyDescriptorLoaderService); + appConfigService = TestBed.get(AppConfigService); + logService = TestBed.get(LogService); + }); + + afterEach(() => { + TestBed.resetTestingModule(); + }); + + describe('getAspects', () => { + + beforeEach(() => { + node = { aspectNames: [ 'exif:exif', 'cm:content', 'custom:custom' ] }; + + testPresets = {}; + appConfigService.config['content-metadata'] = { + presets: testPresets + }; + }); + + it('should call the aspect properties loading for the default aspects related to the given node and defined in the app config', () => { + spyOn(aspectProperties, 'load').and.callFake(x => Observable.of({})); + testPresets.default = { 'exif:exif': [], 'custom:custom': [], 'banana:banana': [] }; + + contentMetadataService.getAspects(node); + + expect(aspectProperties.load).toHaveBeenCalledWith(['exif:exif', 'custom:custom']); + }); + + it('should call the aspect properties loading for the defined aspects related to the given node and defined in the app config', () => { + spyOn(aspectProperties, 'load').and.callFake(x => Observable.of({})); + testPresets.pink = { 'cm:content': [], 'custom:custom': [] }; + + contentMetadataService.getAspects(node, 'pink'); + + expect(aspectProperties.load).toHaveBeenCalledWith(['cm:content', 'custom:custom']); + }); + + it('should call the aspect properties loading for all the node aspectNames if the "*" widecard is used for the preset', () => { + spyOn(aspectProperties, 'load').and.callFake(x => Observable.of({})); + testPresets.default = '*'; + + contentMetadataService.getAspects(node); + + expect(aspectProperties.load).toHaveBeenCalledWith(['exif:exif', 'cm:content', 'custom:custom']); + }); + + it('should call the aspect properties loading for all the node aspectNames if there is no preset data defined in the app config', () => { + spyOn(aspectProperties, 'load').and.callFake(x => Observable.of({})); + spyOn(logService, 'error').and.stub(); + appConfigService.config['content-metadata'] = undefined; + + contentMetadataService.getAspects(node); + + expect(logService.error).not.toHaveBeenCalled(); + expect(aspectProperties.load).toHaveBeenCalledWith(['exif:exif', 'cm:content', 'custom:custom']); + }); + + it('should show meaningful error when invalid preset are given', () => { + spyOn(aspectProperties, 'load').and.callFake(x => Observable.of({})); + spyOn(logService, 'error').and.stub(); + testPresets.pink = { 'cm:content': {}, 'custom:custom': {} }; + + contentMetadataService.getAspects(node, 'blue'); + + expect(logService.error).toHaveBeenCalledWith('No content-metadata preset for: blue'); + }); + + it('should filter out properties which are not defined in the particular aspect', () => { + spyOn(aspectProperties, 'load').and.callFake(() => { + return Observable.of([ + { + name: 'exif:exif', + properties: [ + { name: 'exif:1' }, + { name: 'exif:2' } + ] + } + ]); + }); + + testPresets.default = { 'exif:exif': ['exif:2'] }; + + contentMetadataService.getAspects(node).subscribe({ + next: (aspects) => { + expect(aspects[0].name).toBe('exif:exif'); + expect(aspects[0].properties).toContain( { name: 'exif:2' }); + expect(aspects[0].properties).not.toContain( { name: 'exif:1' }); + } + }); + }); + + it('should accept "*" wildcard for aspect properties', () => { + spyOn(aspectProperties, 'load').and.callFake(() => { + return Observable.of([ + { + name: 'exif:exif', + properties: [ + { name: 'exif:1' }, + { name: 'exif:2' } + ] + }, + { + name: 'custom:custom', + properties: [ + { name: 'custom:1' }, + { name: 'custom:2' } + ] + } + ]); + }); + + testPresets.default = { + 'exif:exif': '*', + 'custom:custom': ['custom:1'] + }; + + contentMetadataService.getAspects(node).subscribe({ + next: (aspects) => { + expect(aspects.length).toBe(2); + expect(aspects[0].name).toBe('exif:exif'); + expect(aspects[0].properties).toContain( { name: 'exif:1' }); + expect(aspects[0].properties).toContain( { name: 'exif:2' }); + + expect(aspects[1].name).toBe('custom:custom'); + expect(aspects[1].properties).toContain( { name: 'custom:1' }); + expect(aspects[1].properties).not.toContain( { name: 'custom:2' }); + } + }); + }); + + it('should filter out aspects which are not present in app config preset', () => { + spyOn(aspectProperties, 'load').and.callFake(() => { + return Observable.of([ + { + name: 'exif:exif', + properties: [ + { name: 'exif:1' } + ] + } + ]); + }); + + testPresets.default = { + 'exif:exif': ['exif:1'], + 'banana:banana': ['banana:1'] + }; + + contentMetadataService.getAspects(node).subscribe({ + next: (aspects) => { + expect(aspects.length).toBe(1); + expect(aspects[0].name).toBe('exif:exif'); + expect(aspects[0].properties).toContain( { name: 'exif:1' }); + } + }); + }); + }); +}); diff --git a/lib/content-services/content-metadata/services/property-descriptors.service.ts b/lib/content-services/content-metadata/services/property-descriptors.service.ts new file mode 100644 index 0000000000..1a24659a78 --- /dev/null +++ b/lib/content-services/content-metadata/services/property-descriptors.service.ts @@ -0,0 +1,60 @@ +/*! + * @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 { Injectable } from '@angular/core'; +import { MinimalNodeEntryEntity } from 'alfresco-js-api'; +import { PropertyDescriptorLoaderService } from './properties-loader.service'; +import { AspectWhiteListService } from './aspect-whitelist.service'; +import { Observable } from 'rxjs/Observable'; +import { Aspect, AspectProperty } from '../interfaces/content-metadata.interfaces'; + +@Injectable() +export class PropertyDescriptorsService { + + constructor(private aspectWhiteListService: AspectWhiteListService, + private aspectPropertiesService: PropertyDescriptorLoaderService) {} + + getAspects(node: MinimalNodeEntryEntity, presetName: string = 'default'): Observable { + this.aspectWhiteListService.choosePreset(presetName); + + return this.loadAspectDescriptors(node.aspectNames) + .map(this.filterPropertiesByWhitelist.bind(this)); + } + + private loadAspectDescriptors(aspectsAssignedToNode: string[]): Observable { + const aspectsToLoad = aspectsAssignedToNode + .filter(nodeAspectName => this.aspectWhiteListService.isAspectAllowed(nodeAspectName)); + + return this.aspectPropertiesService.load(aspectsToLoad); + } + + private filterPropertiesByWhitelist(aspectDescriptors): Aspect[] { + return aspectDescriptors.map((aspectDescriptor) => { + return Object.assign({}, aspectDescriptor, { + properties: this.getFilteredPropertiesArray(aspectDescriptor) + }); + }); + } + + private getFilteredPropertiesArray(aspectDescriptor): AspectProperty[] { + const aspectName = aspectDescriptor.name; + + return Object.keys(aspectDescriptor.properties) + .map(propertyName => aspectDescriptor.properties[propertyName]) + .filter(property => this.aspectWhiteListService.isPropertyAllowed(aspectName, property.name)); + } +} diff --git a/lib/content-services/i18n/en.json b/lib/content-services/i18n/en.json index 0c242715b1..fabe081ec0 100644 --- a/lib/content-services/i18n/en.json +++ b/lib/content-services/i18n/en.json @@ -188,6 +188,7 @@ }, "METADATA": { "BASIC": { + "HEADER": "Properties", "NAME": "Name", "TITLE": "Title", "DESCRIPTION": "Description", diff --git a/lib/content-services/material.module.ts b/lib/content-services/material.module.ts index 9c79ff8008..4eb6c5c1f4 100644 --- a/lib/content-services/material.module.ts +++ b/lib/content-services/material.module.ts @@ -29,6 +29,7 @@ import { MatProgressBarModule, MatProgressSpinnerModule, MatRippleModule, + MatExpansionModule, MatSelectModule } from '@angular/material'; @@ -46,6 +47,7 @@ export function modules() { MatRippleModule, MatMenuModule, MatOptionModule, + MatExpansionModule, MatSelectModule ]; } diff --git a/lib/content-services/styles/_index.scss b/lib/content-services/styles/_index.scss index 198d7257c6..550f533880 100644 --- a/lib/content-services/styles/_index.scss +++ b/lib/content-services/styles/_index.scss @@ -12,9 +12,8 @@ @import '../dialogs/folder.dialog'; -@import '../content-metadata/content-metadata.component'; -@import '../content-metadata/content-metadata-card.component'; @import '../content-node-selector/content-node-selector.component'; +@import '../content-metadata/content-metadata.module'; @mixin adf-content-services-theme($theme) { @include adf-breadcrumb-theme($theme); @@ -27,7 +26,6 @@ @include adf-search-control-theme($theme); @include adf-search-autocomplete-theme($theme); @include adf-dialog-theme($theme); - @include adf-content-metadata-theme($theme); - @include adf-content-metadata-card-theme($theme); @include adf-content-node-selector-dialog-theme($theme) ; + @include adf-content-metadata-module-theme($theme); } diff --git a/lib/core/card-view/card-view-dateitem.component.html b/lib/core/card-view/card-view-dateitem.component.html deleted file mode 100644 index 452153ba32..0000000000 --- a/lib/core/card-view/card-view-dateitem.component.html +++ /dev/null @@ -1,30 +0,0 @@ -
{{ property.label | translate }}
-
- - - {{ property.displayValue }} - - - - - - {{ property.displayValue }} - - - - - - - - {{ property.default | translate }} - -
diff --git a/lib/core/card-view/card-view-dateitem.component.scss b/lib/core/card-view/card-view-dateitem.component.scss deleted file mode 100644 index 59b2c249bf..0000000000 --- a/lib/core/card-view/card-view-dateitem.component.scss +++ /dev/null @@ -1,36 +0,0 @@ -@mixin adf-card-view-dateitem-theme($theme) { - - .adf { - &-invisible-date-input { - height: 24px; - width: 0; - overflow: hidden; - opacity: 0; - border: none; - margin: 0; - padding: 0; - display: none; - } - - &-dateitem-editable { - cursor: pointer; - - button.mat-icon-button { - line-height: 20px; - height: 20px; - width: 20px; - } - - mat-icon { - width: 16px; - height: 16px; - opacity: 0.5; - margin-left: 4px; - } - - &:hover mat-icon { - opacity: 1; - } - } - } -} diff --git a/lib/core/card-view/card-view-textitem.component.html b/lib/core/card-view/card-view-textitem.component.html deleted file mode 100644 index fe22f4099d..0000000000 --- a/lib/core/card-view/card-view-textitem.component.html +++ /dev/null @@ -1,51 +0,0 @@ -
{{ property.label | translate }}
-
- - - {{ property.displayValue }} - - - - {{ property.displayValue }} - - - - -
- - {{ property.displayValue }} - - create -
-
- - - - - done - clear -
-
- - {{ property.default | translate }} - -
diff --git a/lib/core/card-view/card-view-textitem.component.spec.ts b/lib/core/card-view/card-view-textitem.component.spec.ts deleted file mode 100644 index ff73e6fdbc..0000000000 --- a/lib/core/card-view/card-view-textitem.component.spec.ts +++ /dev/null @@ -1,233 +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 { HttpClientModule } from '@angular/common/http'; -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { FormsModule } from '@angular/forms'; -import { MatDatepickerModule, MatIconModule, MatInputModule, MatNativeDateModule } from '@angular/material'; -import { By } from '@angular/platform-browser'; -import { NoopAnimationsModule } from '@angular/platform-browser/animations'; -import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; -import { CardViewTextItemModel } from '../models/card-view-textitem.model'; -import { AppConfigService } from '../app-config/app-config.service'; -import { CardViewUpdateService } from '../services/card-view-update.service'; -import { LogService } from '../services/log.service'; -import { TranslateLoaderService } from '../services/translate-loader.service'; - -import { CardViewTextItemComponent } from './card-view-textitem.component'; - -describe('CardViewTextItemComponent', () => { - - let fixture: ComponentFixture; - let component: CardViewTextItemComponent; - - beforeEach(async(() => { - TestBed.configureTestingModule({ - imports: [ - HttpClientModule, - FormsModule, - NoopAnimationsModule, - MatDatepickerModule, - MatIconModule, - MatInputModule, - MatNativeDateModule, - TranslateModule.forRoot({ - loader: { - provide: TranslateLoader, - useClass: TranslateLoaderService - } - }) - ], - declarations: [ - CardViewTextItemComponent - ], - providers: [ - AppConfigService, - CardViewUpdateService, - LogService - ] - }).compileComponents(); - })); - - beforeEach(() => { - fixture = TestBed.createComponent(CardViewTextItemComponent); - component = fixture.componentInstance; - component.property = new CardViewTextItemModel ({ - label: 'Text label', - value: 'Lorem ipsum', - key: 'textkey', - default: 'FAKE-DEFAULT-KEY', - editable: false - }); - }); - - afterEach(() => { - fixture.destroy(); - TestBed.resetTestingModule(); - }); - - it('should render the label and value', () => { - fixture.detectChanges(); - - let labelValue = fixture.debugElement.query(By.css('.adf-property-label')); - expect(labelValue).not.toBeNull(); - expect(labelValue.nativeElement.innerText).toBe('Text label'); - - let value = fixture.debugElement.query(By.css(`[data-automation-id="card-textitem-value-${component.property.key}"]`)); - expect(value).not.toBeNull(); - expect(value.nativeElement.innerText.trim()).toBe('Lorem ipsum'); - }); - - it('should render the default as value if the value is empty and editable false', () => { - component.property = new CardViewTextItemModel ({ - label: 'Text label', - value: '', - key: 'textkey', - default: 'FAKE-DEFAULT-KEY', - editable: false - }); - fixture.detectChanges(); - - let value = fixture.debugElement.query(By.css(`[data-automation-id="card-textitem-value-${component.property.key}"]`)); - expect(value).not.toBeNull(); - expect(value.nativeElement.innerText.trim()).toBe('FAKE-DEFAULT-KEY'); - }); - - it('should render the default as value if the value is empty and editable true', () => { - component.property = new CardViewTextItemModel ({ - label: 'Text label', - value: '', - key: 'textkey', - default: 'FAKE-DEFAULT-KEY', - editable: true - }); - fixture.detectChanges(); - - let value = fixture.debugElement.query(By.css(`[data-automation-id="card-textitem-value-${component.property.key}"]`)); - expect(value).not.toBeNull(); - expect(value.nativeElement.innerText.trim()).toBe('FAKE-DEFAULT-KEY'); - }); - - it('should render the default as value if the value is empty and clickable false', () => { - component.property = new CardViewTextItemModel ({ - label: 'Text label', - value: '', - key: 'textkey', - default: 'FAKE-DEFAULT-KEY', - clickable: false - }); - fixture.detectChanges(); - - let value = fixture.debugElement.query(By.css(`[data-automation-id="card-textitem-value-${component.property.key}"]`)); - expect(value).not.toBeNull(); - expect(value.nativeElement.innerText.trim()).toBe('FAKE-DEFAULT-KEY'); - }); - - it('should render the default as value if the value is empty and clickable true', () => { - component.property = new CardViewTextItemModel ({ - label: 'Text label', - value: '', - key: 'textkey', - default: 'FAKE-DEFAULT-KEY', - clickable: true - }); - fixture.detectChanges(); - - let value = fixture.debugElement.query(By.css(`[data-automation-id="card-textitem-value-${component.property.key}"]`)); - expect(value).not.toBeNull(); - expect(value.nativeElement.innerText.trim()).toBe('FAKE-DEFAULT-KEY'); - }); - - it('should render value when editable:true', () => { - component.editable = true; - component.property.editable = true; - fixture.detectChanges(); - - let value = fixture.debugElement.query(By.css(`[data-automation-id="card-textitem-value-${component.property.key}"]`)); - expect(value).not.toBeNull(); - expect(value.nativeElement.innerText.trim()).toBe('Lorem ipsum'); - }); - - it('should render the edit icon in case of editable:true', () => { - component.editable = true; - component.property.editable = true; - fixture.detectChanges(); - - let editIcon = fixture.debugElement.query(By.css(`[data-automation-id="card-textitem-edit-icon-${component.property.key}"]`)); - expect(editIcon).not.toBeNull('Edit icon should be shown'); - }); - - it('should NOT render the edit icon in case of editable:false', () => { - component.editable = false; - fixture.detectChanges(); - - let editIcon = fixture.debugElement.query(By.css(`[data-automation-id="card-textitem-edit-icon-${component.property.key}"]`)); - expect(editIcon).toBeNull('Edit icon should NOT be shown'); - }); - - it('should NOT render the picker and toggle in case of editable:true but (general) editable:false', () => { - component.editable = false; - component.property.editable = true; - fixture.detectChanges(); - - let editIcon = fixture.debugElement.query(By.css(`[data-automation-id="card-textitem-edit-icon-${component.property.key}"]`)); - expect(editIcon).toBeNull('Edit icon should NOT be shown'); - }); - - it('should trigger an update event on the CardViewUpdateService', (done) => { - component.editable = true; - component.property.editable = true; - const cardViewUpdateService = TestBed.get(CardViewUpdateService); - const expectedText = 'changed text'; - fixture.detectChanges(); - - cardViewUpdateService.itemUpdated$.subscribe( - (updateNotification) => { - expect(updateNotification.target).toBe(component.property); - expect(updateNotification.changed).toEqual({ textkey: expectedText }); - done(); - } - ); - - let editIcon = fixture.debugElement.query(By.css(`[data-automation-id="card-textitem-edit-toggle-${component.property.key}"]`)); - editIcon.triggerEventHandler('click', null); - fixture.detectChanges(); - - let editInput = fixture.debugElement.query(By.css(`[data-automation-id="card-textitem-editinput-${component.property.key}"]`)); - editInput.nativeElement.value = expectedText; - editInput.nativeElement.dispatchEvent(new Event('input')); - fixture.detectChanges(); - - let updateInput = fixture.debugElement.query(By.css(`[data-automation-id="card-textitem-update-${component.property.key}"]`)); - updateInput.triggerEventHandler('click', null); - }); - - it('should switch back to readonly mode after an update attempt', async(() => { - component.editable = true; - component.property.editable = true; - component.inEdit = true; - component.editedValue = 'updated-value'; - fixture.detectChanges(); - - component.update(); - - fixture.whenStable().then(() => { - expect(component.property.value).toBe(component.editedValue); - expect(component.inEdit).toBeFalsy(); - }); - })); -}); diff --git a/lib/core/card-view/card-view.component.html b/lib/core/card-view/card-view.component.html deleted file mode 100644 index afe045024f..0000000000 --- a/lib/core/card-view/card-view.component.html +++ /dev/null @@ -1,8 +0,0 @@ -
-
-
- -
-
-
- diff --git a/lib/core/card-view/card-view.component.scss b/lib/core/card-view/card-view.component.scss deleted file mode 100644 index 2cac58417d..0000000000 --- a/lib/core/card-view/card-view.component.scss +++ /dev/null @@ -1,34 +0,0 @@ - -@mixin adf-card-view-theme($theme) { - - .adf { - &-property-list { - display: table; - width: 100%; - border-collapse: collapse; - border-spacing: 0; - } - - &-property { - display: table-row; - } - - &-property &-property-label { - display: table-cell; - min-width: 100px; - padding-right: 30px; - word-wrap: break-word; - color: rgb(186, 186, 186);; - vertical-align: top; - padding-bottom: 20px; - } - - &-property &-property-value { - width: 100%; - display: table-cell; - color: rgb(101, 101, 101); - vertical-align: top; - padding-bottom: 20px; - } - } -} diff --git a/lib/core/card-view/card-view.module.scss b/lib/core/card-view/card-view.module.scss new file mode 100644 index 0000000000..a3a1c75074 --- /dev/null +++ b/lib/core/card-view/card-view.module.scss @@ -0,0 +1,11 @@ +@import './components/card-view-dateitem/card-view-dateitem.component'; +@import './components/card-view-textitem/card-view-textitem.component'; +@import './components/card-view/card-view.component'; +@import '~@mat-datetimepicker/core/datetimepicker/datetimepicker-theme.scss'; + +@mixin adf-card-view-module-theme($theme) { + @include adf-card-view-dateitem-theme($theme); + @include adf-card-view-textitem-theme($theme); + @include adf-card-view-theme($theme); + @include mat-datetimepicker-theme($theme); +} diff --git a/lib/core/card-view/card-view.module.ts b/lib/core/card-view/card-view.module.ts index 4941131289..8cacf66803 100644 --- a/lib/core/card-view/card-view.module.ts +++ b/lib/core/card-view/card-view.module.ts @@ -18,47 +18,69 @@ import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; import { FormsModule } from '@angular/forms'; -import { MatButtonModule, MatDatepickerModule, MatIconModule, MatInputModule, MatNativeDateModule } from '@angular/material'; +import { + MatButtonModule, + MatDatepickerModule, + MatIconModule, + MatInputModule, + MatCheckboxModule, + MatNativeDateModule +} from '@angular/material'; +import { MatDatetimepickerModule, MatNativeDatetimeModule } from '@mat-datetimepicker/core'; import { FlexLayoutModule } from '@angular/flex-layout'; import { TranslateModule } from '@ngx-translate/core'; -import { CardViewContentProxyDirective } from './card-view-content-proxy.directive'; -import { CardViewDateItemComponent } from './card-view-dateitem.component'; -import { CardViewItemDispatcherComponent } from './card-view-item-dispatcher.component'; -import { CardViewMapItemComponent } from './card-view-mapitem.component'; -import { CardViewTextItemComponent } from './card-view-textitem.component'; -import { CardViewComponent } from './card-view.component'; +import { CardViewContentProxyDirective } from './directives/card-view-content-proxy.directive'; +import { CardViewComponent } from './components/card-view/card-view.component'; +import { CardViewBoolItemComponent } from './components/card-view-boolitem/card-view-boolitem.component'; +import { CardViewDateItemComponent } from './components/card-view-dateitem/card-view-dateitem.component'; +import { CardViewItemDispatcherComponent } from './components/card-view-item-dispatcher/card-view-item-dispatcher.component'; +import { CardViewMapItemComponent } from './components/card-view-mapitem/card-view-mapitem.component'; +import { CardViewTextItemComponent } from './components/card-view-textitem/card-view-textitem.component'; + +import { CardItemTypeService } from './services/card-item-types.service'; +import { CardViewUpdateService } from './services/card-view-update.service'; @NgModule({ imports: [ CommonModule, + FormsModule, + FlexLayoutModule, + TranslateModule, MatDatepickerModule, MatNativeDateModule, + MatCheckboxModule, MatInputModule, MatIconModule, MatButtonModule, - FormsModule, - FlexLayoutModule, - TranslateModule + MatDatetimepickerModule, + MatNativeDatetimeModule ], declarations: [ CardViewComponent, - CardViewItemDispatcherComponent, - CardViewContentProxyDirective, - CardViewTextItemComponent, + CardViewBoolItemComponent, + CardViewDateItemComponent, CardViewMapItemComponent, - CardViewDateItemComponent + CardViewTextItemComponent, + CardViewItemDispatcherComponent, + CardViewContentProxyDirective ], entryComponents: [ - CardViewTextItemComponent, + CardViewBoolItemComponent, + CardViewDateItemComponent, CardViewMapItemComponent, - CardViewDateItemComponent + CardViewTextItemComponent ], exports: [ CardViewComponent, - CardViewTextItemComponent, + CardViewBoolItemComponent, + CardViewDateItemComponent, CardViewMapItemComponent, - CardViewDateItemComponent + CardViewTextItemComponent + ], + providers: [ + CardItemTypeService, + CardViewUpdateService ] }) export class CardViewModule {} diff --git a/lib/core/card-view/components/card-view-boolitem/card-view-boolitem.component.html b/lib/core/card-view/components/card-view-boolitem/card-view-boolitem.component.html new file mode 100644 index 0000000000..202f179844 --- /dev/null +++ b/lib/core/card-view/components/card-view-boolitem/card-view-boolitem.component.html @@ -0,0 +1,10 @@ + +
{{ property.label | translate }}
+
+ + +
+
diff --git a/lib/core/card-view/components/card-view-boolitem/card-view-boolitem.component.scss b/lib/core/card-view/components/card-view-boolitem/card-view-boolitem.component.scss new file mode 100644 index 0000000000..a94027d036 --- /dev/null +++ b/lib/core/card-view/components/card-view-boolitem/card-view-boolitem.component.scss @@ -0,0 +1,2 @@ +.adf { +} diff --git a/lib/core/card-view/components/card-view-boolitem/card-view-boolitem.component.spec.ts b/lib/core/card-view/components/card-view-boolitem/card-view-boolitem.component.spec.ts new file mode 100644 index 0000000000..1e32830480 --- /dev/null +++ b/lib/core/card-view/components/card-view-boolitem/card-view-boolitem.component.spec.ts @@ -0,0 +1,243 @@ +/*! + * @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 { HttpClientModule } from '@angular/common/http'; +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { FormsModule } from '@angular/forms'; +import { MaterialModule } from '../../../material.module'; +import { MatCheckboxChange, MatCheckbox } from '@angular/material'; +import { By } from '@angular/platform-browser'; +import { NoopAnimationsModule } from '@angular/platform-browser/animations'; +import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; +import { AppConfigService } from '../../../app-config/app-config.service'; +import { CardViewUpdateService } from '../../services/card-view-update.service'; +import { LogService } from '../../../services/log.service'; +import { TranslateLoaderService } from '../../../services/translate-loader.service'; + +import { CardViewBoolItemComponent } from './card-view-boolitem.component'; +import { CardViewBoolItemModel } from '../../models/card-view-boolitem.model'; + +describe('CardViewBoolItemComponent', () => { + + let fixture: ComponentFixture; + let component: CardViewBoolItemComponent; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [ + HttpClientModule, + FormsModule, + NoopAnimationsModule, + MaterialModule, + TranslateModule.forRoot({ + loader: { + provide: TranslateLoader, + useClass: TranslateLoaderService + } + }) + ], + declarations: [ + CardViewBoolItemComponent + ], + providers: [ + AppConfigService, + CardViewUpdateService, + LogService + ] + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(CardViewBoolItemComponent); + component = fixture.componentInstance; + component.property = new CardViewBoolItemModel({ + label: 'Boolean label', + value: true, + key: 'boolkey', + default: false, + editable: false + }); + }); + + afterEach(() => { + fixture.destroy(); + TestBed.resetTestingModule(); + }); + + describe('Rendering', () => { + + it('should render the label and value if the property is editable', () => { + component.editable = true; + component.property.editable = true; + fixture.detectChanges(); + + let label = fixture.debugElement.query(By.css('.adf-property-label')); + expect(label).not.toBeNull(); + expect(label.nativeElement.innerText).toBe('Boolean label'); + + let value = fixture.debugElement.query(By.css('.adf-property-value')); + expect(value).not.toBeNull(); + }); + + it('should NOT render the label and value if the property is NOT editable and doesn\'t have a proper boolean value set' , () => { + component.editable = true; + component.property.value = undefined; + component.property.editable = false; + fixture.detectChanges(); + + let label = fixture.debugElement.query(By.css('.adf-property-label')); + expect(label).toBeNull(); + + let value = fixture.debugElement.query(By.css('.adf-property-value')); + expect(value).toBeNull(); + }); + + it('should render the label and value if the property is NOT editable but has a proper boolean value set' , () => { + component.editable = true; + component.property.value = false; + component.property.editable = false; + fixture.detectChanges(); + + let label = fixture.debugElement.query(By.css('.adf-property-label')); + expect(label).not.toBeNull(); + + let value = fixture.debugElement.query(By.css('.adf-property-value')); + expect(value).not.toBeNull(); + }); + + it('should render ticked checkbox if property\'s value is true', () => { + component.property.value = true; + fixture.detectChanges(); + + let value = fixture.debugElement.query(By.css('.adf-property-value input[type="checkbox"]')); + expect(value).not.toBeNull(); + expect(value.nativeElement.checked).toBe(true); + }); + + it('should render ticked checkbox if property\'s value is not set but default is true and editable', () => { + component.editable = true; + component.property.editable = true; + component.property.value = undefined; + component.property.default = true; + fixture.detectChanges(); + + let value = fixture.debugElement.query(By.css('.adf-property-value input[type="checkbox"]')); + expect(value).not.toBeNull(); + expect(value.nativeElement.checked).toBe(true); + }); + + it('should render unticked checkbox if property\'s value is false', () => { + component.property.value = false; + fixture.detectChanges(); + + let value = fixture.debugElement.query(By.css('.adf-property-value input[type="checkbox"]')); + expect(value).not.toBeNull(); + expect(value.nativeElement.checked).toBe(false); + }); + + it('should render unticked checkbox if property\'s value is not set but default is false and editable', () => { + component.editable = true; + component.property.editable = true; + component.property.value = undefined; + component.property.default = false; + fixture.detectChanges(); + + let value = fixture.debugElement.query(By.css('.adf-property-value input[type="checkbox"]')); + expect(value).not.toBeNull(); + expect(value.nativeElement.checked).toBe(false); + }); + + it('should render enabled checkbox if property and component are both editable', () => { + component.editable = true; + component.property.editable = true; + component.property.value = true; + fixture.detectChanges(); + + let value = fixture.debugElement.query(By.css('.adf-property-value input[type="checkbox"]')); + expect(value).not.toBeNull(); + expect(value.nativeElement.hasAttribute('disabled')).toBe(false); + }); + + it('should render disabled checkbox if property is not editable', () => { + component.editable = true; + component.property.editable = false; + component.property.value = true; + fixture.detectChanges(); + + let value = fixture.debugElement.query(By.css('.adf-property-value input[type="checkbox"]')); + expect(value).not.toBeNull(); + expect(value.nativeElement.hasAttribute('disabled')).toBe(true); + }); + + it('should render disabled checkbox if component is not editable', () => { + component.editable = false; + component.property.editable = true; + component.property.value = true; + fixture.detectChanges(); + + let value = fixture.debugElement.query(By.css('.adf-property-value input[type="checkbox"]')); + expect(value).not.toBeNull(); + expect(value.nativeElement.hasAttribute('disabled')).toBe(true); + }); + }); + + describe('Update', () => { + + beforeEach(() => { + component.editable = true; + component.property.editable = true; + component.property.value = undefined; + fixture.detectChanges(); + }); + + it('should trigger the update event when changing the checkbox', () => { + const cardViewUpdateService = TestBed.get(CardViewUpdateService); + spyOn(cardViewUpdateService, 'update'); + + component.changed( {checked: true}); + + expect(cardViewUpdateService.update).toHaveBeenCalledWith(component.property, true); + }); + + it('should update the propery\'s value after a changed', async(() => { + component.property.value = true; + + component.changed( {checked: false}); + + fixture.whenStable().then(() => { + expect(component.property.value).toBe(false); + }); + })); + + it('should trigger an update event on the CardViewUpdateService [integration]', (done) => { + const cardViewUpdateService = TestBed.get(CardViewUpdateService); + component.property.value = false; + fixture.detectChanges(); + + cardViewUpdateService.itemUpdated$.subscribe( + (updateNotification) => { + expect(updateNotification.target).toBe(component.property); + expect(updateNotification.changed).toEqual({ boolkey: true }); + done(); + } + ); + + const labelElement = fixture.debugElement.query(By.directive(MatCheckbox)).nativeElement.querySelector('label'); + labelElement.click(); + }); + }); +}); diff --git a/lib/core/card-view/components/card-view-boolitem/card-view-boolitem.component.ts b/lib/core/card-view/components/card-view-boolitem/card-view-boolitem.component.ts new file mode 100644 index 0000000000..719f484479 --- /dev/null +++ b/lib/core/card-view/components/card-view-boolitem/card-view-boolitem.component.ts @@ -0,0 +1,47 @@ +/*! + * @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 { Component, Input } from '@angular/core'; +import { MatCheckboxChange } from '@angular/material'; +import { CardViewBoolItemModel } from '../../models/card-view-boolitem.model'; +import { CardViewUpdateService } from '../../services/card-view-update.service'; + +@Component({ + selector: 'adf-card-view-boolitem', + templateUrl: './card-view-boolitem.component.html', + styleUrls: ['./card-view-boolitem.component.scss'] +}) + +export class CardViewBoolItemComponent { + + @Input() + property: CardViewBoolItemModel; + + @Input() + editable: boolean; + + constructor(private cardViewUpdateService: CardViewUpdateService) {} + + isEditable() { + return this.editable && this.property.editable; + } + + changed(change: MatCheckboxChange) { + this.cardViewUpdateService.update(this.property, change.checked ); + this.property.value = change.checked; + } +} diff --git a/lib/core/card-view/components/card-view-dateitem/card-view-dateitem.component.html b/lib/core/card-view/components/card-view-dateitem/card-view-dateitem.component.html new file mode 100644 index 0000000000..d16f4bfb7e --- /dev/null +++ b/lib/core/card-view/components/card-view-dateitem/card-view-dateitem.component.html @@ -0,0 +1,37 @@ +
{{ property.label | translate }}
+
+ + + {{ property.displayValue }} + + +
+
+ + {{ property.displayValue }} + + + +
+ + + + + +
+ + {{ property.default | translate }} + +
diff --git a/lib/core/card-view/components/card-view-dateitem/card-view-dateitem.component.scss b/lib/core/card-view/components/card-view-dateitem/card-view-dateitem.component.scss new file mode 100644 index 0000000000..17a1307e3f --- /dev/null +++ b/lib/core/card-view/components/card-view-dateitem/card-view-dateitem.component.scss @@ -0,0 +1,41 @@ +@mixin adf-card-view-dateitem-theme($theme) { + + .adf { + &-invisible-date-input { + height: 2px; + width: 0; + overflow: hidden; + opacity: 0; + border: none; + margin: 0; + padding: 0; + float: right; + } + + &-dateitem-editable { + cursor: pointer; + + &-controls { + display: flex; + align-items: center; + justify-content: space-between; + + button.mat-icon-button { + line-height: 20px; + height: 20px; + width: 20px; + } + + mat-icon { + width: 16px; + height: 16px; + opacity: 0.5; + } + + &:hover mat-icon { + opacity: 1; + } + } + } + } +} diff --git a/lib/core/card-view/card-view-dateitem.component.spec.ts b/lib/core/card-view/components/card-view-dateitem/card-view-dateitem.component.spec.ts similarity index 79% rename from lib/core/card-view/card-view-dateitem.component.spec.ts rename to lib/core/card-view/components/card-view-dateitem/card-view-dateitem.component.spec.ts index 19cbe653ca..3a15f3d5cd 100644 --- a/lib/core/card-view/card-view-dateitem.component.spec.ts +++ b/lib/core/card-view/components/card-view-dateitem/card-view-dateitem.component.spec.ts @@ -18,13 +18,14 @@ import { HttpClientModule } from '@angular/common/http'; import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { MatDatepickerModule, MatInputModule, MatNativeDateModule } from '@angular/material'; +import { MatDatetimepickerModule, MatNativeDatetimeModule } from '@mat-datetimepicker/core'; import { By } from '@angular/platform-browser'; import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; import moment from 'moment-es6'; -import { AppConfigService } from '../index'; -import { CardViewDateItemModel } from '../models/card-view-dateitem.model'; -import { CardViewUpdateService } from '../services/card-view-update.service'; -import { TranslateLoaderService } from '../services/translate-loader.service'; +import { AppConfigService } from '../../../index'; +import { CardViewDateItemModel } from '../../models/card-view-dateitem.model'; +import { CardViewUpdateService } from '../../services/card-view-update.service'; +import { TranslateLoaderService } from '../../../services/translate-loader.service'; import { CardViewDateItemComponent } from './card-view-dateitem.component'; @@ -40,6 +41,8 @@ describe('CardViewDateItemComponent', () => { MatDatepickerModule, MatInputModule, MatNativeDateModule, + MatNativeDatetimeModule, + MatDatetimepickerModule, TranslateModule.forRoot({ loader: { provide: TranslateLoader, @@ -87,7 +90,7 @@ describe('CardViewDateItemComponent', () => { expect(value.nativeElement.innerText.trim()).toBe('Jul 10 2017'); }); - it('should render the default as value if the value is empty and editable:false', () => { + it('should NOT render the default as value if the value is empty, editable:false and displayEmpty is false', () => { component.property = new CardViewDateItemModel ({ label: 'Date label', value: '', @@ -96,6 +99,26 @@ describe('CardViewDateItemComponent', () => { format: '', editable: false }); + component.editable = true; + component.displayEmpty = false; + fixture.detectChanges(); + + let value = fixture.debugElement.query(By.css('.adf-property-value')); + expect(value).not.toBeNull(); + expect(value.nativeElement.innerText.trim()).toBe(''); + }); + + it('should render the default as value if the value is empty, editable:false and displayEmpty is true', () => { + component.property = new CardViewDateItemModel ({ + label: 'Date label', + value: '', + key: 'datekey', + default: 'FAKE-DEFAULT-KEY', + format: '', + editable: false + }); + component.editable = true; + component.displayEmpty = true; fixture.detectChanges(); let value = fixture.debugElement.query(By.css('.adf-property-value')); @@ -112,6 +135,7 @@ describe('CardViewDateItemComponent', () => { format: '', editable: true }); + component.editable = true; fixture.detectChanges(); let value = fixture.debugElement.query(By.css('.adf-property-value')); @@ -161,7 +185,7 @@ describe('CardViewDateItemComponent', () => { expect(datePickerToggle).toBeNull('Datepicker toggle should NOT be shown'); }); - it('should open the datetimepicker when clicking on the label', () => { + it('should open the dateXXXpicker when clicking on the label', () => { component.editable = true; component.property.editable = true; fixture.detectChanges(); @@ -190,4 +214,20 @@ describe('CardViewDateItemComponent', () => { component.onDateChanged({value: expectedDate}); }); + + it('should update the propery\'s value after a succesful update attempt', async(() => { + component.editable = true; + component.property.editable = true; + component.property.value = null; + const expectedDate = moment('Jul 10 2017', 'MMM DD YY'); + fixture.detectChanges(); + + component.onDateChanged({value: expectedDate}); + + fixture.whenStable().then( + (updateNotification) => { + expect(component.property.value).toEqual(expectedDate.toDate()); + } + ); + })); }); diff --git a/lib/core/card-view/card-view-dateitem.component.ts b/lib/core/card-view/components/card-view-dateitem/card-view-dateitem.component.ts similarity index 76% rename from lib/core/card-view/card-view-dateitem.component.ts rename to lib/core/card-view/components/card-view-dateitem/card-view-dateitem.component.ts index ab353da31a..9442c8552e 100644 --- a/lib/core/card-view/card-view-dateitem.component.ts +++ b/lib/core/card-view/components/card-view-dateitem/card-view-dateitem.component.ts @@ -16,15 +16,15 @@ */ import { Component, Input, OnInit, ViewChild } from '@angular/core'; -import { MatDatepicker } from '@angular/material'; import { DateAdapter, MAT_DATE_FORMATS } from '@angular/material'; +import { MatDatetimepicker } from '@mat-datetimepicker/core'; import moment from 'moment-es6'; import { Moment } from 'moment'; -import { CardViewDateItemModel } from '../models/card-view-dateitem.model'; -import { CardViewUpdateService } from '../services/card-view-update.service'; -import { UserPreferencesService } from '../services/user-preferences.service'; -import { MomentDateAdapter } from '../utils/momentDateAdapter'; -import { MOMENT_DATE_FORMATS } from '../utils/moment-date-formats.model'; +import { CardViewDateItemModel } from '../../models/card-view-dateitem.model'; +import { CardViewUpdateService } from '../../services/card-view-update.service'; +import { UserPreferencesService } from '../../../services/user-preferences.service'; +import { MomentDateAdapter } from '../../../utils/momentDateAdapter'; +import { MOMENT_DATE_FORMATS } from '../../../utils/moment-date-formats.model'; @Component({ providers: [ @@ -42,10 +42,13 @@ export class CardViewDateItemComponent implements OnInit { property: CardViewDateItemModel; @Input() - editable: boolean; + editable: boolean = false; - @ViewChild(MatDatepicker) - public datepicker: MatDatepicker; + @Input() + displayEmpty: boolean = true; + + @ViewChild(MatDatetimepicker) + public datepicker: MatDatetimepicker; valueDate: Moment; @@ -64,7 +67,10 @@ export class CardViewDateItemComponent implements OnInit { if (this.property.value) { this.valueDate = moment(this.property.value, this.SHOW_FORMAT); } + } + showProperty() { + return this.displayEmpty || !this.property.isEmpty(); } isEditable() { @@ -81,6 +87,7 @@ export class CardViewDateItemComponent implements OnInit { if (momentDate.isValid()) { this.valueDate = momentDate; this.cardViewUpdateService.update(this.property, momentDate.toDate()); + this.property.value = momentDate.toDate(); } } } diff --git a/lib/core/card-view/card-view-item-dispatcher.component.spec.ts b/lib/core/card-view/components/card-view-item-dispatcher/card-view-item-dispatcher.component.spec.ts similarity index 71% rename from lib/core/card-view/card-view-item-dispatcher.component.spec.ts rename to lib/core/card-view/components/card-view-item-dispatcher/card-view-item-dispatcher.component.spec.ts index 42da90102d..8201923007 100644 --- a/lib/core/card-view/card-view-item-dispatcher.component.spec.ts +++ b/lib/core/card-view/components/card-view-item-dispatcher/card-view-item-dispatcher.component.spec.ts @@ -17,14 +17,14 @@ /* tslint:disable:component-selector */ -import { Component, Input } from '@angular/core'; +import { Component, Input, SimpleChange } from '@angular/core'; import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { By } from '@angular/platform-browser'; import { BrowserDynamicTestingModule } from '@angular/platform-browser-dynamic/testing'; -import { CardViewItem } from '../interface/card-view-item.interface'; -import { CardItemTypeService } from '../services/card-item-types.service'; -import { CardViewContentProxyDirective } from './card-view-content-proxy.directive'; -import { CardViewItemDispatcherComponent } from './card-view-item-dispatcher.component'; +import { CardViewItem } from '../../interfaces/card-view-item.interface'; +import { CardItemTypeService } from '../../services/card-item-types.service'; +import { CardViewContentProxyDirective } from '../../directives/card-view-content-proxy.directive'; +import { CardViewItemDispatcherComponent } from '../card-view-item-dispatcher/card-view-item-dispatcher.component'; @Component({ selector: 'whatever-you-want-to-have', @@ -79,9 +79,10 @@ describe('CardViewItemDispatcherComponent', () => { } }; component.editable = true; + component.displayEmpty = true; fixture.detectChanges(); - component.ngOnChanges(null); + component.ngOnChanges({}); }); afterEach(() => { @@ -97,9 +98,9 @@ describe('CardViewItemDispatcherComponent', () => { }); it('should load the CardViewShinyCustomElementItemComponent only ONCE', () => { - component.ngOnChanges(); - component.ngOnChanges(); - component.ngOnChanges(); + component.ngOnChanges({}); + component.ngOnChanges({}); + component.ngOnChanges({}); fixture.detectChanges(); const shinyCustomElementItemComponent = fixture.debugElement.queryAll(By.css('whatever-you-want-to-have')); @@ -107,11 +108,32 @@ describe('CardViewItemDispatcherComponent', () => { expect(shinyCustomElementItemComponent.length).toEqual(1); }); - it('should pass through the property and editable parameters', () => { + it('should pass through the property, editable and displayEmpty parameters', () => { const shinyCustomElementItemComponent = fixture.debugElement.query(By.css('whatever-you-want-to-have')).componentInstance; expect(shinyCustomElementItemComponent.property).toBe(component.property); expect(shinyCustomElementItemComponent.editable).toBe(component.editable); + expect(shinyCustomElementItemComponent.displayEmpty).toBe(component.displayEmpty); + }); + + it('should update the subcomponent\'s input parameters', () => { + const expectedEditable = false, + expectedDisplayEmpty = true, + expectedProperty = {}, + expectedCustomInput = 1; + + component.ngOnChanges({ + editable: new SimpleChange(true, expectedEditable, false), + displayEmpty: new SimpleChange(false, expectedDisplayEmpty, false), + property: new SimpleChange(null, expectedProperty, false), + customInput: new SimpleChange(0, expectedCustomInput, false) + }); + + const shinyCustomElementItemComponent = fixture.debugElement.query(By.css('whatever-you-want-to-have')).componentInstance; + expect(shinyCustomElementItemComponent.property).toBe(expectedProperty); + expect(shinyCustomElementItemComponent.editable).toBe(expectedEditable); + expect(shinyCustomElementItemComponent.displayEmpty).toBe(expectedDisplayEmpty); + expect(shinyCustomElementItemComponent.customInput).toBe(expectedCustomInput); }); }); @@ -138,21 +160,21 @@ describe('CardViewItemDispatcherComponent', () => { lifeCycleMethods.forEach((lifeCycleMethod) => { shinyCustomElementItemComponent[lifeCycleMethod] = () => {}; spyOn(shinyCustomElementItemComponent, lifeCycleMethod); - const param1 = {}; - const param2 = {}; + const param = {}; - component[lifeCycleMethod].call(component, param1, param2); + component[lifeCycleMethod].call(component, param); - expect(shinyCustomElementItemComponent[lifeCycleMethod]).toHaveBeenCalledWith(param1, param2); + expect(shinyCustomElementItemComponent[lifeCycleMethod]).toHaveBeenCalledWith(param); }); }); it('should NOT call through the lifecycle methods if the method does not exist (no error should be thrown)', () => { + const param = {}; lifeCycleMethods.forEach((lifeCycleMethod) => { shinyCustomElementItemComponent[lifeCycleMethod] = undefined; const execution = () => { - component[lifeCycleMethod].call(component); + component[lifeCycleMethod].call(component, param); }; expect(execution).not.toThrowError(); diff --git a/lib/core/card-view/card-view-item-dispatcher.component.ts b/lib/core/card-view/components/card-view-item-dispatcher/card-view-item-dispatcher.component.ts similarity index 76% rename from lib/core/card-view/card-view-item-dispatcher.component.ts rename to lib/core/card-view/components/card-view-item-dispatcher/card-view-item-dispatcher.component.ts index c8b3ea13c0..338d1cf3a7 100644 --- a/lib/core/card-view/card-view-item-dispatcher.component.ts +++ b/lib/core/card-view/components/card-view-item-dispatcher/card-view-item-dispatcher.component.ts @@ -20,11 +20,13 @@ import { ComponentFactoryResolver, Input, OnChanges, + SimpleChange, + SimpleChanges, ViewChild } from '@angular/core'; -import { CardViewItem } from '../interface/card-view-item.interface'; -import { CardItemTypeService } from '../services/card-item-types.service'; -import { CardViewContentProxyDirective } from './card-view-content-proxy.directive'; +import { CardViewItem } from '../../interfaces/card-view-item.interface'; +import { CardItemTypeService } from '../../services/card-item-types.service'; +import { CardViewContentProxyDirective } from '../../directives/card-view-content-proxy.directive'; @Component({ selector: 'adf-card-view-item-dispatcher', @@ -37,6 +39,9 @@ export class CardViewItemDispatcherComponent implements OnChanges { @Input() editable: boolean; + @Input() + displayEmpty: boolean = true; + @ViewChild(CardViewContentProxyDirective) private content: CardViewContentProxyDirective; @@ -63,13 +68,19 @@ export class CardViewItemDispatcherComponent implements OnChanges { }); } - ngOnChanges(...args) { + ngOnChanges(changes: SimpleChanges) { if (!this.loaded) { this.loadComponent(); this.loaded = true; } - this.proxy('ngOnChanges', ...args); + Object.keys(changes) + .map(changeName => [changeName, changes[changeName]]) + .forEach(([inputParamName, simpleChange]: [string, SimpleChange]) => { + this.componentReference.instance[inputParamName] = simpleChange.currentValue; + }); + + this.proxy('ngOnChanges', changes); } private loadComponent() { @@ -80,6 +91,7 @@ export class CardViewItemDispatcherComponent implements OnChanges { this.componentReference.instance.editable = this.editable; this.componentReference.instance.property = this.property; + this.componentReference.instance.displayEmpty = this.displayEmpty; } private proxy(methodName, ...args) { diff --git a/lib/core/card-view/card-view-mapitem.component.html b/lib/core/card-view/components/card-view-mapitem/card-view-mapitem.component.html similarity index 63% rename from lib/core/card-view/card-view-mapitem.component.html rename to lib/core/card-view/components/card-view-mapitem/card-view-mapitem.component.html index 01021e0348..82f9749b3c 100644 --- a/lib/core/card-view/card-view-mapitem.component.html +++ b/lib/core/card-view/components/card-view-mapitem/card-view-mapitem.component.html @@ -1,12 +1,12 @@ -
{{ property.label | translate }}
+
{{ property.label | translate }}
- {{ property.displayValue }} + {{ property.displayValue }} - {{ property.displayValue }} + {{ property.displayValue }}
diff --git a/lib/core/card-view/card-view-mapitem.component.scss b/lib/core/card-view/components/card-view-mapitem/card-view-mapitem.component.scss similarity index 100% rename from lib/core/card-view/card-view-mapitem.component.scss rename to lib/core/card-view/components/card-view-mapitem/card-view-mapitem.component.scss diff --git a/lib/core/card-view/card-view-mapitem.component.spec.ts b/lib/core/card-view/components/card-view-mapitem/card-view-mapitem.component.spec.ts similarity index 77% rename from lib/core/card-view/card-view-mapitem.component.spec.ts rename to lib/core/card-view/components/card-view-mapitem/card-view-mapitem.component.spec.ts index e9e3a0b480..814cdc89f4 100644 --- a/lib/core/card-view/card-view-mapitem.component.spec.ts +++ b/lib/core/card-view/components/card-view-mapitem/card-view-mapitem.component.spec.ts @@ -19,15 +19,15 @@ import { HttpClientModule } from '@angular/common/http'; import { DebugElement } from '@angular/core'; import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { FormsModule } from '@angular/forms'; -import { MaterialModule } from '../material.module'; +import { MaterialModule } from '../../../material.module'; import { By } from '@angular/platform-browser'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; -import { CardViewMapItemModel } from '../models/card-view-mapitem.model'; -import { AppConfigService } from '../app-config/app-config.service'; -import { CardViewUpdateService } from '../services/card-view-update.service'; -import { LogService } from '../services/log.service'; -import { TranslateLoaderService } from '../services/translate-loader.service'; +import { CardViewMapItemModel } from '../../models/card-view-mapitem.model'; +import { AppConfigService } from '../../../app-config/app-config.service'; +import { CardViewUpdateService } from '../../services/card-view-update.service'; +import { LogService } from '../../../services/log.service'; +import { TranslateLoaderService } from '../../../services/translate-loader.service'; import { CardViewMapItemComponent } from './card-view-mapitem.component'; @@ -77,14 +77,15 @@ describe('CardViewMapItemComponent', () => { TestBed.resetTestingModule(); }); - it('should render the default if the value is empty', () => { + it('should render the default if the value is empty and displayEmpty is true', () => { component.property = new CardViewMapItemModel({ label: 'Map label', value: null, key: 'mapkey', - default: 'Fake default' + default: 'Fake default', + clickable: false }); - + component.displayEmpty = true; fixture.detectChanges(); let labelValue = debug.query(By.css('.adf-property-label')); @@ -96,6 +97,25 @@ describe('CardViewMapItemComponent', () => { expect(value.nativeElement.innerText.trim()).toBe('Fake default'); }); + it('should NOT render the default if the value is empty and displayEmpty is false', () => { + component.property = new CardViewMapItemModel({ + label: 'Map label', + value: null, + key: 'mapkey', + default: 'Fake default', + clickable: false + }); + component.displayEmpty = false; + fixture.detectChanges(); + + let labelValue = debug.query(By.css('.adf-property-label')); + expect(labelValue).toBeNull(); + + let value = debug.query(By.css(`[data-automation-id="card-mapitem-value-${component.property.key}"]`)); + expect(value).not.toBeNull(); + expect(value.nativeElement.innerText.trim()).toBe(''); + }); + it('should render the label and value', () => { component.property = new CardViewMapItemModel({ label: 'Map label', diff --git a/lib/core/card-view/card-view-mapitem.component.ts b/lib/core/card-view/components/card-view-mapitem/card-view-mapitem.component.ts similarity index 79% rename from lib/core/card-view/card-view-mapitem.component.ts rename to lib/core/card-view/components/card-view-mapitem/card-view-mapitem.component.ts index a6a4f30641..c325db0c3c 100644 --- a/lib/core/card-view/card-view-mapitem.component.ts +++ b/lib/core/card-view/components/card-view-mapitem/card-view-mapitem.component.ts @@ -16,8 +16,8 @@ */ import { Component, Input } from '@angular/core'; -import { CardViewMapItemModel } from '../models/card-view-mapitem.model'; -import { CardViewUpdateService } from '../services/card-view-update.service'; +import { CardViewMapItemModel } from '../../models/card-view-mapitem.model'; +import { CardViewUpdateService } from '../../services/card-view-update.service'; @Component({ selector: 'adf-card-view-mapitem', @@ -29,8 +29,15 @@ export class CardViewMapItemComponent { @Input() property: CardViewMapItemModel; + @Input() + displayEmpty: boolean = true; + constructor(private cardViewUpdateService: CardViewUpdateService) {} + showProperty() { + return this.displayEmpty || !this.property.isEmpty(); + } + isClickable() { return this.property.clickable; } diff --git a/lib/core/card-view/components/card-view-textitem/card-view-textitem.component.html b/lib/core/card-view/components/card-view-textitem/card-view-textitem.component.html new file mode 100644 index 0000000000..9947abc706 --- /dev/null +++ b/lib/core/card-view/components/card-view-textitem/card-view-textitem.component.html @@ -0,0 +1,58 @@ +
{{ property.label | translate }}
+
+ + + {{ property.displayValue }} + + + + {{ property.displayValue }} + + + + +
+ + {{ property.displayValue }} + + create +
+
+
+ + + + + done + clear +
+ +
    +
  • {{ errorMessage | translate }}
  • +
+
+
+
+ + {{ property.default | translate }} + +
diff --git a/lib/core/card-view/card-view-textitem.component.scss b/lib/core/card-view/components/card-view-textitem/card-view-textitem.component.scss similarity index 66% rename from lib/core/card-view/card-view-textitem.component.scss rename to lib/core/card-view/components/card-view-textitem/card-view-textitem.component.scss index a803682fad..58b5209f1c 100644 --- a/lib/core/card-view/card-view-textitem.component.scss +++ b/lib/core/card-view/components/card-view-textitem/card-view-textitem.component.scss @@ -30,23 +30,46 @@ } &-textitem-editable { - display: flex; - mat-icon:hover { - opacity: 1; - cursor: pointer; + &-controls { + display: flex; + + mat-icon:hover { + opacity: 1; + cursor: pointer; + } + + mat-form-field { + width: 100%; + } + + input:focus, + textarea:focus { + border: 1px solid mat-color($foreground, text, 0.15); + } } - mat-form-field { - width: 100%; - } + &-error { + font-size: 12px; + padding-top: 4px; - input:focus, - textarea:focus { - border: 1px solid mat-color($foreground, text, 0.15); + ul { + margin: 0; + padding: 0; + list-style-type: none; + + li { + margin: 0; + padding: 0; + } + } } } + &-textitem-default-value { + color: mat-color($foreground, text, 0.54); + } + &-textitem-editable .mat-input-wrapper { margin: 0; padding-bottom: 0; @@ -67,7 +90,7 @@ } &-textitem-editable .mat-input-placeholder { - top: 0; + top: 4px; } &-textitem-editable .mat-input-element { diff --git a/lib/core/card-view/components/card-view-textitem/card-view-textitem.component.spec.ts b/lib/core/card-view/components/card-view-textitem/card-view-textitem.component.spec.ts new file mode 100644 index 0000000000..7aeee87c86 --- /dev/null +++ b/lib/core/card-view/components/card-view-textitem/card-view-textitem.component.spec.ts @@ -0,0 +1,325 @@ +/*! + * @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 { HttpClientModule } from '@angular/common/http'; +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { FormsModule } from '@angular/forms'; +import { MatDatepickerModule, MatIconModule, MatInputModule, MatNativeDateModule } from '@angular/material'; +import { By } from '@angular/platform-browser'; +import { NoopAnimationsModule } from '@angular/platform-browser/animations'; +import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; +import { CardViewTextItemModel } from '../../models/card-view-textitem.model'; +import { AppConfigService } from '../../../app-config/app-config.service'; +import { CardViewUpdateService } from '../../services/card-view-update.service'; +import { LogService } from '../../../services/log.service'; +import { TranslateLoaderService } from '../../../services/translate-loader.service'; + +import { CardViewTextItemComponent } from './card-view-textitem.component'; + +describe('CardViewTextItemComponent', () => { + + let fixture: ComponentFixture; + let component: CardViewTextItemComponent; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [ + HttpClientModule, + FormsModule, + NoopAnimationsModule, + MatDatepickerModule, + MatIconModule, + MatInputModule, + MatNativeDateModule, + TranslateModule.forRoot({ + loader: { + provide: TranslateLoader, + useClass: TranslateLoaderService + } + }) + ], + declarations: [ + CardViewTextItemComponent + ], + providers: [ + AppConfigService, + CardViewUpdateService, + LogService + ] + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(CardViewTextItemComponent); + component = fixture.componentInstance; + component.property = new CardViewTextItemModel ({ + label: 'Text label', + value: 'Lorem ipsum', + key: 'textkey', + default: 'FAKE-DEFAULT-KEY', + editable: false + }); + }); + + afterEach(() => { + fixture.destroy(); + TestBed.resetTestingModule(); + }); + + describe('Rendering', () => { + + it('should render the label and value', () => { + fixture.detectChanges(); + + let labelValue = fixture.debugElement.query(By.css('.adf-property-label')); + expect(labelValue).not.toBeNull(); + expect(labelValue.nativeElement.innerText).toBe('Text label'); + + let value = fixture.debugElement.query(By.css(`[data-automation-id="card-textitem-value-${component.property.key}"]`)); + expect(value).not.toBeNull(); + expect(value.nativeElement.innerText.trim()).toBe('Lorem ipsum'); + }); + + it('should NOT render the default as value if the value is empty, editable is false and displayEmpty is false', () => { + component.property = new CardViewTextItemModel ({ + label: 'Text label', + value: '', + key: 'textkey', + default: 'FAKE-DEFAULT-KEY', + editable: false + }); + component.displayEmpty = false; + fixture.detectChanges(); + + let value = fixture.debugElement.query(By.css(`[data-automation-id="card-textitem-value-${component.property.key}"]`)); + expect(value).not.toBeNull(); + expect(value.nativeElement.innerText.trim()).toBe(''); + }); + + it('should render the default as value if the value is empty, editable is false and displayEmpty is true', () => { + component.property = new CardViewTextItemModel ({ + label: 'Text label', + value: '', + key: 'textkey', + default: 'FAKE-DEFAULT-KEY', + editable: false + }); + component.displayEmpty = true; + fixture.detectChanges(); + + let value = fixture.debugElement.query(By.css(`[data-automation-id="card-textitem-value-${component.property.key}"]`)); + expect(value).not.toBeNull(); + expect(value.nativeElement.innerText.trim()).toBe('FAKE-DEFAULT-KEY'); + }); + + it('should render the default as value if the value is empty and editable true', () => { + component.property = new CardViewTextItemModel ({ + label: 'Text label', + value: '', + key: 'textkey', + default: 'FAKE-DEFAULT-KEY', + editable: true + }); + component.editable = true; + fixture.detectChanges(); + + let value = fixture.debugElement.query(By.css(`[data-automation-id="card-textitem-value-${component.property.key}"]`)); + expect(value).not.toBeNull(); + expect(value.nativeElement.innerText.trim()).toBe('FAKE-DEFAULT-KEY'); + }); + + it('should NOT render the default as value if the value is empty, clickable is false and displayEmpty is false', () => { + component.property = new CardViewTextItemModel ({ + label: 'Text label', + value: '', + key: 'textkey', + default: 'FAKE-DEFAULT-KEY', + clickable: false + }); + component.displayEmpty = false; + fixture.detectChanges(); + + let value = fixture.debugElement.query(By.css(`[data-automation-id="card-textitem-value-${component.property.key}"]`)); + expect(value).not.toBeNull(); + expect(value.nativeElement.innerText.trim()).toBe(''); + }); + + it('should render the default as value if the value is empty, clickable is false and displayEmpty is true', () => { + component.property = new CardViewTextItemModel ({ + label: 'Text label', + value: '', + key: 'textkey', + default: 'FAKE-DEFAULT-KEY', + clickable: false + }); + component.displayEmpty = true; + fixture.detectChanges(); + + let value = fixture.debugElement.query(By.css(`[data-automation-id="card-textitem-value-${component.property.key}"]`)); + expect(value).not.toBeNull(); + expect(value.nativeElement.innerText.trim()).toBe('FAKE-DEFAULT-KEY'); + }); + + it('should render the default as value if the value is empty and clickable true', () => { + component.property = new CardViewTextItemModel ({ + label: 'Text label', + value: '', + key: 'textkey', + default: 'FAKE-DEFAULT-KEY', + clickable: true + }); + fixture.detectChanges(); + + let value = fixture.debugElement.query(By.css(`[data-automation-id="card-textitem-value-${component.property.key}"]`)); + expect(value).not.toBeNull(); + expect(value.nativeElement.innerText.trim()).toBe('FAKE-DEFAULT-KEY'); + }); + + it('should render value when editable:true', () => { + component.editable = true; + component.property.editable = true; + fixture.detectChanges(); + + let value = fixture.debugElement.query(By.css(`[data-automation-id="card-textitem-value-${component.property.key}"]`)); + expect(value).not.toBeNull(); + expect(value.nativeElement.innerText.trim()).toBe('Lorem ipsum'); + }); + + it('should render the edit icon in case of editable:true', () => { + component.editable = true; + component.property.editable = true; + fixture.detectChanges(); + + let editIcon = fixture.debugElement.query(By.css(`[data-automation-id="card-textitem-edit-icon-${component.property.key}"]`)); + expect(editIcon).not.toBeNull('Edit icon should be shown'); + }); + + it('should NOT render the edit icon in case of editable:false', () => { + component.editable = false; + fixture.detectChanges(); + + let editIcon = fixture.debugElement.query(By.css(`[data-automation-id="card-textitem-edit-icon-${component.property.key}"]`)); + expect(editIcon).toBeNull('Edit icon should NOT be shown'); + }); + + it('should NOT render the picker and toggle in case of editable:true but (general) editable:false', () => { + component.editable = false; + component.property.editable = true; + fixture.detectChanges(); + + let editIcon = fixture.debugElement.query(By.css(`[data-automation-id="card-textitem-edit-icon-${component.property.key}"]`)); + expect(editIcon).toBeNull('Edit icon should NOT be shown'); + }); + }); + + describe('Update', () => { + + beforeEach(() => { + component.editable = true; + component.property.editable = true; + component.inEdit = true; + component.editedValue = 'updated-value'; + fixture.detectChanges(); + }); + + it('should call the isValid method with the edited value', () => { + spyOn(component.property, 'isValid'); + component.editedValue = 'updated-value'; + + component.update(); + + expect(component.property.isValid).toHaveBeenCalledWith('updated-value'); + }); + + it('should trigger the update event if the editedValue is valid', () => { + component.property.isValid = () => true; + const cardViewUpdateService = TestBed.get(CardViewUpdateService); + spyOn(cardViewUpdateService, 'update'); + component.editedValue = 'updated-value'; + + component.update(); + + expect(cardViewUpdateService.update).toHaveBeenCalledWith(component.property, 'updated-value'); + }); + + it('should NOT trigger the update event if the editedValue is invalid', () => { + component.property.isValid = () => false; + const cardViewUpdateService = TestBed.get(CardViewUpdateService); + spyOn(cardViewUpdateService, 'update'); + + component.update(); + + expect(cardViewUpdateService.update).not.toHaveBeenCalled(); + }); + + it('should set the errorMessages properly if the editedValue is invalid', () => { + const expectedErrorMessages = ['Something went wrong']; + component.property.isValid = () => false; + component.property.getValidationErrors = () => expectedErrorMessages; + + component.update(); + + expect(component.errorMessages).toBe(expectedErrorMessages); + }); + + it('should update the propery\'s value after a succesful update attempt', async(() => { + component.property.isValid = () => true; + component.update(); + + fixture.whenStable().then(() => { + expect(component.property.value).toBe(component.editedValue); + }); + })); + + it('should switch back to readonly mode after an update attempt', async(() => { + component.property.isValid = () => true; + component.update(); + + fixture.whenStable().then(() => { + expect(component.inEdit).toBeFalsy(); + }); + })); + + it('should trigger an update event on the CardViewUpdateService [integration]', (done) => { + component.inEdit = false; + component.property.isValid = () => true; + const cardViewUpdateService = TestBed.get(CardViewUpdateService); + const expectedText = 'changed text'; + fixture.detectChanges(); + + cardViewUpdateService.itemUpdated$.subscribe( + (updateNotification) => { + expect(updateNotification.target).toBe(component.property); + expect(updateNotification.changed).toEqual({ textkey: expectedText }); + done(); + } + ); + + let editIcon = fixture.debugElement.query(By.css(`[data-automation-id="card-textitem-edit-toggle-${component.property.key}"]`)); + editIcon.triggerEventHandler('click', null); + fixture.detectChanges(); + + let editInput = fixture.debugElement.query(By.css(`[data-automation-id="card-textitem-editinput-${component.property.key}"]`)); + editInput.nativeElement.value = expectedText; + editInput.nativeElement.dispatchEvent(new Event('input')); + fixture.detectChanges(); + + let updateInput = fixture.debugElement.query(By.css(`[data-automation-id="card-textitem-update-${component.property.key}"]`)); + updateInput.triggerEventHandler('click', null); + }); + }); +}); diff --git a/lib/core/card-view/card-view-textitem.component.ts b/lib/core/card-view/components/card-view-textitem/card-view-textitem.component.ts similarity index 67% rename from lib/core/card-view/card-view-textitem.component.ts rename to lib/core/card-view/components/card-view-textitem/card-view-textitem.component.ts index 38b7a63b01..015aebca63 100644 --- a/lib/core/card-view/card-view-textitem.component.ts +++ b/lib/core/card-view/components/card-view-textitem/card-view-textitem.component.ts @@ -16,8 +16,8 @@ */ import { Component, Input, OnChanges, ViewChild } from '@angular/core'; -import { CardViewTextItemModel } from '../models/card-view-textitem.model'; -import { CardViewUpdateService } from '../services/card-view-update.service'; +import { CardViewTextItemModel } from '../../models/card-view-textitem.model'; +import { CardViewUpdateService } from '../../services/card-view-update.service'; @Component({ selector: 'adf-card-view-textitem', @@ -29,28 +29,40 @@ export class CardViewTextItemComponent implements OnChanges { property: CardViewTextItemModel; @Input() - editable: boolean; + editable: boolean = false; + + @Input() + displayEmpty: boolean = true; @ViewChild('editorInput') private editorInput: any; inEdit: boolean = false; editedValue: string; + errorMessages: string[]; constructor(private cardViewUpdateService: CardViewUpdateService) {} - ngOnChanges() { + ngOnChanges(): void { this.editedValue = this.property.value; } - isEditable() { + showProperty(): boolean { + return this.displayEmpty || !this.property.isEmpty(); + } + + isEditable(): boolean { return this.editable && this.property.editable; } - isClickable() { + isClickable(): boolean { return this.property.clickable; } + hasErrors(): number { + return this.errorMessages && this.errorMessages.length; + } + setEditMode(editStatus: boolean): void { this.inEdit = editStatus; setTimeout(() => { @@ -66,9 +78,13 @@ export class CardViewTextItemComponent implements OnChanges { } update(): void { - this.cardViewUpdateService.update(this.property, this.editedValue ); - this.property.value = this.editedValue; - this.setEditMode(false); + if (this.property.isValid(this.editedValue)) { + this.cardViewUpdateService.update(this.property, this.editedValue ); + this.property.value = this.editedValue; + this.setEditMode(false); + } else { + this.errorMessages = this.property.getValidationErrors(this.editedValue); + } } clicked(): void { diff --git a/lib/core/card-view/components/card-view.components.ts b/lib/core/card-view/components/card-view.components.ts new file mode 100644 index 0000000000..3187f44ff6 --- /dev/null +++ b/lib/core/card-view/components/card-view.components.ts @@ -0,0 +1,23 @@ +/*! + * @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. + */ + +export * from './card-view/card-view.component'; +export * from './card-view-boolitem/card-view-boolitem.component'; +export * from './card-view-dateitem/card-view-dateitem.component'; +export * from './card-view-item-dispatcher/card-view-item-dispatcher.component'; +export * from './card-view-mapitem/card-view-mapitem.component'; +export * from './card-view-textitem/card-view-textitem.component'; diff --git a/lib/core/card-view/components/card-view/card-view.component.html b/lib/core/card-view/components/card-view/card-view.component.html new file mode 100644 index 0000000000..e362d0552f --- /dev/null +++ b/lib/core/card-view/components/card-view/card-view.component.html @@ -0,0 +1,11 @@ +
+
+
+ + +
+
+
diff --git a/lib/core/card-view/components/card-view/card-view.component.scss b/lib/core/card-view/components/card-view/card-view.component.scss new file mode 100644 index 0000000000..04b07cbb11 --- /dev/null +++ b/lib/core/card-view/components/card-view/card-view.component.scss @@ -0,0 +1,22 @@ + +@mixin adf-card-view-theme($theme) { + $primary: map-get($theme, primary); + $foreground: map-get($theme, foreground); + + .adf-property-list { + .adf-property { + margin-bottom: 20px; + + .adf-property-label { + font-size: 12px; + color: mat-color($foreground, text, 0.4); + word-wrap: break-word; + } + + .adf-property-value { + font-size: 14px; + color: mat-color($foreground, text, 0.87); + } + } + } +} diff --git a/lib/core/card-view/card-view.component.spec.ts b/lib/core/card-view/components/card-view/card-view.component.spec.ts similarity index 61% rename from lib/core/card-view/card-view.component.spec.ts rename to lib/core/card-view/components/card-view/card-view.component.spec.ts index 03f28b6eed..6e6c351079 100644 --- a/lib/core/card-view/card-view.component.spec.ts +++ b/lib/core/card-view/components/card-view/card-view.component.spec.ts @@ -20,24 +20,26 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { HttpClientModule } from '@angular/common/http'; import { FormsModule } from '@angular/forms'; import { MatDatepickerModule, MatIconModule, MatInputModule, MatNativeDateModule } from '@angular/material'; +import { MatDatetimepickerModule, MatNativeDatetimeModule } from '@mat-datetimepicker/core'; import { By } from '@angular/platform-browser'; import { BrowserDynamicTestingModule } from '@angular/platform-browser-dynamic/testing'; import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; -import { AppConfigService } from '../app-config/app-config.service'; +import { AppConfigService } from '../../../app-config/app-config.service'; -import { CardViewDateItemModel } from '../models/card-view-dateitem.model'; -import { CardViewTextItemModel } from '../models/card-view-textitem.model'; -import { CardViewUpdateService } from '../services/card-view-update.service'; +import { CardViewDateItemModel } from '../../models/card-view-dateitem.model'; +import { CardViewTextItemModel } from '../../models/card-view-textitem.model'; +import { CardViewUpdateService } from '../../services/card-view-update.service'; -import { TranslateLoaderService } from '../services/translate-loader.service'; -import { CardViewContentProxyDirective } from './card-view-content-proxy.directive'; -import { CardViewDateItemComponent } from './card-view-dateitem.component'; -import { CardViewItemDispatcherComponent } from './card-view-item-dispatcher.component'; -import { CardViewTextItemComponent } from './card-view-textitem.component'; +import { TranslateLoaderService } from '../../../services/translate-loader.service'; +import { CardViewContentProxyDirective } from '../../directives/card-view-content-proxy.directive'; +import { CardViewDateItemComponent } from '../card-view-dateitem/card-view-dateitem.component'; +import { CardItemTypeService } from '../../services/card-item-types.service'; +import { CardViewItemDispatcherComponent } from '../card-view-item-dispatcher/card-view-item-dispatcher.component'; +import { CardViewTextItemComponent } from '../card-view-textitem/card-view-textitem.component'; import { CardViewComponent } from './card-view.component'; -describe('AdfCardView', () => { +describe('CardViewComponent', () => { let fixture: ComponentFixture; let component: CardViewComponent; @@ -50,6 +52,8 @@ describe('AdfCardView', () => { MatIconModule, MatInputModule, MatNativeDateModule, + MatDatetimepickerModule, + MatNativeDatetimeModule, FormsModule, TranslateModule.forRoot({ loader: { @@ -66,6 +70,7 @@ describe('AdfCardView', () => { CardViewDateItemComponent ], providers: [ + CardItemTypeService, CardViewUpdateService, AppConfigService ] @@ -135,14 +140,42 @@ describe('AdfCardView', () => { }); })); - it('should render the default value if the value is empty', async(() => { + it('should NOT render anything if the value is empty, not editable and displayEmpty is false', async(() => { component.properties = [new CardViewTextItemModel({ label: 'My default label', value: null, default: 'default value', - key: 'some key' + key: 'some-key', + editable: false })]; + component.editable = true; + component.displayEmpty = false; fixture.detectChanges(); + + fixture.whenStable().then(() => { + fixture.detectChanges(); + + let labelValue = fixture.debugElement.query(By.css('.adf-property-label')); + expect(labelValue).toBeNull(); + + let value = fixture.debugElement.query(By.css('.adf-property-value')); + expect(value).not.toBeNull(); + expect(value.nativeElement.innerText.trim()).toBe(''); + }); + })); + + it('should render the default value if the value is empty, not editable and displayEmpty is true', async(() => { + component.properties = [new CardViewTextItemModel({ + label: 'My default label', + value: null, + default: 'default value', + key: 'some-key', + editable: false + })]; + component.editable = true; + component.displayEmpty = true; + fixture.detectChanges(); + fixture.whenStable().then(() => { fixture.detectChanges(); @@ -150,9 +183,34 @@ describe('AdfCardView', () => { expect(labelValue).not.toBeNull(); expect(labelValue.nativeElement.innerText).toBe('My default label'); - let value = fixture.debugElement.query(By.css('.adf-property-value')); + let value = fixture.debugElement.query(By.css('.adf-property-value [data-automation-id="card-textitem-value-some-key"]')); expect(value).not.toBeNull(); - expect(value.nativeElement.innerText).toBe('default value'); + expect(value.nativeElement.innerText.trim()).toBe('default value'); + }); + })); + + it('should render the default value if the value is empty and is editable', async(() => { + component.properties = [new CardViewTextItemModel({ + label: 'My default label', + value: null, + default: 'default value', + key: 'some-key', + editable: true + })]; + component.editable = true; + component.displayEmpty = false; + fixture.detectChanges(); + + fixture.whenStable().then(() => { + fixture.detectChanges(); + + let labelValue = fixture.debugElement.query(By.css('.adf-property-label')); + expect(labelValue).not.toBeNull(); + expect(labelValue.nativeElement.innerText).toBe('My default label'); + + let value = fixture.debugElement.query(By.css('.adf-property-value [data-automation-id="card-textitem-value-some-key"]')); + expect(value).not.toBeNull(); + expect(value.nativeElement.innerText.trim()).toBe('default value'); }); })); }); diff --git a/lib/core/card-view/card-view.component.ts b/lib/core/card-view/components/card-view/card-view.component.ts similarity index 88% rename from lib/core/card-view/card-view.component.ts rename to lib/core/card-view/components/card-view/card-view.component.ts index 06ef49d618..d945c61cc8 100644 --- a/lib/core/card-view/card-view.component.ts +++ b/lib/core/card-view/components/card-view/card-view.component.ts @@ -16,7 +16,7 @@ */ import { Component, Input } from '@angular/core'; -import { CardViewItem } from '../interface/card-view-item.interface'; +import { CardViewItem } from '../../interfaces/card-view-item.interface'; @Component({ selector: 'adf-card-view', @@ -29,4 +29,7 @@ export class CardViewComponent { @Input() editable: boolean; + + @Input() + displayEmpty: boolean = true; } diff --git a/lib/core/card-view/card-view-content-proxy.directive.ts b/lib/core/card-view/directives/card-view-content-proxy.directive.ts similarity index 100% rename from lib/core/card-view/card-view-content-proxy.directive.ts rename to lib/core/card-view/directives/card-view-content-proxy.directive.ts diff --git a/lib/core/card-view/interfaces/card-view-boolitem-properties.interface.ts b/lib/core/card-view/interfaces/card-view-boolitem-properties.interface.ts new file mode 100644 index 0000000000..99be6e2d78 --- /dev/null +++ b/lib/core/card-view/interfaces/card-view-boolitem-properties.interface.ts @@ -0,0 +1,23 @@ +/*! + * @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 { CardViewItemProperties } from './card-view-item-properties.interface'; + +export interface CardViewBoolItemProperties extends CardViewItemProperties { + value: any; + default?: boolean; +} diff --git a/lib/core/card-view/interfaces/card-view-dateitem-properties.interface.ts b/lib/core/card-view/interfaces/card-view-dateitem-properties.interface.ts new file mode 100644 index 0000000000..d96c22acaf --- /dev/null +++ b/lib/core/card-view/interfaces/card-view-dateitem-properties.interface.ts @@ -0,0 +1,22 @@ +/*! + * @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 { CardViewItemProperties } from './card-view-item-properties.interface'; + +export interface CardViewDateItemProperties extends CardViewItemProperties { + format?: string; +} diff --git a/lib/core/card-view/interfaces/card-view-item-properties.interface.ts b/lib/core/card-view/interfaces/card-view-item-properties.interface.ts new file mode 100644 index 0000000000..7561d776bc --- /dev/null +++ b/lib/core/card-view/interfaces/card-view-item-properties.interface.ts @@ -0,0 +1,28 @@ +/*! + * @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 { CardViewItemValidator } from './card-view-item-validator.interface'; + +export interface CardViewItemProperties { + label: string; + value: any; + key: any; + default?: any; + editable?: boolean; + clickable?: boolean; + validators?: CardViewItemValidator[]; +} diff --git a/lib/core/card-view/interfaces/card-view-item-validator.interface.ts b/lib/core/card-view/interfaces/card-view-item-validator.interface.ts new file mode 100644 index 0000000000..78f0c7c498 --- /dev/null +++ b/lib/core/card-view/interfaces/card-view-item-validator.interface.ts @@ -0,0 +1,21 @@ +/*! + * @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. + */ + +export interface CardViewItemValidator { + message: string; + isValid(value: any): boolean; +} diff --git a/lib/core/interface/card-view-item.interface.ts b/lib/core/card-view/interfaces/card-view-item.interface.ts similarity index 96% rename from lib/core/interface/card-view-item.interface.ts rename to lib/core/card-view/interfaces/card-view-item.interface.ts index b3496f234e..38cb44ade9 100644 --- a/lib/core/interface/card-view-item.interface.ts +++ b/lib/core/card-view/interfaces/card-view-item.interface.ts @@ -21,6 +21,6 @@ export interface CardViewItem { key: string; default?: any; type: string; - displayValue: string; + displayValue: any; editable?: boolean; } diff --git a/lib/core/card-view/interfaces/card-view-textitem-pipe-property.interface.ts b/lib/core/card-view/interfaces/card-view-textitem-pipe-property.interface.ts new file mode 100644 index 0000000000..e5284a5381 --- /dev/null +++ b/lib/core/card-view/interfaces/card-view-textitem-pipe-property.interface.ts @@ -0,0 +1,23 @@ +/*! + * @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 { PipeTransform } from '@angular/core'; + +export interface CardViewTextItemPipeProperty { + pipe: PipeTransform; + params?: any[]; +} diff --git a/lib/core/card-view/interfaces/card-view-textitem-properties.interface.ts b/lib/core/card-view/interfaces/card-view-textitem-properties.interface.ts new file mode 100644 index 0000000000..5ceb9cfa42 --- /dev/null +++ b/lib/core/card-view/interfaces/card-view-textitem-properties.interface.ts @@ -0,0 +1,24 @@ +/*! + * @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 { CardViewItemProperties } from './card-view-item-properties.interface'; +import { CardViewTextItemPipeProperty } from './card-view-textitem-pipe-property.interface'; + +export interface CardViewTextItemProperties extends CardViewItemProperties { + multiline?: boolean; + pipes?: CardViewTextItemPipeProperty[]; +} diff --git a/lib/core/card-view/interfaces/card-view.interfaces.ts b/lib/core/card-view/interfaces/card-view.interfaces.ts new file mode 100644 index 0000000000..a28204cf4c --- /dev/null +++ b/lib/core/card-view/interfaces/card-view.interfaces.ts @@ -0,0 +1,24 @@ +/*! + * @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. + */ + +export * from './card-view-item-properties.interface'; +export * from './card-view-item-validator.interface'; +export * from './card-view-item.interface'; +export * from './card-view-textitem-properties.interface'; +export * from './card-view-dateitem-properties.interface'; +export * from './card-view-boolitem-properties.interface'; +export * from './card-view-textitem-pipe-property.interface'; diff --git a/lib/core/card-view/models/card-view-baseitem.model.spec.ts b/lib/core/card-view/models/card-view-baseitem.model.spec.ts new file mode 100644 index 0000000000..0ae5f559e9 --- /dev/null +++ b/lib/core/card-view/models/card-view-baseitem.model.spec.ts @@ -0,0 +1,85 @@ +/*! + * @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 { CardViewItemProperties } from '../interfaces/card-view.interfaces'; +import { CardViewBaseItemModel } from './card-view-baseitem.model'; +import { CardViewItemValidator } from '../interfaces/card-view.interfaces'; + +class CarViewCustomItemModel extends CardViewBaseItemModel {} + +describe('CardViewBaseItemModel', () => { + + let properties: CardViewItemProperties; + + beforeEach(() => { + properties = { + label: 'Tribe', + value: 'Oseram', + key: 'tribe' + }; + }); + + describe('isValid & Validation errors', () => { + + it('should be true when no validators are set', () => { + const itemModel = new CarViewCustomItemModel(properties); + + const isValid = itemModel.isValid(null); + + expect(isValid).toBe(true); + }); + + it('should call the registered validators to validate', () => { + const validator1: CardViewItemValidator = { isValid: () => true, message: 'validator 1' }; + const validator2: CardViewItemValidator = { isValid: () => true, message: 'validator 2' }; + spyOn(validator1, 'isValid'); + spyOn(validator2, 'isValid'); + properties.validators = [ validator1, validator2 ]; + const itemModel = new CarViewCustomItemModel(properties); + + itemModel.isValid('test-against-this'); + + expect(validator1.isValid).toHaveBeenCalledWith('test-against-this'); + expect(validator2.isValid).toHaveBeenCalledWith('test-against-this'); + }); + + it('should return the registered validators\' common decision (case true)', () => { + const validator1: CardViewItemValidator = { isValid: () => true, message: 'validator 1' }; + const validator2: CardViewItemValidator = { isValid: () => true, message: 'validator 2' }; + properties.validators = [ validator1, validator2 ]; + const itemModel = new CarViewCustomItemModel(properties); + + const isValid = itemModel.isValid('test-against-this'); + + expect(isValid).toBe(true); + expect(itemModel.getValidationErrors('test-against-this')).toEqual([]); + }); + + it('should return the registered validators\' common decision (case false)', () => { + const validator1: CardViewItemValidator = { isValid: () => false, message: 'validator 1' }; + const validator2: CardViewItemValidator = { isValid: () => true, message: 'validator 2' }; + const validator3: CardViewItemValidator = { isValid: () => false, message: 'validator 3' }; + properties.validators = [ validator1, validator2, validator3 ]; + const itemModel = new CarViewCustomItemModel(properties); + + const isValid = itemModel.isValid('test-against-this'); + + expect(isValid).toBe(false); + expect(itemModel.getValidationErrors('test-against-this')).toEqual(['validator 1', 'validator 3']); + }); + }); +}); diff --git a/lib/core/models/card-view-baseitem.model.ts b/lib/core/card-view/models/card-view-baseitem.model.ts similarity index 59% rename from lib/core/models/card-view-baseitem.model.ts rename to lib/core/card-view/models/card-view-baseitem.model.ts index d4f2432796..beb5ecb68e 100644 --- a/lib/core/models/card-view-baseitem.model.ts +++ b/lib/core/card-view/models/card-view-baseitem.model.ts @@ -15,30 +15,16 @@ * limitations under the License. */ -/** - * - * This object represent the basic structure of a card view. - * - * - * @returns {CardViewBaseItemModel} . - */ - -export interface CardViewItemProperties { - label: string; - value: any; - key: any; - default?: string; - editable?: boolean; - clickable?: boolean; -} +import { CardViewItemProperties, CardViewItemValidator } from '../interfaces/card-view.interfaces'; export abstract class CardViewBaseItemModel { label: string; value: any; key: any; - default: string; + default: any; editable: boolean; clickable: boolean; + validators?: CardViewItemValidator[]; constructor(obj: CardViewItemProperties) { this.label = obj.label || ''; @@ -47,9 +33,30 @@ export abstract class CardViewBaseItemModel { this.default = obj.default; this.editable = !!obj.editable; this.clickable = !!obj.clickable; + this.validators = obj.validators || []; } isEmpty(): boolean { return this.value === undefined || this.value === null || this.value === ''; } + + isValid(newValue: any): boolean { + if (!this.validators.length) { + return true; + } + + return this.validators + .map((validator) => validator.isValid(newValue)) + .reduce((isValidUntilNow, isValid) => isValidUntilNow && isValid, true); + } + + getValidationErrors(value): string[] { + if (!this.validators.length) { + return []; + } + + return this.validators + .filter((validator) => !validator.isValid(value)) + .map((validator) => validator.message); + } } diff --git a/lib/core/card-view/models/card-view-boolitem.model.spec.ts b/lib/core/card-view/models/card-view-boolitem.model.spec.ts new file mode 100644 index 0000000000..835423f0e4 --- /dev/null +++ b/lib/core/card-view/models/card-view-boolitem.model.spec.ts @@ -0,0 +1,88 @@ +/*! + * @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 { CardViewBoolItemModel } from './card-view-boolitem.model'; +import { CardViewBoolItemProperties } from '../interfaces/card-view.interfaces'; + +describe('CardViewFloatItemModel', () => { + + let properties: CardViewBoolItemProperties; + + beforeEach(() => { + properties = { + label: 'Tribe', + value: undefined, + key: 'tribe' + }; + }); + + it('true should be parsed as true', () => { + properties.value = true; + const itemModel = new CardViewBoolItemModel(properties); + + expect(itemModel.value).toBe(true); + }); + + it('"true" should be parsed as true', () => { + properties.value = 'true'; + const itemModel = new CardViewBoolItemModel(properties); + + expect(itemModel.value).toBe(true); + }); + + it('1 should be parsed as true', () => { + properties.value = 1; + const itemModel = new CardViewBoolItemModel(properties); + + expect(itemModel.value).toBe(true); + }); + + it('"1" should be parsed as true', () => { + properties.value = '1'; + const itemModel = new CardViewBoolItemModel(properties); + + expect(itemModel.value).toBe(true); + }); + + it('"false" should be parsed as false', () => { + properties.value = 'false'; + const itemModel = new CardViewBoolItemModel(properties); + + expect(itemModel.value).toBe(false); + }); + + it('false should be parsed as false', () => { + properties.value = 'false'; + const itemModel = new CardViewBoolItemModel(properties); + + expect(itemModel.value).toBe(false); + }); + + it('undefined should be parsed as false', () => { + properties.value = undefined; + const itemModel = new CardViewBoolItemModel(properties); + + expect(itemModel.value).toBe(false); + }); + + it('null should be parsed as false', () => { + properties.value = null; + const itemModel = new CardViewBoolItemModel(properties); + + expect(itemModel.value).toBe(false); + }); +}); diff --git a/lib/core/card-view/models/card-view-boolitem.model.ts b/lib/core/card-view/models/card-view-boolitem.model.ts new file mode 100644 index 0000000000..aef558aac8 --- /dev/null +++ b/lib/core/card-view/models/card-view-boolitem.model.ts @@ -0,0 +1,43 @@ +/*! + * @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 { CardViewItem } from '../interfaces/card-view-item.interface'; +import { DynamicComponentModel } from '../../services/dynamic-component-mapper.service'; +import { CardViewBaseItemModel } from './card-view-baseitem.model'; +import { CardViewBoolItemProperties } from '../interfaces/card-view.interfaces'; + +export class CardViewBoolItemModel extends CardViewBaseItemModel implements CardViewItem, DynamicComponentModel { + type: string = 'bool'; + value: boolean = false; + default: boolean; + + constructor(obj: CardViewBoolItemProperties) { + super(obj); + + if (obj.value !== undefined) { + this.value = !!JSON.parse(obj.value); + } + } + + get displayValue() { + if (this.isEmpty()) { + return this.default; + } else { + return this.value; + } + } +} diff --git a/lib/core/models/card-view-dateitem.model.ts b/lib/core/card-view/models/card-view-dateitem.model.ts similarity index 66% rename from lib/core/models/card-view-dateitem.model.ts rename to lib/core/card-view/models/card-view-dateitem.model.ts index 2ec89e66ac..efd303cc4a 100644 --- a/lib/core/models/card-view-dateitem.model.ts +++ b/lib/core/card-view/models/card-view-dateitem.model.ts @@ -15,30 +15,23 @@ * limitations under the License. */ -/** - * - * This object represent the basic structure of a card view. - * - * - * @returns {CardViewDateItemModel} . - */ - import moment from 'moment-es6'; -import { CardViewItem } from '../interface/card-view-item.interface'; -import { DynamicComponentModel } from '../services/dynamic-component-mapper.service'; -import { CardViewBaseItemModel, CardViewItemProperties } from './card-view-baseitem.model'; - -export interface CardViewDateItemProperties extends CardViewItemProperties { - format?: string; -} +import { CardViewItem } from '../interfaces/card-view-item.interface'; +import { DynamicComponentModel } from '../../services/dynamic-component-mapper.service'; +import { CardViewBaseItemModel } from './card-view-baseitem.model'; +import { CardViewDateItemProperties } from '../interfaces/card-view.interfaces'; export class CardViewDateItemModel extends CardViewBaseItemModel implements CardViewItem, DynamicComponentModel { type: string = 'date'; - format: string; + format: string = 'MMM DD YYYY'; constructor(obj: CardViewDateItemProperties) { super(obj); - this.format = obj.format || 'MMM DD YYYY'; + + if (obj.format) { + this.format = obj.format; + } + } get displayValue() { diff --git a/lib/core/card-view/models/card-view-datetimeitem.model.ts b/lib/core/card-view/models/card-view-datetimeitem.model.ts new file mode 100644 index 0000000000..bef5c813f3 --- /dev/null +++ b/lib/core/card-view/models/card-view-datetimeitem.model.ts @@ -0,0 +1,25 @@ +/*! + * @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 { CardViewItem } from '../interfaces/card-view-item.interface'; +import { DynamicComponentModel } from '../../services/dynamic-component-mapper.service'; +import { CardViewDateItemModel } from './card-view-dateitem.model'; + +export class CardViewDatetimeItemModel extends CardViewDateItemModel implements CardViewItem, DynamicComponentModel { + type: string = 'datetime'; + format: string = 'MMM DD YYYY HH:mm'; +} diff --git a/lib/core/card-view/models/card-view-floatitem.model.spec.ts b/lib/core/card-view/models/card-view-floatitem.model.spec.ts new file mode 100644 index 0000000000..7a47f6e6d6 --- /dev/null +++ b/lib/core/card-view/models/card-view-floatitem.model.spec.ts @@ -0,0 +1,59 @@ +/*! + * @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 { CardViewFloatItemModel } from './card-view-floatitem.model'; +import { CardViewTextItemProperties } from '../interfaces/card-view.interfaces'; + +describe('CardViewFloatItemModel', () => { + + let properties: CardViewTextItemProperties; + + beforeEach(() => { + properties = { + label: 'Tribe', + value: '42.42', + key: 'tribe' + }; + }); + + it('value should be parsed as float', () => { + const itemModel = new CardViewFloatItemModel(properties); + + expect(itemModel.value).toBe(42.42); + }); + + it('value should be parsed as float only if there is a value', () => { + properties.value = undefined; + const itemModel = new CardViewFloatItemModel(properties); + + expect(itemModel.value).toBe(undefined); + }); + + it('isValid should return the validator\'s value', () => { + const itemModel = new CardViewFloatItemModel(properties); + + expect(itemModel.isValid(42)).toBe(true, 'For 42 it should be true'); + expect(itemModel.isValid(42.0)).toBe(true, 'For 42.0 it should be true'); + expect(itemModel.isValid('42')).toBe(true, 'For "42" it should be true'); + expect(itemModel.isValid('42.0')).toBe(true, 'For "42.0" it should be true'); + expect(itemModel.isValid('4e2')).toBe(true, 'For "4e2" it should be true'); + expect(itemModel.isValid('4g2')).toBe(false, 'For "4g2" it should be false'); + expect(itemModel.isValid(42.3)).toBe(true, 'For 42.3 it should be true'); + expect(itemModel.isValid('42.3')).toBe(true, 'For "42.3" it should be true'); + expect(itemModel.isValid('test')).toBe(false, 'For "test" it should be false'); + }); +}); diff --git a/lib/core/card-view/models/card-view-floatitem.model.ts b/lib/core/card-view/models/card-view-floatitem.model.ts new file mode 100644 index 0000000000..a302f25a96 --- /dev/null +++ b/lib/core/card-view/models/card-view-floatitem.model.ts @@ -0,0 +1,35 @@ +/*! + * @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 { CardViewItem } from '../interfaces/card-view-item.interface'; +import { DynamicComponentModel } from '../../services/dynamic-component-mapper.service'; +import { CardViewTextItemModel } from './card-view-textitem.model'; +import { CardViewTextItemProperties } from '../interfaces/card-view.interfaces'; +import { CardViewItemFloatValidator } from '..//validators/card-view.validators'; + +export class CardViewFloatItemModel extends CardViewTextItemModel implements CardViewItem, DynamicComponentModel { + type: string = 'float'; + + constructor(obj: CardViewTextItemProperties) { + super(obj); + + this.validators.push(new CardViewItemFloatValidator()); + if (obj.value) { + this.value = parseFloat(obj.value); + } + } +} diff --git a/lib/core/card-view/models/card-view-intitem.model.spec.ts b/lib/core/card-view/models/card-view-intitem.model.spec.ts new file mode 100644 index 0000000000..f6e7ee818a --- /dev/null +++ b/lib/core/card-view/models/card-view-intitem.model.spec.ts @@ -0,0 +1,59 @@ +/*! + * @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 { CardViewIntItemModel } from './card-view-intitem.model'; +import { CardViewTextItemProperties } from '../interfaces/card-view.interfaces'; + +describe('CardViewIntItemModel', () => { + + let properties: CardViewTextItemProperties; + + beforeEach(() => { + properties = { + label: 'Tribe', + value: '42', + key: 'tribe' + }; + }); + + it('value should be parsed as integer', () => { + const itemModel = new CardViewIntItemModel(properties); + + expect(itemModel.value).toBe(42); + }); + + it('value should be parsed as integer only if there is a value', () => { + properties.value = undefined; + const itemModel = new CardViewIntItemModel(properties); + + expect(itemModel.value).toBe(undefined); + }); + + it('isValid should return the validator\'s value', () => { + const itemModel = new CardViewIntItemModel(properties); + + expect(itemModel.isValid(42)).toBe(true, 'For 42 it should be true'); + expect(itemModel.isValid(42.0)).toBe(true, 'For 42.0 it should be true'); + expect(itemModel.isValid('42')).toBe(true, 'For "42" it should be true'); + expect(itemModel.isValid('42.0')).toBe(true, 'For "42.0" it should be true'); + expect(itemModel.isValid('4e2')).toBe(true, 'For "4e2" it should be true'); + expect(itemModel.isValid('4g2')).toBe(false, 'For "4g2" it should be false'); + expect(itemModel.isValid(42.3)).toBe(false, 'For 42.3 it should be false'); + expect(itemModel.isValid('42.3')).toBe(false, 'For "42.3" it should be false'); + expect(itemModel.isValid('test')).toBe(false, 'For "test" it should be false'); + }); +}); diff --git a/lib/core/card-view/models/card-view-intitem.model.ts b/lib/core/card-view/models/card-view-intitem.model.ts new file mode 100644 index 0000000000..09f38b6738 --- /dev/null +++ b/lib/core/card-view/models/card-view-intitem.model.ts @@ -0,0 +1,35 @@ +/*! + * @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 { CardViewItem } from '../interfaces/card-view-item.interface'; +import { DynamicComponentModel } from '../../services/dynamic-component-mapper.service'; +import { CardViewTextItemModel } from './card-view-textitem.model'; +import { CardViewTextItemProperties } from '../interfaces/card-view.interfaces'; +import { CardViewItemIntValidator } from '../validators/card-view.validators'; + +export class CardViewIntItemModel extends CardViewTextItemModel implements CardViewItem, DynamicComponentModel { + type: string = 'int'; + + constructor(obj: CardViewTextItemProperties) { + super(obj); + + this.validators.push(new CardViewItemIntValidator()); + if (obj.value) { + this.value = parseInt(obj.value, 10); + } + } +} diff --git a/lib/core/models/card-view-mapitem.model.ts b/lib/core/card-view/models/card-view-mapitem.model.ts similarity index 69% rename from lib/core/models/card-view-mapitem.model.ts rename to lib/core/card-view/models/card-view-mapitem.model.ts index 61fd04085d..5f0f52111f 100644 --- a/lib/core/models/card-view-mapitem.model.ts +++ b/lib/core/card-view/models/card-view-mapitem.model.ts @@ -15,26 +15,14 @@ * limitations under the License. */ -/** - * - * This object represent the basic structure of a card view. - * - * - * @returns {CardViewMapItemModel} . - */ - -import { CardViewItem } from '../interface/card-view-item.interface'; -import { DynamicComponentModel } from '../services/dynamic-component-mapper.service'; -import { CardViewBaseItemModel, CardViewItemProperties } from './card-view-baseitem.model'; +import { CardViewItem } from '../interfaces/card-view-item.interface'; +import { DynamicComponentModel } from '../../services/dynamic-component-mapper.service'; +import { CardViewBaseItemModel } from './card-view-baseitem.model'; export class CardViewMapItemModel extends CardViewBaseItemModel implements CardViewItem, DynamicComponentModel { type: string = 'map'; value: Map; - constructor(obj: CardViewItemProperties) { - super(obj); - } - get displayValue() { if (this.value && this.value.size > 0) { return this.value.values().next().value; diff --git a/lib/core/models/card-view-textitem.model.spec.ts b/lib/core/card-view/models/card-view-textitem.model.spec.ts similarity index 63% rename from lib/core/models/card-view-textitem.model.spec.ts rename to lib/core/card-view/models/card-view-textitem.model.spec.ts index 240dc17803..2fa78b6db7 100644 --- a/lib/core/models/card-view-textitem.model.spec.ts +++ b/lib/core/card-view/models/card-view-textitem.model.spec.ts @@ -16,7 +16,8 @@ */ import { PipeTransform } from '@angular/core'; -import { CardViewTextItemModel, CardViewTextItemProperties } from './card-view-textitem.model'; +import { CardViewTextItemModel } from './card-view-textitem.model'; +import { CardViewTextItemProperties } from '../interfaces/card-view.interfaces'; class TestPipe implements PipeTransform { transform(value: string, pipeParam: string): string { @@ -39,28 +40,36 @@ describe('CardViewTextItemModel', () => { describe('displayValue', () => { - it('should return the extension if file has it', () => { - const file = new CardViewTextItemModel(properties); + it('should return the value if it is present', () => { + const itemModel = new CardViewTextItemModel(properties); - expect(file.displayValue).toBe('Banuk'); + expect(itemModel.displayValue).toBe('Banuk'); + }); + + it('should return the default value if the value is not present', () => { + properties.value = undefined; + properties.default = 'default-value'; + const itemModel = new CardViewTextItemModel(properties); + + expect(itemModel.displayValue).toBe('default-value'); }); it('should apply a pipe on the value if it is present', () => { properties.pipes = [ { pipe: new TestPipe() } ]; - const file = new CardViewTextItemModel(properties); + const itemModel = new CardViewTextItemModel(properties); - expect(file.displayValue).toBe('testpiped-Banuk'); + expect(itemModel.displayValue).toBe('testpiped-Banuk'); }); it('should apply a pipe on the value with parameters if those are present', () => { properties.pipes = [ { pipe: new TestPipe(), params: ['withParams'] } ]; - const file = new CardViewTextItemModel(properties); + const itemModel = new CardViewTextItemModel(properties); - expect(file.displayValue).toBe('testpiped-Banuk-withParams'); + expect(itemModel.displayValue).toBe('testpiped-Banuk-withParams'); }); it('should apply more pipes on the value with parameters if those are present', () => { @@ -70,9 +79,9 @@ describe('CardViewTextItemModel', () => { { pipe, params: ['2'] }, { pipe, params: ['3'] } ]; - const file = new CardViewTextItemModel(properties); + const itemModel = new CardViewTextItemModel(properties); - expect(file.displayValue).toBe('testpiped-testpiped-testpiped-Banuk-1-2-3'); + expect(itemModel.displayValue).toBe('testpiped-testpiped-testpiped-Banuk-1-2-3'); }); }); }); diff --git a/lib/core/models/card-view-textitem.model.ts b/lib/core/card-view/models/card-view-textitem.model.ts similarity index 62% rename from lib/core/models/card-view-textitem.model.ts rename to lib/core/card-view/models/card-view-textitem.model.ts index 48eea977dc..a30dfcabbe 100644 --- a/lib/core/models/card-view-textitem.model.ts +++ b/lib/core/card-view/models/card-view-textitem.model.ts @@ -15,31 +15,15 @@ * limitations under the License. */ -/** - * - * This object represent the basic structure of a card view. - * - * - * @returns {CardViewTextItemModel} . - */ +import { CardViewItem } from '../interfaces/card-view-item.interface'; +import { DynamicComponentModel } from '../../services/dynamic-component-mapper.service'; +import { CardViewBaseItemModel } from './card-view-baseitem.model'; +import { CardViewTextItemPipeProperty, CardViewTextItemProperties } from '../interfaces/card-view.interfaces'; -import { PipeTransform } from '@angular/core'; -import { CardViewItem } from '../interface/card-view-item.interface'; -import { DynamicComponentModel } from '../services/dynamic-component-mapper.service'; -import { CardViewBaseItemModel, CardViewItemProperties } from './card-view-baseitem.model'; - -export interface CardViewTextItemPipeProperty { - pipe: PipeTransform; - params?: Array; -} -export interface CardViewTextItemProperties extends CardViewItemProperties { - multiline?: boolean; - pipes?: Array; -} export class CardViewTextItemModel extends CardViewBaseItemModel implements CardViewItem, DynamicComponentModel { type: string = 'text'; multiline?: boolean; - pipes?: Array; + pipes?: CardViewTextItemPipeProperty[]; constructor(obj: CardViewTextItemProperties) { super(obj); @@ -48,7 +32,11 @@ export class CardViewTextItemModel extends CardViewBaseItemModel implements Card } get displayValue() { - return this.applyPipes(this.value); + if (this.isEmpty()) { + return this.default; + } else { + return this.applyPipes(this.value); + } } private applyPipes(displayValue) { diff --git a/lib/core/card-view/models/card-view.models.ts b/lib/core/card-view/models/card-view.models.ts new file mode 100644 index 0000000000..0d16dc6552 --- /dev/null +++ b/lib/core/card-view/models/card-view.models.ts @@ -0,0 +1,25 @@ +/*! + * @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. + */ + +export * from './card-view-baseitem.model'; +export * from './card-view-boolitem.model'; +export * from './card-view-dateitem.model'; +export * from './card-view-datetimeitem.model'; +export * from './card-view-floatitem.model'; +export * from './card-view-intitem.model'; +export * from './card-view-mapitem.model'; +export * from './card-view-textitem.model'; diff --git a/lib/core/card-view/public-api.ts b/lib/core/card-view/public-api.ts index 14c9afc436..6d6b8d8bc0 100644 --- a/lib/core/card-view/public-api.ts +++ b/lib/core/card-view/public-api.ts @@ -15,9 +15,16 @@ * limitations under the License. */ -export * from './card-view-content-proxy.directive'; -export * from './card-view-dateitem.component'; -export * from './card-view-item-dispatcher.component'; -export * from './card-view-mapitem.component'; -export * from './card-view-textitem.component'; -export * from './card-view.component'; +export { + CardViewComponent, + CardViewBoolItemComponent, + CardViewDateItemComponent, + CardViewMapItemComponent, + CardViewTextItemComponent +} from './components/card-view.components'; + +export * from './interfaces/card-view.interfaces'; +export * from './validators/card-view.validators'; +export * from './models/card-view.models'; +export * from './services/card-view.services'; +export * from './directives/card-view-content-proxy.directive'; diff --git a/lib/core/services/card-item-types.service.ts b/lib/core/card-view/services/card-item-types.service.ts similarity index 59% rename from lib/core/services/card-item-types.service.ts rename to lib/core/card-view/services/card-item-types.service.ts index 2b7d7e1866..e237d44005 100644 --- a/lib/core/services/card-item-types.service.ts +++ b/lib/core/card-view/services/card-item-types.service.ts @@ -16,10 +16,11 @@ */ import { Injectable, Type } from '@angular/core'; -import { CardViewDateItemComponent } from '../card-view/card-view-dateitem.component'; -import { CardViewMapItemComponent } from '../card-view/card-view-mapitem.component'; -import { CardViewTextItemComponent } from '../card-view/card-view-textitem.component'; -import { DynamicComponentMapper, DynamicComponentResolveFunction, DynamicComponentResolver } from '../services/dynamic-component-mapper.service'; +import { CardViewDateItemComponent } from '../components/card-view-dateitem/card-view-dateitem.component'; +import { CardViewMapItemComponent } from '../components/card-view-mapitem/card-view-mapitem.component'; +import { CardViewTextItemComponent } from '../components/card-view-textitem/card-view-textitem.component'; +import { CardViewBoolItemComponent } from '../components/card-view-boolitem/card-view-boolitem.component'; +import { DynamicComponentMapper, DynamicComponentResolveFunction, DynamicComponentResolver } from '../../services/dynamic-component-mapper.service'; @Injectable() export class CardItemTypeService extends DynamicComponentMapper { @@ -28,7 +29,11 @@ export class CardItemTypeService extends DynamicComponentMapper { protected types: { [key: string]: DynamicComponentResolveFunction } = { 'text': DynamicComponentResolver.fromType(CardViewTextItemComponent), + 'int': DynamicComponentResolver.fromType(CardViewTextItemComponent), + 'float': DynamicComponentResolver.fromType(CardViewTextItemComponent), 'date': DynamicComponentResolver.fromType(CardViewDateItemComponent), + 'datetime': DynamicComponentResolver.fromType(CardViewDateItemComponent), + 'bool': DynamicComponentResolver.fromType(CardViewBoolItemComponent), 'map': DynamicComponentResolver.fromType(CardViewMapItemComponent) }; } diff --git a/lib/core/services/card-view-update.service.spec.ts b/lib/core/card-view/services/card-view-update.service.spec.ts similarity index 100% rename from lib/core/services/card-view-update.service.spec.ts rename to lib/core/card-view/services/card-view-update.service.spec.ts diff --git a/lib/core/services/card-view-update.service.ts b/lib/core/card-view/services/card-view-update.service.ts similarity index 100% rename from lib/core/services/card-view-update.service.ts rename to lib/core/card-view/services/card-view-update.service.ts diff --git a/lib/core/card-view/services/card-view.services.ts b/lib/core/card-view/services/card-view.services.ts new file mode 100644 index 0000000000..a64e2fa0b5 --- /dev/null +++ b/lib/core/card-view/services/card-view.services.ts @@ -0,0 +1,19 @@ +/*! + * @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. + */ + +export * from './card-item-types.service'; +export * from './card-view-update.service'; diff --git a/lib/core/card-view/validators/card-view-item-float.validator.ts b/lib/core/card-view/validators/card-view-item-float.validator.ts new file mode 100644 index 0000000000..e1fe97ffa5 --- /dev/null +++ b/lib/core/card-view/validators/card-view-item-float.validator.ts @@ -0,0 +1,27 @@ +/*! + * @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 { CardViewItemValidator } from '../interfaces/card-view.interfaces'; + +export class CardViewItemFloatValidator implements CardViewItemValidator { + + message = 'CORE.CARDVIEW.VALIDATORS.FLOAT_VALIDATION_ERROR'; + + isValid(value: any): boolean { + return !isNaN(parseFloat(value)) && isFinite(value); + } +} diff --git a/lib/core/card-view/validators/card-view-item-int.validator.ts b/lib/core/card-view/validators/card-view-item-int.validator.ts new file mode 100644 index 0000000000..962611551e --- /dev/null +++ b/lib/core/card-view/validators/card-view-item-int.validator.ts @@ -0,0 +1,27 @@ +/*! + * @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 { CardViewItemValidator } from '../interfaces/card-view.interfaces'; + +export class CardViewItemIntValidator implements CardViewItemValidator { + + message = 'CORE.CARDVIEW.VALIDATORS.INT_VALIDATION_ERROR'; + + isValid(value: any): boolean { + return !isNaN(value) && (function(x) { return (x | 0) === x; })(parseFloat(value)); + } +} diff --git a/lib/core/card-view/validators/card-view.validators.ts b/lib/core/card-view/validators/card-view.validators.ts new file mode 100644 index 0000000000..a924d5da02 --- /dev/null +++ b/lib/core/card-view/validators/card-view.validators.ts @@ -0,0 +1,19 @@ +/*! + * @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. + */ + +export * from './card-view-item-int.validator'; +export * from './card-view-item-float.validator'; diff --git a/lib/core/i18n/en.json b/lib/core/i18n/en.json index 8f1aa31427..3dac2d853c 100644 --- a/lib/core/i18n/en.json +++ b/lib/core/i18n/en.json @@ -83,8 +83,15 @@ "APPLY": "APPLY", "NOT_VALID": "http(s)://host|ip:port(/path) not recognized, try a different URL." }, + "CARDVIEW": { + "VALIDATORS": { + "FLOAT_VALIDATION_ERROR": "The value has to be number", + "INT_VALIDATION_ERROR": "The value has to be integer" + } + }, "METADATA": { "BASIC": { + "HEADER": "Properties", "NAME": "Name", "TITLE": "Title", "DESCRIPTION": "Description", diff --git a/lib/core/interface/public-api.ts b/lib/core/interface/public-api.ts index abc2977d14..ed6c8ab637 100644 --- a/lib/core/interface/public-api.ts +++ b/lib/core/interface/public-api.ts @@ -16,5 +16,4 @@ */ export * from './authentication.interface'; -export * from './card-view-item.interface'; export * from './injection.tokens'; diff --git a/lib/core/models/public-api.ts b/lib/core/models/public-api.ts index 3f85af65ce..26b5c93443 100644 --- a/lib/core/models/public-api.ts +++ b/lib/core/models/public-api.ts @@ -15,10 +15,6 @@ * limitations under the License. */ -export * from './card-view-baseitem.model'; -export * from './card-view-textitem.model'; -export * from './card-view-mapitem.model'; -export * from './card-view-dateitem.model'; export * from './file.model'; export * from './permissions.enum'; export * from './product-version.model'; diff --git a/lib/core/services/alfresco-api.service.ts b/lib/core/services/alfresco-api.service.ts index 3f739691d2..a16baa73d8 100644 --- a/lib/core/services/alfresco-api.service.ts +++ b/lib/core/services/alfresco-api.service.ts @@ -19,7 +19,7 @@ import { Injectable } from '@angular/core'; import { AlfrescoApi, ContentApi, FavoritesApi, NodesApi, PeopleApi, RenditionsApi, SharedlinksApi, SitesApi, - VersionsApi + VersionsApi, ClassesApi } from 'alfresco-js-api'; import * as alfrescoApi from 'alfresco-js-api'; import { AppConfigService } from '../app-config/app-config.service'; @@ -70,6 +70,10 @@ export class AlfrescoApiService { return this.getInstance().core.versionsApi; } + get classesApi(): ClassesApi { + return this.getInstance().core.classesApi; + } + constructor(private appConfig: AppConfigService, private storage: StorageService) { diff --git a/lib/core/services/public-api.ts b/lib/core/services/public-api.ts index 053cfdff7c..67cdea661e 100644 --- a/lib/core/services/public-api.ts +++ b/lib/core/services/public-api.ts @@ -34,8 +34,6 @@ export * from './translate-loader.service'; export * from './thumbnail.service'; export * from './upload.service'; export * from './dynamic-component-mapper.service'; -export * from './card-item-types.service'; -export * from './card-view-update.service'; export * from './user-preferences.service'; export * from './highlight-transform.service'; export * from './deleted-nodes-api.service'; diff --git a/lib/core/services/service.module.ts b/lib/core/services/service.module.ts index 2a60514026..7aa25ed88b 100644 --- a/lib/core/services/service.module.ts +++ b/lib/core/services/service.module.ts @@ -23,8 +23,6 @@ import { AuthGuardBpm } from './auth-guard-bpm.service'; import { AuthGuardEcm } from './auth-guard-ecm.service'; import { AuthGuard } from './auth-guard.service'; import { AuthenticationService } from './authentication.service'; -import { CardItemTypeService } from './card-item-types.service'; -import { CardViewUpdateService } from './card-view-update.service'; import { CommentProcessService } from './comment-process.service'; import { ContentService } from './content.service'; import { CookieService } from './cookie.service'; @@ -72,8 +70,6 @@ import { UserPreferencesService } from './user-preferences.service'; TranslateLoaderService, ThumbnailService, UploadService, - CardItemTypeService, - CardViewUpdateService, UserPreferencesService, HighlightTransformService, DeletedNodesApiService, diff --git a/lib/core/styles/_index.scss b/lib/core/styles/_index.scss index b477cf9f04..4aa223fae4 100644 --- a/lib/core/styles/_index.scss +++ b/lib/core/styles/_index.scss @@ -2,10 +2,7 @@ @import './default-class'; @import './theming'; - -@import '../card-view/card-view-dateitem.component'; -@import '../card-view/card-view-textitem.component'; -@import '../card-view/card-view.component'; +@import '../card-view/card-view.module'; @import '../collapsable/accordion-group.component'; @import '../datatable/components/datatable/datatable.component'; @import '../form/components/widgets/container/container.widget'; @@ -25,9 +22,7 @@ @mixin adf-core-theme($theme) { @include adf-colors-theme($theme); @include adf-default-class-theme($theme); - @include adf-card-view-dateitem-theme($theme); - @include adf-card-view-textitem-theme($theme); - @include adf-card-view-theme($theme); + @include adf-card-view-module-theme($theme); @include adf-accordion-theme($theme); @include adf-datatable-theme($theme); @include adf-form-container-widget-theme($theme); diff --git a/lib/ng-package/ng-package-core.json b/lib/ng-package/ng-package-core.json index 3320d01be5..a8c7d8a51f 100644 --- a/lib/ng-package/ng-package-core.json +++ b/lib/ng-package/ng-package-core.json @@ -11,6 +11,8 @@ "minimatch": "minimatch", "@angular/platform-browser/animations": "@angular/platform-browser/animations", "@angular/material": "@angular/material", + "@mat-datetimepicker/core": "@mat-datetimepicker/core", + "@mat-datetimepicker/moment": "@mat-datetimepicker/moment", "@angular/flex-layout": "@angular/flex-layout", "@angular/material-moment-adapter": "@angular/material-moment-adapter", "@angular/animations": "@angular/animations", diff --git a/lib/package.json b/lib/package.json index e8295db3d7..3a3299155e 100644 --- a/lib/package.json +++ b/lib/package.json @@ -62,6 +62,8 @@ "@angular/platform-browser": "5.1.1", "@angular/platform-browser-dynamic": "5.1.1", "@angular/router": "5.1.1", + "@mat-datetimepicker/core": "^1.0.1", + "@mat-datetimepicker/moment": "^1.0.1", "@ngx-translate/core": "8.0.0", "alfresco-js-api": "2.0.0", "chart.js": "2.5.0", diff --git a/lib/tsconfig.json b/lib/tsconfig.json index 0530c87b5a..c375ff0340 100644 --- a/lib/tsconfig.json +++ b/lib/tsconfig.json @@ -27,6 +27,7 @@ "@alfresco/adf-insights": ["./analytics"], "alfresco-js-api": ["./node_modules/alfresco-js-api/"], "@angular/*": ["./node_modules/@angular/*"], + "@mat-datetimepicker/*": ["./node_modules/@mat-datetimepicker/*"], "rxjs/*": ["./node_modules/rxjs/*"] }, "lib": [