diff --git a/demo-shell/src/app.config.json b/demo-shell/src/app.config.json index 166b43e018..45f3e6755d 100644 --- a/demo-shell/src/app.config.json +++ b/demo-shell/src/app.config.json @@ -819,6 +819,7 @@ "default": { "exif:exif": "*" }, + "all-aspects": "*", "custom-title": [{ "title": "Exif", "items": [ @@ -837,7 +838,8 @@ }] }, "multi-value-pipe-separator": ", ", - "multi-value-chips": true + "multi-value-chips": true, + "copy-to-clipboard-action": true }, "sideNav": { "expandedSidenav": true, diff --git a/docs/content-services/components/content-metadata-card.component.md b/docs/content-services/components/content-metadata-card.component.md index 23d04a2499..b896f7c99c 100644 --- a/docs/content-services/components/content-metadata-card.component.md +++ b/docs/content-services/components/content-metadata-card.component.md @@ -395,3 +395,22 @@ To customize the separator used by this card you can set it in your `app.config. If you want to display chips fo each value instead of a composed string you just need to enable it in the content-metadata config. ![Chips for multi value properties](../../docassets/images/metadata-chips.png) + +## Copy to Clipboard on click + +For easier interaction with metadata properties you can enable the Copy to Clipboard feature from your configuration in the `app.config.json` file. + +Once you have enabled this feature you will be able to double click on your metadata properties and they will get copied to your clipboard for you to use. + + ```json +"content-metadata": { + "presets": { + "default": { + "includeAll": true, + "exclude": "exif:exif", + "exif:exif": [ "exif:pixelXDimension", "exif:pixelYDimension"] + } + }, + "copy-to-clipboard-action": true +} +``` diff --git a/docs/core/components/card-view.component.md b/docs/core/components/card-view.component.md index 4ba5e20d7c..5e22eb01e4 100644 --- a/docs/core/components/card-view.component.md +++ b/docs/core/components/card-view.component.md @@ -109,6 +109,9 @@ Defining properties from Typescript: | Name | Type | Default value | Description | | ---- | ---- | ------------- | ----------- | | displayClearAction | `boolean` | true | Toggles whether or not to display clear action. | +| copyToClipboardAction | `boolean` | true | Toggles whether or not to enable copy to clipboard action. | +| useChipsForMultiValueProperty | `boolean` | true | Toggles whether or not to enable chips for multivalued properties. | +| multiValueSeparator | `string` | ',' | String separator between multi-value property items. | | displayEmpty | `boolean` | true | Toggles whether or not to show empty items in non-editable mode. | | displayNoneOption | `boolean` | true | Toggles whether or not to display none option. | | editable | `boolean` | | Toggles whether or not the items can be edited. | @@ -189,6 +192,9 @@ const textItemProperty = new CardViewTextItemModel(options); | editable | boolean | false | Toggles whether the item is editable | | clickable | boolean | false | Toggles whether the property responds to clicks | | clickableCallBack | function | null | Function to execute when click the element | +| copyToClipboardAction | `boolean` | true | Toggles whether or not to enable copy to clipboard action. | +| useChipsForMultiValueProperty | `boolean` | true | Toggles whether or not to enable chips for multivalued properties. | +| multiValueSeparator | `string` | ',' | String separator between multi-value property items. | | icon | string | | The material icon to show beside the item if it is clickable | | multiline | boolean | false | Single or multiline text | | pipes | [`CardViewTextItemPipeProperty`](../../../lib/core/card-view/interfaces/card-view-textitem-pipe-property.interface.ts)\[] | \[] | Pipes to be applied to the text before display | @@ -242,6 +248,7 @@ const dateItemProperty = new CardViewDateItemModel(options); | ---- | ---- | ------- | ----------- | | label\* | string | | Item label | | value\* | any | | The original data value for the item | +| copyToClipboardAction | `boolean` | true | Toggles whether or not to enable copy to clipboard action. | | key\* | string | | Identifying key (important when editing the item) | | default | any | | The default value to display if the value is empty | | displayValue\* | any | | The value to display | diff --git a/e2e/process-services-cloud/task-header-cloud.e2e.ts b/e2e/process-services-cloud/task-header-cloud.e2e.ts index 370c902f5f..92334582a8 100644 --- a/e2e/process-services-cloud/task-header-cloud.e2e.ts +++ b/e2e/process-services-cloud/task-header-cloud.e2e.ts @@ -34,6 +34,10 @@ import { TasksCloudDemoPage } from '../pages/adf/demo-shell/process-services/tas import { NavigationBarPage } from '../pages/adf/navigation-bar.page'; import moment = require('moment'); +const isValueInvalid = (value: any): boolean => { + return value === null || value === undefined; +}; + describe('Task Header cloud component', () => { const basicCreatedTaskName = StringUtil.generateRandomString(); const completedTaskName = StringUtil.generateRandomString(); @@ -127,16 +131,16 @@ describe('Task Header cloud component', () => { await tasksCloudDemoPage.taskListCloudComponent().selectRow(basicCreatedTaskName); await expect(await taskHeaderCloudPage.getId()).toEqual(basicCreatedTask.entry.id); await expect(await taskHeaderCloudPage.getDescription()) - .toEqual(basicCreatedTask.entry.description === null ? CONSTANTS.TASK_DETAILS.NO_DESCRIPTION : basicCreatedTask.entry.description); + .toEqual(isValueInvalid(basicCreatedTask.entry.description) ? CONSTANTS.TASK_DETAILS.NO_DESCRIPTION : basicCreatedTask.entry.description); await expect(await taskHeaderCloudPage.getStatus()).toEqual(basicCreatedTask.entry.status); await expect(await taskHeaderCloudPage.getPriority()).toEqual(basicCreatedTask.entry.priority.toString()); await expect(await taskHeaderCloudPage.getCategory()).toEqual(!basicCreatedTask.entry.category ? CONSTANTS.TASK_DETAILS.NO_CATEGORY : basicCreatedTask.entry.category); - await expect(await taskHeaderCloudPage.getDueDate()).toEqual(basicCreatedTask.entry.dueDate === null ? + await expect(await taskHeaderCloudPage.getDueDate()).toEqual(isValueInvalid(basicCreatedTask.entry.dueDate) ? CONSTANTS.TASK_DETAILS.NO_DATE : basicCreatedDate); await expect(await taskHeaderCloudPage.getEndDate()).toEqual(''); await expect(await taskHeaderCloudPage.getCreated()).toEqual(basicCreatedDate); - await expect(await taskHeaderCloudPage.getAssignee()).toEqual(basicCreatedTask.entry.assignee === null ? '' : basicCreatedTask.entry.assignee); + await expect(await taskHeaderCloudPage.getAssignee()).toEqual(isValueInvalid(basicCreatedTask.entry.assignee) ? '' : basicCreatedTask.entry.assignee); await expect(await taskHeaderCloudPage.getParentName()).toEqual(CONSTANTS.TASK_DETAILS.NO_PARENT); }); @@ -146,16 +150,16 @@ describe('Task Header cloud component', () => { await tasksCloudDemoPage.taskListCloudComponent().selectRow(completedTaskName); await expect(await taskHeaderCloudPage.getId()).toEqual(completedTask.entry.id); await expect(await taskHeaderCloudPage.getDescription()) - .toEqual(completedTask.entry.description === null ? CONSTANTS.TASK_DETAILS.NO_DESCRIPTION : completedTask.entry.description); + .toEqual(isValueInvalid(completedTask.entry.description) ? CONSTANTS.TASK_DETAILS.NO_DESCRIPTION : completedTask.entry.description); await expect(await taskHeaderCloudPage.getStatus()).toEqual(completedTask.entry.status); await expect(await taskHeaderCloudPage.getPriority()).toEqual(completedTask.entry.priority.toString()); await expect(await taskHeaderCloudPage.getCategory()).toEqual(!completedTask.entry.category ? CONSTANTS.TASK_DETAILS.NO_CATEGORY : completedTask.entry.category); - await expect(await taskHeaderCloudPage.getDueDate()).toEqual(completedTask.entry.dueDate === null ? + await expect(await taskHeaderCloudPage.getDueDate()).toEqual(isValueInvalid(completedTask.entry.dueDate) ? CONSTANTS.TASK_DETAILS.NO_DATE : dueDate); await expect(await taskHeaderCloudPage.getEndDate()).toEqual(completedEndDate); await expect(await taskHeaderCloudPage.getCreated()).toEqual(completedCreatedDate); - await expect(await taskHeaderCloudPage.getAssignee()).toEqual(completedTask.entry.assignee === null ? '' : completedTask.entry.assignee); + await expect(await taskHeaderCloudPage.getAssignee()).toEqual(isValueInvalid(completedTask.entry.assignee) ? '' : completedTask.entry.assignee); await expect(await taskHeaderCloudPage.getParentName()).toEqual(CONSTANTS.TASK_DETAILS.NO_PARENT); }); @@ -165,19 +169,18 @@ describe('Task Header cloud component', () => { await tasksCloudDemoPage.taskListCloudComponent().selectRow(subTask.entry.name); await expect(await taskHeaderCloudPage.getId()).toEqual(subTask.entry.id); await expect(await taskHeaderCloudPage.getDescription()) - .toEqual(subTask.entry.description === null ? CONSTANTS.TASK_DETAILS.NO_DESCRIPTION : subTask.entry.description); + .toEqual(isValueInvalid(subTask.entry.description) ? CONSTANTS.TASK_DETAILS.NO_DESCRIPTION : subTask.entry.description); await expect(await taskHeaderCloudPage.getStatus()).toEqual(subTask.entry.status); await expect(await taskHeaderCloudPage.getPriority()).toEqual(subTask.entry.priority.toString()); await expect(await taskHeaderCloudPage.getCategory()).toEqual(!subTask.entry.category ? CONSTANTS.TASK_DETAILS.NO_CATEGORY : subTask.entry.category); - await expect(await taskHeaderCloudPage.getDueDate()).toEqual(subTask.entry.dueDate === null ? - CONSTANTS.TASK_DETAILS.NO_DATE : subTaskCreatedDate); + await expect(await taskHeaderCloudPage.getDueDate()).toEqual(isValueInvalid(subTask.entry.dueDate) ? CONSTANTS.TASK_DETAILS.NO_DATE : subTaskCreatedDate); await expect(await taskHeaderCloudPage.getEndDate()).toEqual(''); await expect(await taskHeaderCloudPage.getCreated()).toEqual(subTaskCreatedDate); - await expect(await taskHeaderCloudPage.getAssignee()).toEqual(subTask.entry.assignee === null ? '' : subTask.entry.assignee); + await expect(await taskHeaderCloudPage.getAssignee()).toEqual(isValueInvalid(subTask.entry.assignee) ? '' : subTask.entry.assignee); await expect(await taskHeaderCloudPage.getParentName()).toEqual(basicCreatedTask.entry.name); await expect(await taskHeaderCloudPage.getParentTaskId()) - .toEqual(subTask.entry.parentTaskId === null ? '' : subTask.entry.parentTaskId); + .toEqual(isValueInvalid(subTask.entry.parentTaskId) ? '' : subTask.entry.parentTaskId); }); it('[C309698] - Should validate the Priority field', async () => { diff --git a/lib/content-services/src/lib/content-metadata/components/content-metadata/content-metadata.component.html b/lib/content-services/src/lib/content-metadata/components/content-metadata/content-metadata.component.html index 550f78c295..b813ba75b5 100644 --- a/lib/content-services/src/lib/content-metadata/components/content-metadata/content-metadata.component.html +++ b/lib/content-services/src/lib/content-metadata/components/content-metadata/content-metadata.component.html @@ -14,7 +14,10 @@ (keydown)="keyDown($event)" [properties]="basicProperties$ | async" [editable]="editable" - [displayEmpty]="displayEmpty"> + [displayEmpty]="displayEmpty" + [copyToClipboardAction]="copyToClipboardAction" + [useChipsForMultiValueProperty]="useChipsForMultiValueProperty" + [multiValueSeparator]="multiValueSeparator"> @@ -34,7 +37,10 @@ (keydown)="keyDown($event)" [properties]="group.properties" [editable]="editable" - [displayEmpty]="displayEmpty"> + [displayEmpty]="displayEmpty" + [copyToClipboardAction]="copyToClipboardAction" + [useChipsForMultiValueProperty]="useChipsForMultiValueProperty" + [multiValueSeparator]="multiValueSeparator"> diff --git a/lib/content-services/src/lib/content-metadata/components/content-metadata/content-metadata.component.ts b/lib/content-services/src/lib/content-metadata/components/content-metadata/content-metadata.component.ts index 007c3b2e19..b518cdf5c1 100644 --- a/lib/content-services/src/lib/content-metadata/components/content-metadata/content-metadata.component.ts +++ b/lib/content-services/src/lib/content-metadata/components/content-metadata/content-metadata.component.ts @@ -24,7 +24,8 @@ import { LogService, CardViewUpdateService, AlfrescoApiService, - TranslationService + TranslationService, + AppConfigService } from '@alfresco/adf-core'; import { ContentMetadataService } from '../../services/content-metadata.service'; import { CardViewGroup } from '../../interfaces/content-metadata.interfaces'; @@ -39,6 +40,8 @@ import { switchMap, takeUntil, catchError } from 'rxjs/operators'; }) export class ContentMetadataComponent implements OnChanges, OnInit, OnDestroy { + static DEFAULT_SEPARATOR = ', '; + protected onDestroy$ = new Subject(); /** (required) The node entity to fetch metadata about */ @@ -75,6 +78,15 @@ export class ContentMetadataComponent implements OnChanges, OnInit, OnDestroy { @Input() displayAspect: string = null; + /** Toggles whether or not to enable copy to clipboard action. */ + @Input() + copyToClipboardAction: boolean = true; + + /** Toggles whether or not to enable chips for multivalued properties. */ + @Input() + useChipsForMultiValueProperty: boolean = true; + + multiValueSeparator: string; basicProperties$: Observable; groupedProperties$: Observable; @@ -84,8 +96,12 @@ export class ContentMetadataComponent implements OnChanges, OnInit, OnDestroy { private nodesApiService: NodesApiService, private logService: LogService, private alfrescoApiService: AlfrescoApiService, - private translationService: TranslationService + private translationService: TranslationService, + private appConfig: AppConfigService ) { + this.copyToClipboardAction = this.appConfig.get('content-metadata.copy-to-clipboard-action'); + this.multiValueSeparator = this.appConfig.get('content-metadata.multi-value-pipe-separator') || ContentMetadataComponent.DEFAULT_SEPARATOR; + this.useChipsForMultiValueProperty = this.appConfig.get('content-metadata.multi-value-chips'); } ngOnInit() { diff --git a/lib/core/card-view/components/card-view-item-dispatcher/card-view-item-dispatcher.component.ts b/lib/core/card-view/components/card-view-item-dispatcher/card-view-item-dispatcher.component.ts index 4955ba7416..a01c69242d 100644 --- a/lib/core/card-view/components/card-view-item-dispatcher/card-view-item-dispatcher.component.ts +++ b/lib/core/card-view/components/card-view-item-dispatcher/card-view-item-dispatcher.component.ts @@ -27,6 +27,7 @@ import { 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 { DEFAULT_SEPARATOR } from '../card-view-textitem/card-view-textitem.component'; @Component({ selector: 'adf-card-view-item-dispatcher', @@ -48,6 +49,15 @@ export class CardViewItemDispatcherComponent implements OnChanges { @Input() displayClearAction: boolean = true; + @Input() + copyToClipboardAction: boolean = true; + + @Input() + useChipsForMultiValueProperty: boolean = true; + + @Input() + multiValueSeparator: string = DEFAULT_SEPARATOR; + @ViewChild(CardViewContentProxyDirective) private content: CardViewContentProxyDirective; @@ -100,6 +110,9 @@ export class CardViewItemDispatcherComponent implements OnChanges { this.componentReference.instance.displayEmpty = this.displayEmpty; this.componentReference.instance.displayNoneOption = this.displayNoneOption; this.componentReference.instance.displayClearAction = this.displayClearAction; + this.componentReference.instance.copyToClipboardAction = this.copyToClipboardAction; + this.componentReference.instance.useChipsForMultiValueProperty = this.useChipsForMultiValueProperty; + this.componentReference.instance.multiValueSeparator = this.multiValueSeparator; } private proxy(methodName, ...args) { 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 index 180efab41d..b8b69c5c69 100644 --- 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 @@ -6,7 +6,12 @@ - + {{ property.displayValue }} + + implements OnChanges { - static DEFAULT_SEPARATOR = ', '; - static DEFAULT_USE_CHIPS = false; - @Input() editable: boolean = false; @Input() displayEmpty: boolean = true; + @Input() + copyToClipboardAction: boolean = true; + + @Input() + useChipsForMultiValueProperty: boolean = true; + + @Input() + multiValueSeparator: string = DEFAULT_SEPARATOR; + @ViewChild('editorInput') private editorInput: any; inEdit: boolean = false; editedValue: string | string[]; errorMessages: string[]; - valueSeparator: string; - useChipsForMultiValueProperty: boolean; constructor(cardViewUpdateService: CardViewUpdateService, - private appConfig: AppConfigService, private clipboardService: ClipboardService, private translateService: TranslationService) { super(cardViewUpdateService); - this.valueSeparator = this.appConfig.get('content-metadata.multi-value-pipe-separator') || CardViewTextItemComponent.DEFAULT_SEPARATOR; - this.useChipsForMultiValueProperty = this.appConfig.get('content-metadata.multi-value-chips') || CardViewTextItemComponent.DEFAULT_USE_CHIPS; } ngOnChanges(): void { @@ -131,7 +133,7 @@ export class CardViewTextItemComponent extends BaseCardView item.trim()); + const listOfValues = value.split(this.multiValueSeparator.trim()).map((item) => item.trim()); return listOfValues; } return value; 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 index eebbed91c9..b21b3d656f 100644 --- a/lib/core/card-view/components/card-view/card-view.component.html +++ b/lib/core/card-view/components/card-view/card-view.component.html @@ -6,7 +6,10 @@ [editable]="editable" [displayEmpty]="displayEmpty" [displayNoneOption]="displayNoneOption" - [displayClearAction]="displayClearAction"> + [displayClearAction]="displayClearAction" + [copyToClipboardAction]="copyToClipboardAction" + [useChipsForMultiValueProperty]="useChipsForMultiValueProperty" + [multiValueSeparator]="multiValueSeparator"> diff --git a/lib/core/card-view/components/card-view/card-view.component.ts b/lib/core/card-view/components/card-view/card-view.component.ts index 2199e627ed..8254ffe00e 100644 --- a/lib/core/card-view/components/card-view/card-view.component.ts +++ b/lib/core/card-view/components/card-view/card-view.component.ts @@ -17,6 +17,7 @@ import { Component, Input } from '@angular/core'; import { CardViewItem } from '../../interfaces/card-view-item.interface'; +import { DEFAULT_SEPARATOR } from '../card-view-textitem/card-view-textitem.component'; @Component({ selector: 'adf-card-view', @@ -43,4 +44,16 @@ export class CardViewComponent { /** Toggles whether or not to display clear action. */ @Input() displayClearAction: boolean = true; + + /** Toggles whether or not to enable copy to clipboard action. */ + @Input() + copyToClipboardAction: boolean = true; + + /** Toggles whether or not to enable chips for multivalued properties. */ + @Input() + useChipsForMultiValueProperty: boolean = true; + + /** String separator between multi-value property items. */ + @Input() + multiValueSeparator: string = DEFAULT_SEPARATOR; }