mirror of
https://github.com/Alfresco/alfresco-ng2-components.git
synced 2025-10-08 14:51:32 +00:00
[ACS-8001] content enrichment menu component
This commit is contained in:
committed by
Mykyta Maliarchuk
parent
548592b329
commit
a1cbcfca33
3
lib/core/src/lib/assets/images/ai-sparkles.svg
Normal file
3
lib/core/src/lib/assets/images/ai-sparkles.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M19 8.99987L17.75 6.24987L15 4.99987L17.75 3.74987L19 0.999873L20.25 3.74987L23 4.99987L20.25 6.24987L19 8.99987ZM19 22.9999L17.75 20.2499L15 18.9999L17.75 17.7499L19 14.9999L20.25 17.7499L23 18.9999L20.25 20.2499L19 22.9999ZM8.99998 19.9999L6.49998 14.4999L0.999984 11.9999L6.49998 9.49987L8.99998 3.99987L11.5 9.49987L17 11.9999L11.5 14.4999L8.99998 19.9999ZM8.99998 15.1499L9.99998 12.9999L12.15 11.9999L9.99998 10.9999L8.99998 8.84987L7.99998 10.9999L5.84998 11.9999L7.99998 12.9999L8.99998 15.1499Z" fill="#212328" fill-opacity="0.7"/>
|
||||
</svg>
|
After Width: | Height: | Size: 653 B |
@@ -43,6 +43,7 @@ import { CardViewKeyValuePairsItemComponent } from './components/card-view-keyva
|
||||
import { CardViewSelectItemComponent } from './components/card-view-selectitem/card-view-selectitem.component';
|
||||
import { CardViewArrayItemComponent } from './components/card-view-arrayitem/card-view-arrayitem.component';
|
||||
import { SelectFilterInputComponent } from './components/card-view-selectitem/select-filter-input/select-filter-input.component';
|
||||
import { ContentEnrichmentMenuComponent } from '../prediction/components/content-enrichment-menu/content-enrichment-menu.component';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
@@ -63,7 +64,8 @@ import { SelectFilterInputComponent } from './components/card-view-selectitem/se
|
||||
MatCardModule,
|
||||
MatDatetimepickerModule,
|
||||
MatNativeDatetimeModule,
|
||||
MatSlideToggleModule
|
||||
MatSlideToggleModule,
|
||||
ContentEnrichmentMenuComponent
|
||||
],
|
||||
declarations: [
|
||||
CardViewComponent,
|
||||
|
@@ -40,6 +40,18 @@ export abstract class BaseCardView<T extends CardViewItem> implements OnDestroy
|
||||
this.property.value = itemModel.value;
|
||||
}
|
||||
});
|
||||
|
||||
this.cardViewUpdateService.predictionStatusChanged$.pipe(takeUntil(this.destroy$)).subscribe((items) => {
|
||||
items.map(item => {
|
||||
if (this.property.key.split('.')[1] === item.key) {
|
||||
this.property.prediction = null;
|
||||
|
||||
if (item.previousValue !== null && item.previousValue !== undefined) {
|
||||
this.property.value = item.previousValue;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
get isEditable(): boolean {
|
||||
|
@@ -1,13 +1,16 @@
|
||||
<ng-container *ngIf="!property.isEmpty() || isEditable">
|
||||
<div class="adf-property-value">
|
||||
<mat-checkbox [attr.data-automation-id]="'card-boolean-' + property.key"
|
||||
[attr.title]="'CORE.METADATA.ACTIONS.TOGGLE' | translate"
|
||||
[checked]="property.displayValue"
|
||||
[disabled]="!isEditable"
|
||||
color="primary"
|
||||
(change)="changed($event)">
|
||||
<div [attr.data-automation-id]="'card-boolean-label-' + property.key"
|
||||
class="adf-property-label">{{ property.label | translate }}</div>
|
||||
</mat-checkbox>
|
||||
<div class="adf-property-value-wrapper">
|
||||
<adf-content-enrichment-menu matPrefix *ngIf="hasContentEnrichment" [prediction]="property.prediction"></adf-content-enrichment-menu>
|
||||
<div class="adf-property-value">
|
||||
<mat-checkbox [attr.data-automation-id]="'card-boolean-' + property.key"
|
||||
[attr.title]="'CORE.METADATA.ACTIONS.TOGGLE' | translate"
|
||||
[checked]="property.displayValue"
|
||||
[disabled]="!isEditable"
|
||||
color="primary"
|
||||
(change)="changed($event)">
|
||||
<div [attr.data-automation-id]="'card-boolean-label-' + property.key"
|
||||
class="adf-property-label">{{ property.label | translate }}</div>
|
||||
</mat-checkbox>
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
@@ -25,10 +25,12 @@ import { BaseCardView } from '../base-card-view';
|
||||
templateUrl: './card-view-boolitem.component.html',
|
||||
styles: [
|
||||
`
|
||||
.adf-property-value {
|
||||
padding: 15px 0;
|
||||
}
|
||||
`
|
||||
.adf-property-value {
|
||||
padding: 15px 0;
|
||||
display: flex;
|
||||
align-content: center;
|
||||
}
|
||||
`
|
||||
]
|
||||
})
|
||||
|
||||
@@ -36,6 +38,9 @@ export class CardViewBoolItemComponent extends BaseCardView<CardViewBoolItemMode
|
||||
@Input()
|
||||
editable: boolean;
|
||||
|
||||
@Input()
|
||||
hasContentEnrichment = false;
|
||||
|
||||
changed(change: MatCheckboxChange) {
|
||||
this.cardViewUpdateService.update({ ...this.property } as CardViewBoolItemModel, change.checked );
|
||||
this.property.value = change.checked;
|
||||
|
@@ -8,108 +8,62 @@
|
||||
>
|
||||
{{ property.label | translate }}
|
||||
</label>
|
||||
<div class="adf-property-value" [ngClass]="{ 'adf-property-value-editable': editable, 'adf-property-readonly-value': isReadonlyProperty }">
|
||||
<span *ngIf="!isEditable && !property.multivalued" [attr.data-automation-id]="'card-' + property.type + '-value-' + property.key">
|
||||
<span
|
||||
*ngIf="showProperty"
|
||||
[attr.data-automation-id]="'card-dateitem-' + property.key"
|
||||
(dblclick)="copyToClipboard(property.displayValue)"
|
||||
matTooltipShowDelay="1000"
|
||||
[title]="'CORE.METADATA.ACTIONS.COPY_TO_CLIPBOARD' | translate"
|
||||
>{{ property.displayValue }}</span
|
||||
>
|
||||
</span>
|
||||
<div *ngIf="isEditable && !property.multivalued" class="adf-dateitem-editable">
|
||||
<div class="adf-dateitem-editable-controls">
|
||||
<div class="adf-property-value-wrapper">
|
||||
<adf-content-enrichment-menu matPrefix *ngIf="hasContentEnrichment" [prediction]="property.prediction"></adf-content-enrichment-menu>
|
||||
<div class="adf-property-value" [ngClass]="{ 'adf-property-value-editable': editable, 'adf-property-readonly-value': isReadonlyProperty }">
|
||||
<span *ngIf="!isEditable && !property.multivalued" [attr.data-automation-id]="'card-' + property.type + '-value-' + property.key">
|
||||
<span
|
||||
class="adf-datepicker-toggle"
|
||||
[attr.data-automation-id]="'datepicker-label-toggle-' + property.key"
|
||||
(click)="showDatePicker()"
|
||||
tabindex="0"
|
||||
role="button"
|
||||
(keyup.enter)="showDatePicker()"
|
||||
*ngIf="showProperty"
|
||||
[attr.data-automation-id]="'card-dateitem-' + property.key"
|
||||
(dblclick)="copyToClipboard(property.displayValue)"
|
||||
matTooltipShowDelay="1000"
|
||||
[title]="'CORE.METADATA.ACTIONS.COPY_TO_CLIPBOARD' | translate"
|
||||
>{{ property.displayValue }}</span
|
||||
>
|
||||
<span *ngIf="showProperty; else elseEmptyValueBlock" [attr.data-automation-id]="'card-' + property.type + '-value-' + property.key">
|
||||
{{ property.displayValue }}</span
|
||||
</span>
|
||||
<div *ngIf="isEditable && !property.multivalued" class="adf-dateitem-editable">
|
||||
<div class="adf-dateitem-editable-controls">
|
||||
<span
|
||||
class="adf-datepicker-toggle"
|
||||
[attr.data-automation-id]="'datepicker-label-toggle-' + property.key"
|
||||
(click)="showDatePicker()"
|
||||
tabindex="0"
|
||||
role="button"
|
||||
(keyup.enter)="showDatePicker()"
|
||||
>
|
||||
</span>
|
||||
<span *ngIf="showProperty; else elseEmptyValueBlock" [attr.data-automation-id]="'card-' + property.type + '-value-' + property.key">
|
||||
{{ property.displayValue }}</span
|
||||
>
|
||||
</span>
|
||||
|
||||
<mat-icon
|
||||
*ngIf="showClearAction"
|
||||
class="adf-date-reset-icon"
|
||||
(click)="onDateClear()"
|
||||
[attr.title]="'CORE.METADATA.ACTIONS.CLEAR' | translate"
|
||||
[attr.data-automation-id]="'datepicker-date-clear-' + property.key"
|
||||
>
|
||||
clear
|
||||
</mat-icon>
|
||||
<mat-icon
|
||||
*ngIf="showClearAction"
|
||||
class="adf-date-reset-icon"
|
||||
(click)="onDateClear()"
|
||||
[attr.title]="'CORE.METADATA.ACTIONS.CLEAR' | translate"
|
||||
[attr.data-automation-id]="'datepicker-date-clear-' + property.key"
|
||||
>
|
||||
clear
|
||||
</mat-icon>
|
||||
|
||||
<mat-datetimepicker-toggle
|
||||
[attr.tabindex]="-1"
|
||||
[attr.title]="'CORE.METADATA.ACTIONS.EDIT' | translate"
|
||||
[attr.data-automation-id]="'datepickertoggle-' + property.key"
|
||||
[for]="datetimePicker"
|
||||
>
|
||||
</mat-datetimepicker-toggle>
|
||||
</div>
|
||||
<mat-datetimepicker-toggle
|
||||
[attr.tabindex]="-1"
|
||||
[attr.title]="'CORE.METADATA.ACTIONS.EDIT' | translate"
|
||||
[attr.data-automation-id]="'datepickertoggle-' + property.key"
|
||||
[for]="datetimePicker"
|
||||
>
|
||||
</mat-datetimepicker-toggle>
|
||||
</div>
|
||||
|
||||
<input
|
||||
class="adf-invisible-date-input"
|
||||
[attr.tabIndex]="-1"
|
||||
[matDatetimepicker]="datetimePicker"
|
||||
[value]="valueDate"
|
||||
(dateChange)="onDateChanged($event)"
|
||||
[attr.id]="'card-view-dateitem-' + property.key"
|
||||
/>
|
||||
|
||||
<mat-datetimepicker
|
||||
#datetimePicker
|
||||
[type]="$any(property).type"
|
||||
[timeInterval]="5"
|
||||
[attr.data-automation-id]="'datepicker-' + property.key"
|
||||
[startAt]="valueDate"
|
||||
>
|
||||
</mat-datetimepicker>
|
||||
</div>
|
||||
<ng-template #elseEmptyValueBlock>
|
||||
{{ property.default | translate }}
|
||||
</ng-template>
|
||||
|
||||
<div *ngIf="property.multivalued"
|
||||
class="adf-property-field adf-dateitem-chip-list-container adf-dateitem-editable">
|
||||
<mat-chip-listbox #chipList class="adf-textitem-chip-list">
|
||||
<mat-chip-option
|
||||
*ngFor="let propertyValue of property.displayValue; let idx = index"
|
||||
[removable]="isEditable"
|
||||
(removed)="removeValueFromList(idx)">
|
||||
{{ propertyValue }}
|
||||
<mat-icon *ngIf="isEditable" matChipRemove>cancel</mat-icon>
|
||||
</mat-chip-option>
|
||||
</mat-chip-listbox>
|
||||
|
||||
<div
|
||||
*ngIf="isEditable"
|
||||
class="adf-property-field adf-dateitem-editable-controls"
|
||||
(click)="showDatePicker()"
|
||||
tabindex="0"
|
||||
role="button"
|
||||
(keyup.enter)="showDatePicker()"
|
||||
>
|
||||
<input
|
||||
class="adf-invisible-date-input"
|
||||
[attr.tabIndex]="-1"
|
||||
[matDatetimepicker]="datetimePicker"
|
||||
(dateChange)="addDateToList($event)"
|
||||
[value]="valueDate"
|
||||
(dateChange)="onDateChanged($event)"
|
||||
[attr.id]="'card-view-dateitem-' + property.key"
|
||||
/>
|
||||
<mat-datetimepicker-toggle
|
||||
[attr.tabindex]="-1"
|
||||
matSuffix
|
||||
[attr.title]="'CORE.METADATA.ACTIONS.EDIT' | translate"
|
||||
[attr.data-automation-id]="'datepickertoggle-' + property.key"
|
||||
[for]="datetimePicker"
|
||||
>
|
||||
</mat-datetimepicker-toggle>
|
||||
|
||||
<mat-datetimepicker
|
||||
#datetimePicker
|
||||
[type]="$any(property).type"
|
||||
@@ -119,5 +73,54 @@
|
||||
>
|
||||
</mat-datetimepicker>
|
||||
</div>
|
||||
<ng-template #elseEmptyValueBlock>
|
||||
{{ property.default | translate }}
|
||||
</ng-template>
|
||||
|
||||
<div *ngIf="property.multivalued" class="adf-property-field adf-dateitem-chip-list-container adf-dateitem-editable">
|
||||
<mat-chip-list #chipList class="adf-textitem-chip-list">
|
||||
<mat-chip
|
||||
*ngFor="let propertyValue of property.displayValue; let idx = index"
|
||||
[removable]="isEditable"
|
||||
(removed)="removeValueFromList(idx)"
|
||||
>
|
||||
{{ propertyValue }}
|
||||
<mat-icon *ngIf="isEditable" matChipRemove>cancel</mat-icon>
|
||||
</mat-chip>
|
||||
</mat-chip-list>
|
||||
|
||||
<div
|
||||
*ngIf="isEditable"
|
||||
class="adf-property-field adf-dateitem-editable-controls"
|
||||
(click)="showDatePicker()"
|
||||
tabindex="0"
|
||||
role="button"
|
||||
(keyup.enter)="showDatePicker()"
|
||||
>
|
||||
<input
|
||||
class="adf-invisible-date-input"
|
||||
[attr.tabIndex]="-1"
|
||||
[matDatetimepicker]="datetimePicker"
|
||||
(dateChange)="addDateToList($event)"
|
||||
[attr.id]="'card-view-dateitem-' + property.key"
|
||||
/>
|
||||
<mat-datetimepicker-toggle
|
||||
[attr.tabindex]="-1"
|
||||
matSuffix
|
||||
[attr.title]="'CORE.METADATA.ACTIONS.EDIT' | translate"
|
||||
[attr.data-automation-id]="'datepickertoggle-' + property.key"
|
||||
[for]="datetimePicker"
|
||||
>
|
||||
</mat-datetimepicker-toggle>
|
||||
<mat-datetimepicker
|
||||
#datetimePicker
|
||||
[type]="$any(property).type"
|
||||
[timeInterval]="5"
|
||||
[attr.data-automation-id]="'datepicker-' + property.key"
|
||||
[startAt]="valueDate"
|
||||
>
|
||||
</mat-datetimepicker>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -49,6 +49,9 @@ export class CardViewDateItemComponent extends BaseCardView<CardViewDateItemMode
|
||||
@Input()
|
||||
displayClearAction = true;
|
||||
|
||||
@Input()
|
||||
hasContentEnrichment = false;
|
||||
|
||||
@ViewChild('datetimePicker')
|
||||
public datepicker: MatDatetimepickerComponent<any>;
|
||||
|
||||
|
@@ -32,27 +32,30 @@ export class CardViewItemDispatcherComponent implements OnChanges {
|
||||
editable: boolean;
|
||||
|
||||
@Input()
|
||||
displayEmpty: boolean = true;
|
||||
displayEmpty = true;
|
||||
|
||||
@Input()
|
||||
displayNoneOption: boolean = true;
|
||||
displayNoneOption = true;
|
||||
|
||||
@Input()
|
||||
displayClearAction: boolean = true;
|
||||
displayClearAction = true;
|
||||
|
||||
@Input()
|
||||
copyToClipboardAction: boolean = true;
|
||||
copyToClipboardAction = true;
|
||||
|
||||
@Input()
|
||||
useChipsForMultiValueProperty: boolean = true;
|
||||
useChipsForMultiValueProperty = true;
|
||||
|
||||
@Input()
|
||||
multiValueSeparator: string = DEFAULT_SEPARATOR;
|
||||
multiValueSeparator = DEFAULT_SEPARATOR;
|
||||
|
||||
@Input()
|
||||
displayLabelForChips: boolean = false;
|
||||
displayLabelForChips = false;
|
||||
|
||||
private loaded: boolean = false;
|
||||
@Input()
|
||||
hasContentEnrichment = false;
|
||||
|
||||
private loaded = false;
|
||||
private componentReference: any = null;
|
||||
|
||||
public ngOnInit;
|
||||
@@ -104,6 +107,7 @@ export class CardViewItemDispatcherComponent implements OnChanges {
|
||||
this.componentReference.instance.useChipsForMultiValueProperty = this.useChipsForMultiValueProperty;
|
||||
this.componentReference.instance.multiValueSeparator = this.multiValueSeparator;
|
||||
this.componentReference.instance.displayLabelForChips = this.displayLabelForChips;
|
||||
this.componentReference.instance.hasContentEnrichment = this.hasContentEnrichment;
|
||||
}
|
||||
|
||||
private proxy(methodName, ...args) {
|
||||
|
@@ -12,9 +12,12 @@
|
||||
*ngIf="!isEditable"
|
||||
class="adf-property-value adf-property-read-only"
|
||||
[attr.data-automation-id]="'select-readonly-value-' + property.key"
|
||||
data-automation-class="read-only-value">{{ (property.displayValue | async) | translate }}
|
||||
data-automation-class="read-only-value">
|
||||
<adf-content-enrichment-menu *ngIf="hasContentEnrichment" [prediction]="property.prediction" class="adf-content-enrichment-trigger"></adf-content-enrichment-menu>
|
||||
{{ (property.displayValue | async) | translate }}
|
||||
</div>
|
||||
<div *ngIf="isEditable">
|
||||
<div *ngIf="isEditable" class="adf-property-editable">
|
||||
<adf-content-enrichment-menu matPrefix *ngIf="hasContentEnrichment" [prediction]="property.prediction"></adf-content-enrichment-menu>
|
||||
<mat-form-field class="adf-property-value" [ngClass]="{'adf-property-value-editable': isEditable}">
|
||||
<mat-select
|
||||
[(value)]="value"
|
||||
|
@@ -47,6 +47,18 @@
|
||||
padding: 6px 0;
|
||||
border-bottom: 1px solid var(--adf-metadata-property-panel-border-color);
|
||||
color: var(--adf-metadata-property-panel-title-color);
|
||||
|
||||
adf-content-enrichment-menu.adf-content-enrichment-trigger {
|
||||
display: inline-block;
|
||||
height: 17px;
|
||||
margin-left: -3px;
|
||||
margin-right: -7px;
|
||||
}
|
||||
}
|
||||
|
||||
.adf-property-editable {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.mdc-line-ripple {
|
||||
|
@@ -38,10 +38,13 @@ export class CardViewSelectItemComponent extends BaseCardView<CardViewSelectItem
|
||||
@Input() options$: Observable<CardViewSelectItemOption<string | number>[]>;
|
||||
|
||||
@Input()
|
||||
displayNoneOption: boolean = true;
|
||||
displayNoneOption = true;
|
||||
|
||||
@Input()
|
||||
displayEmpty: boolean = true;
|
||||
displayEmpty = true;
|
||||
|
||||
@Input()
|
||||
hasContentEnrichment = false;
|
||||
|
||||
value: string | number;
|
||||
filter$ = new BehaviorSubject<string>('');
|
||||
@@ -50,6 +53,7 @@ export class CardViewSelectItemComponent extends BaseCardView<CardViewSelectItem
|
||||
|
||||
ngOnChanges(): void {
|
||||
this.value = this.property.value;
|
||||
this.property.setValue(this.property.value);
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
|
@@ -37,6 +37,7 @@
|
||||
[attr.data-automation-id]="'card-textitem-value-' + property.key"
|
||||
(keydown)="undoText($event)"
|
||||
/>
|
||||
<adf-content-enrichment-menu matPrefix *ngIf="hasContentEnrichment" [prediction]="property.prediction"></adf-content-enrichment-menu>
|
||||
<textarea
|
||||
matInput
|
||||
*ngIf="property.multiline"
|
||||
@@ -119,6 +120,7 @@
|
||||
>
|
||||
{{ property.label | translate }}
|
||||
</mat-label>
|
||||
<!-- <adf-content-enrichment-menu matPrefix *ngIf="hasContentEnrichment && !isEditable" [prediction]="property.prediction"></adf-content-enrichment-menu>-->
|
||||
<input
|
||||
matInput
|
||||
[type]="property.inputType"
|
||||
|
@@ -57,6 +57,9 @@ export class CardViewTextItemComponent extends BaseCardView<CardViewTextItemMode
|
||||
@Input()
|
||||
displayLabelForChips = false;
|
||||
|
||||
@Input()
|
||||
hasContentEnrichment = false;
|
||||
|
||||
editedValue: string | string[];
|
||||
errors: CardViewItemValidator[];
|
||||
templateType: string;
|
||||
|
@@ -10,7 +10,8 @@
|
||||
[copyToClipboardAction]="copyToClipboardAction"
|
||||
[useChipsForMultiValueProperty]="useChipsForMultiValueProperty"
|
||||
[multiValueSeparator]="multiValueSeparator"
|
||||
[displayLabelForChips]="displayLabelForChips">
|
||||
[displayLabelForChips]="displayLabelForChips"
|
||||
[hasContentEnrichment]="!!property.prediction">
|
||||
</adf-card-view-item-dispatcher>
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -24,6 +24,16 @@
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.adf-property-value-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border-bottom: 1px solid var(--adf-metadata-property-panel-border-color);
|
||||
|
||||
adf-content-enrichment-menu button.adf-ai-button {
|
||||
margin-left: -10px;
|
||||
}
|
||||
}
|
||||
|
||||
.adf-property {
|
||||
.adf-property-field {
|
||||
width: 100%;
|
||||
@@ -48,6 +58,7 @@
|
||||
|
||||
#{$mat-form--text-field-infix} {
|
||||
border-top-width: 0;
|
||||
position: initial;
|
||||
}
|
||||
|
||||
#{$mat-form-field-flex} {
|
||||
|
@@ -19,13 +19,16 @@ import { Subject } from 'rxjs';
|
||||
import { CardViewBaseItemModel } from '../models/card-view-baseitem.model';
|
||||
import { UpdateNotification } from './update-notification.interface';
|
||||
import { ClickNotification } from './click-notification.interface';
|
||||
import { PredictionStatusUpdate } from '../../prediction';
|
||||
|
||||
export interface BaseCardViewUpdate {
|
||||
itemUpdated$: Subject<UpdateNotification>;
|
||||
itemClicked$: Subject<ClickNotification>;
|
||||
updateItem$: Subject<CardViewBaseItemModel>;
|
||||
predictionStatusChanged$: Subject<PredictionStatusUpdate[]>;
|
||||
|
||||
update(property: CardViewBaseItemModel, newValue: any);
|
||||
clicked(property: CardViewBaseItemModel);
|
||||
updateElement(notification: CardViewBaseItemModel);
|
||||
onPredictionStatusChanged(notification: PredictionStatusUpdate[]);
|
||||
}
|
||||
|
@@ -16,6 +16,7 @@
|
||||
*/
|
||||
|
||||
import { CardViewItemValidator } from './card-view-item-validator.interface';
|
||||
import { Prediction } from '@alfresco/js-api';
|
||||
|
||||
export interface CardViewItemProperties {
|
||||
label: string;
|
||||
@@ -36,4 +37,5 @@ export interface CardViewItemProperties {
|
||||
parameters?: { [key: string]: any };
|
||||
}>;
|
||||
multivalued?: boolean;
|
||||
prediction?: Prediction;
|
||||
}
|
||||
|
@@ -15,6 +15,8 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Prediction } from '@alfresco/js-api';
|
||||
|
||||
export interface CardViewItem {
|
||||
label: string;
|
||||
value: any;
|
||||
@@ -24,4 +26,5 @@ export interface CardViewItem {
|
||||
displayValue: any;
|
||||
editable?: boolean;
|
||||
icon?: string;
|
||||
prediction?: Prediction;
|
||||
}
|
||||
|
@@ -17,6 +17,7 @@
|
||||
|
||||
import { CardViewItemProperties, CardViewItemValidator } from '../interfaces/card-view.interfaces';
|
||||
import validatorsMap from '../validators/validators.map';
|
||||
import { Prediction } from '@alfresco/js-api';
|
||||
|
||||
export abstract class CardViewBaseItemModel<T = any> {
|
||||
label: string;
|
||||
@@ -31,6 +32,7 @@ export abstract class CardViewBaseItemModel<T = any> {
|
||||
data?: any;
|
||||
type?: string;
|
||||
multivalued?: boolean;
|
||||
prediction?: Prediction;
|
||||
|
||||
constructor(props: CardViewItemProperties) {
|
||||
this.label = props.label || '';
|
||||
@@ -44,6 +46,7 @@ export abstract class CardViewBaseItemModel<T = any> {
|
||||
this.validators = props.validators || [];
|
||||
this.data = props.data || null;
|
||||
this.multivalued = !!props.multivalued;
|
||||
this.prediction = props.prediction || null;
|
||||
|
||||
if (props?.constraints?.length ?? 0) {
|
||||
for (const constraint of props.constraints) {
|
||||
|
@@ -57,5 +57,14 @@ describe('CardViewSelectItemModel', () => {
|
||||
|
||||
expect(itemModel.displayNoneOption).toBe(false);
|
||||
}));
|
||||
|
||||
it('should update the value when new value is set', (done) => {
|
||||
const itemModel = new CardViewSelectItemModel(properties);
|
||||
itemModel.setValue('three');
|
||||
itemModel.displayValue.subscribe((value) => {
|
||||
expect(value).toBe(mockData[2].label);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@@ -19,8 +19,8 @@ import { CardViewItem } from '../interfaces/card-view-item.interface';
|
||||
import { DynamicComponentModel } from '../../common/services/dynamic-component-mapper.service';
|
||||
import { CardViewBaseItemModel } from './card-view-baseitem.model';
|
||||
import { CardViewSelectItemProperties, CardViewSelectItemOption } from '../interfaces/card-view.interfaces';
|
||||
import { Observable, of } from 'rxjs';
|
||||
import { switchMap } from 'rxjs/operators';
|
||||
import { BehaviorSubject, Observable } from 'rxjs';
|
||||
import { map, switchMap } from 'rxjs/operators';
|
||||
|
||||
export class CardViewSelectItemModel<T> extends CardViewBaseItemModel implements CardViewItem, DynamicComponentModel {
|
||||
type = 'select';
|
||||
@@ -29,6 +29,8 @@ export class CardViewSelectItemModel<T> extends CardViewBaseItemModel implements
|
||||
|
||||
valueFetch$: Observable<string> = null;
|
||||
|
||||
private valueSubject = new BehaviorSubject<any>(this.value);
|
||||
|
||||
constructor(cardViewSelectItemProperties: CardViewSelectItemProperties<T>) {
|
||||
super(cardViewSelectItemProperties);
|
||||
|
||||
@@ -36,11 +38,13 @@ export class CardViewSelectItemModel<T> extends CardViewBaseItemModel implements
|
||||
|
||||
this.options$ = cardViewSelectItemProperties.options$;
|
||||
|
||||
this.valueFetch$ = this.options$.pipe(
|
||||
switchMap((options) => {
|
||||
const option = options.find((o) => o.key === this.value?.toString());
|
||||
return of(option ? option.label : '');
|
||||
})
|
||||
this.valueFetch$ = this.valueSubject.pipe(
|
||||
switchMap((value) => this.options$.pipe(
|
||||
map((options) => {
|
||||
const option = options.find((o) => o.key === value?.toString());
|
||||
return option ? option.label : '';
|
||||
})
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
@@ -50,5 +54,6 @@ export class CardViewSelectItemModel<T> extends CardViewBaseItemModel implements
|
||||
|
||||
setValue(value: any) {
|
||||
this.value = value;
|
||||
this.valueSubject.next(value);
|
||||
}
|
||||
}
|
||||
|
@@ -18,6 +18,7 @@
|
||||
import { fakeAsync, TestBed } from '@angular/core/testing';
|
||||
import { CardViewBaseItemModel } from '../models/card-view-baseitem.model';
|
||||
import { CardViewUpdateService, transformKeyToObject } from './card-view-update.service';
|
||||
import { PredictionStatusUpdate } from '@alfresco/adf-core';
|
||||
|
||||
describe('CardViewUpdateService', () => {
|
||||
|
||||
@@ -82,5 +83,14 @@ describe('CardViewUpdateService', () => {
|
||||
);
|
||||
cardViewUpdateService.clicked(property);
|
||||
}));
|
||||
|
||||
it('should emit predictionStatusChanged$ when onPredictionStatusChanged is called', (done) => {
|
||||
const mockPredictionStatusUpdate: PredictionStatusUpdate[] = [{ key: 'test', previousValue: 'value' }];
|
||||
cardViewUpdateService.predictionStatusChanged$.subscribe((value) => {
|
||||
expect(value).toEqual(mockPredictionStatusUpdate);
|
||||
done();
|
||||
});
|
||||
cardViewUpdateService.onPredictionStatusChanged(mockPredictionStatusUpdate);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@@ -21,6 +21,7 @@ import { BaseCardViewUpdate } from '../interfaces/base-card-view-update.interfac
|
||||
import { ClickNotification } from '../interfaces/click-notification.interface';
|
||||
import { UpdateNotification } from '../interfaces/update-notification.interface';
|
||||
import { CardViewBaseItemModel } from '../models/card-view-baseitem.model';
|
||||
import { PredictionStatusUpdate } from '../../prediction/interfaces/prediction-status-update.interface';
|
||||
|
||||
export const transformKeyToObject = (key: string, value): any => {
|
||||
const objectLevels: string[] = key.split('.').reverse();
|
||||
@@ -36,6 +37,7 @@ export class CardViewUpdateService implements BaseCardViewUpdate {
|
||||
itemUpdated$ = new Subject<UpdateNotification>();
|
||||
itemClicked$ = new Subject<ClickNotification>();
|
||||
updateItem$ = new Subject<CardViewBaseItemModel>();
|
||||
predictionStatusChanged$ = new Subject<PredictionStatusUpdate[]>();
|
||||
|
||||
update(property: CardViewBaseItemModel, newValue: any) {
|
||||
this.itemUpdated$.next({
|
||||
@@ -59,4 +61,7 @@ export class CardViewUpdateService implements BaseCardViewUpdate {
|
||||
this.updateItem$.next(notification);
|
||||
}
|
||||
|
||||
onPredictionStatusChanged(notification: PredictionStatusUpdate[]) {
|
||||
this.predictionStatusChanged$.next(notification);
|
||||
}
|
||||
}
|
||||
|
@@ -158,7 +158,8 @@ export class ThumbnailService {
|
||||
'save-as': './assets/images/save-as.svg',
|
||||
save: './assets/images/save.svg',
|
||||
task: './assets/images/task.svg',
|
||||
'multipart/related': './assets/images/ft_ic_website.svg'
|
||||
'multipart/related': './assets/images/ft_ic_website.svg',
|
||||
'ai-sparkles': './assets/images/ai-sparkles.svg'
|
||||
};
|
||||
|
||||
constructor(matIconRegistry: MatIconRegistry, sanitizer: DomSanitizer) {
|
||||
|
@@ -248,6 +248,19 @@
|
||||
"DATEPICKER": "Use the arrow keys to navigate between dates. Up and down move to the next or previous week but on the same day. Left and right move to the next or previous day. Press Enter or Return to select a date.",
|
||||
"COPY_TO_CLIPBOARD_MESSAGE": "Value copied to clipboard",
|
||||
"EDIT_ASPECTS": "Edit Aspect"
|
||||
},
|
||||
"CONTENT_ENRICHMENT": {
|
||||
"ACCURACY_LEVEL": "Accuracy Level",
|
||||
"ACCURACY_DESCRIPTION": "This score represents the quality of the suggestion.",
|
||||
"LAST_VALUE": "Last user value",
|
||||
"CURRENT_VALUE": "Current Value",
|
||||
"ACTIONS": {
|
||||
"REVERT": "Revert",
|
||||
"CONFIRM": "Confirm"
|
||||
},
|
||||
"ARIA-LABEL": {
|
||||
"OPEN-MENU": "Open content enrichment menu"
|
||||
}
|
||||
}
|
||||
},
|
||||
"SEARCH": {
|
||||
|
@@ -0,0 +1,49 @@
|
||||
<button mat-icon-button [attr.aria-label]="'CORE.METADATA.CONTENT_ENRICHMENT.ACTIONS.CONFIRM' | translate" #menuTrigger="matMenuTrigger" class="adf-ai-button" [matMenuTriggerFor]="menu" (menuOpened)="onMenuOpen()">
|
||||
<adf-icon [value]="'adf:ai-sparkles'"></adf-icon>
|
||||
</button>
|
||||
|
||||
<mat-menu #menu="matMenu" class="adf-ai-mat-menu" (closed)="onClosed()">
|
||||
<div class="adf-content-enrichment-menu" #menuContainer (click)="$event.stopPropagation()" (keydown.tab)="$event.stopPropagation()" (keydown.shift.tab)="$event.stopPropagation()" tabindex="-1">
|
||||
<div class="adf-content-enrichment-menu__accuracy">
|
||||
<div class="adf-content-enrichment-menu__accuracy__title">
|
||||
<span>{{ 'CORE.METADATA.CONTENT_ENRICHMENT.ACCURACY_LEVEL' | translate }}</span>
|
||||
<p>{{ 'CORE.METADATA.CONTENT_ENRICHMENT.ACCURACY_DESCRIPTION' | translate }}</p>
|
||||
</div>
|
||||
<div class="adf-content-enrichment-menu__accuracy__level">
|
||||
<span>{{ confidencePercentage }}</span>
|
||||
<mat-progress-spinner
|
||||
class="adf-accuracy-level-spinner"
|
||||
diameter="49"
|
||||
title="{{ 'CORE.METADATA.CONTENT_ENRICHMENT.ACCURACY_LEVEL' | translate }}"
|
||||
color="primary"
|
||||
mode="determinate"
|
||||
[value]="confidencePercentage">
|
||||
</mat-progress-spinner>
|
||||
</div>
|
||||
</div>
|
||||
<div class="adf-content-enrichment-menu__content">
|
||||
<div class="adf-content-enrichment-menu__content__last">
|
||||
<p class="adf-content-enrichment-menu__content__label">{{ 'CORE.METADATA.CONTENT_ENRICHMENT.LAST_VALUE' | translate }}</p>
|
||||
<div class="adf-content-enrichment-menu__content__last__value">{{ previousValue || 'None' }}</div>
|
||||
</div>
|
||||
<mat-icon>arrow_downward</mat-icon>
|
||||
<div class="adf-content-enrichment-menu__content__current">
|
||||
<p class="adf-content-enrichment-menu__content__label">{{ 'CORE.METADATA.CONTENT_ENRICHMENT.CURRENT_VALUE' | translate }}
|
||||
<span>({{ predictionDateTime | adfLocalizedDate: 'MM/dd/yyyy' }})</span>
|
||||
</p>
|
||||
<div class="adf-content-enrichment-menu__content__current__value">
|
||||
<adf-icon [value]="'adf:ai-sparkles'"></adf-icon>
|
||||
<span>{{ predictionValue }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="adf-content-enrichment-menu__footer">
|
||||
<button mat-flat-button (click)="onRevert()">
|
||||
{{ 'CORE.METADATA.CONTENT_ENRICHMENT.ACTIONS.REVERT' | translate }}
|
||||
</button>
|
||||
<button mat-button color="primary" (click)="onConfirm()" class="adf-confirm-button">
|
||||
{{ 'CORE.METADATA.CONTENT_ENRICHMENT.ACTIONS.CONFIRM' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</mat-menu>
|
@@ -0,0 +1,136 @@
|
||||
adf-content-enrichment-menu {
|
||||
svg {
|
||||
height: 17px;
|
||||
width: 17px;
|
||||
}
|
||||
}
|
||||
|
||||
.adf-ai-mat-menu {
|
||||
border: 1px solid transparent;
|
||||
border-radius: 6px;
|
||||
background-image: linear-gradient(var(--adf-theme-background-dialog-color), var(--adf-theme-background-dialog-color)),
|
||||
var(--adf-ai-border-gradient);
|
||||
background-origin: border-box;
|
||||
background-clip: content-box, border-box;
|
||||
}
|
||||
|
||||
.adf-content-enrichment-menu {
|
||||
padding: 5px 10px;
|
||||
|
||||
&__accuracy {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 18px;
|
||||
justify-content: space-between;
|
||||
|
||||
&__title {
|
||||
span {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
line-height: 16px;
|
||||
letter-spacing: 0.5px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
line-height: 16px;
|
||||
letter-spacing: 0.5px;
|
||||
text-align: left;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&__level {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
span {
|
||||
width: 0;
|
||||
position: relative;
|
||||
left: 14px;
|
||||
font-size: 17.96px;
|
||||
font-weight: 700;
|
||||
line-height: 25.66px;
|
||||
letter-spacing: 0.1924px;
|
||||
}
|
||||
|
||||
.adf-accuracy-level-spinner circle {
|
||||
stroke: var(--theme-accent-color);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__content {
|
||||
padding: 18px 10px;
|
||||
border: 1px solid var(--adf-metadata-property-panel-border-color);
|
||||
border-radius: 12px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 30px;
|
||||
|
||||
&__label {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
line-height: 20px;
|
||||
letter-spacing: 0.25px;
|
||||
text-align: left;
|
||||
margin: 0 0 4px;
|
||||
|
||||
span {
|
||||
font-size: 10px;
|
||||
font-weight: 500;
|
||||
line-height: 16px;
|
||||
letter-spacing: 0.2px;
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
|
||||
&__last {
|
||||
width: 100%;
|
||||
|
||||
&__value {
|
||||
background: var(--adf-theme-foreground-text-color-007);
|
||||
border-radius: 6px;
|
||||
padding: 7px;
|
||||
min-height: 19px;
|
||||
}
|
||||
}
|
||||
|
||||
&__current {
|
||||
width: 100%;
|
||||
|
||||
&__value {
|
||||
padding: 7px;
|
||||
border: 2px dashed var(--adf-metadata-property-panel-border-color);
|
||||
border-radius: 6px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
mat-icon {
|
||||
width: 17px;
|
||||
height: 17px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mat-icon {
|
||||
color: var(--adf-theme-foreground-text-color-054);
|
||||
}
|
||||
}
|
||||
|
||||
&__footer {
|
||||
margin-top: 18px;
|
||||
text-align: end;
|
||||
|
||||
.adf-confirm-button{
|
||||
margin-left: 8px;
|
||||
color: var(--adf-theme-foreground-base-color);
|
||||
background-color: var(--theme-grey-text-background-color);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,172 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright © 2005-2024 Hyland Software, Inc. and its affiliates. All rights reserved.
|
||||
*
|
||||
* 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 { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { of } from 'rxjs';
|
||||
import { ContentEnrichmentMenuComponent } from './content-enrichment-menu.component';
|
||||
import { PredictionService } from '../../services';
|
||||
import { CoreTestingModule } from '../../../testing/core.testing.module';
|
||||
import { LocalizedDatePipe } from '../../../pipes/localized-date.pipe';
|
||||
import { Prediction, ReviewStatus } from '@alfresco/js-api';
|
||||
|
||||
describe('ContentEnrichmentMenuComponent', () => {
|
||||
let component: ContentEnrichmentMenuComponent;
|
||||
let fixture: ComponentFixture<ContentEnrichmentMenuComponent>;
|
||||
let predictionService: PredictionService;
|
||||
let localizedDatePipe: LocalizedDatePipe;
|
||||
|
||||
const predictionMock: Prediction = {
|
||||
confidenceLevel: 0.9,
|
||||
predictionDateTime: new Date(2022, 1, 1),
|
||||
modelId: 'test-model-id',
|
||||
property: 'test:test',
|
||||
id: 'test-prediction-id',
|
||||
previousValue: 'previous value',
|
||||
predictionValue: 'new value',
|
||||
updateType: 'AUTOCORRECT',
|
||||
reviewStatus: ReviewStatus.UNREVIEWED
|
||||
};
|
||||
|
||||
const openMenu = () => {
|
||||
const button = fixture.debugElement.query(By.css('.adf-ai-button')).nativeElement;
|
||||
button.click();
|
||||
fixture.detectChanges();
|
||||
};
|
||||
|
||||
beforeEach(async () => {
|
||||
const predictionServiceMock = {
|
||||
reviewPrediction: jasmine.createSpy('reviewPrediction').and.returnValue(of(null)),
|
||||
predictionStatusUpdated$: {next: jasmine.createSpy('next')}
|
||||
};
|
||||
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [ContentEnrichmentMenuComponent, CoreTestingModule, MatProgressSpinnerModule],
|
||||
providers: [{provide: PredictionService, useValue: predictionServiceMock}]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(ContentEnrichmentMenuComponent);
|
||||
component = fixture.componentInstance;
|
||||
component.prediction = {...predictionMock};
|
||||
predictionService = TestBed.inject(PredictionService);
|
||||
localizedDatePipe = TestBed.inject(LocalizedDatePipe);
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should initialize properties when prediction is provided', () => {
|
||||
expect(component.confidencePercentage).toBe(90);
|
||||
expect(component.previousValue).toBe('previous value');
|
||||
expect(component.predictionValue).toBe('new value');
|
||||
expect(component.predictionDateTime).toEqual(predictionMock.predictionDateTime);
|
||||
});
|
||||
|
||||
it('should initialize properties with default values when prediction is not provided', () => {
|
||||
component.prediction = null;
|
||||
component.ngOnInit();
|
||||
|
||||
expect(component.confidencePercentage).toBe(0);
|
||||
expect(component.previousValue).toBe('');
|
||||
expect(component.predictionValue).toBe('');
|
||||
expect(component.predictionDateTime).toBeNull();
|
||||
});
|
||||
|
||||
it('should correctly set boolean values', () => {
|
||||
component.prediction.previousValue = false;
|
||||
component.prediction.predictionValue = true;
|
||||
component.ngOnInit();
|
||||
|
||||
expect(component.previousValue).toBeFalse();
|
||||
expect(component.predictionValue).toBeTrue();
|
||||
});
|
||||
|
||||
it('should call reviewPrediction with REJECTED on revert', () => {
|
||||
component.onRevert();
|
||||
expect(predictionService.reviewPrediction).toHaveBeenCalledWith(component.prediction.id, ReviewStatus.REJECTED);
|
||||
});
|
||||
|
||||
it('should call reviewPrediction with CONFIRMED on confirm', () => {
|
||||
component.onConfirm();
|
||||
expect(predictionService.reviewPrediction).toHaveBeenCalledWith(component.prediction.id, ReviewStatus.CONFIRMED);
|
||||
});
|
||||
|
||||
it('should emit predictionStatusUpdated$ on confirm', () => {
|
||||
component.onConfirm();
|
||||
expect(predictionService.predictionStatusUpdated$.next).toHaveBeenCalledWith({key: component.prediction.property});
|
||||
});
|
||||
|
||||
it('should emit predictionStatusUpdated$ on revert', () => {
|
||||
component.onRevert();
|
||||
expect(predictionService.predictionStatusUpdated$.next).toHaveBeenCalledWith({
|
||||
key: component.prediction.property,
|
||||
previousValue: component.previousValue
|
||||
});
|
||||
});
|
||||
|
||||
it('should close the menu on revert', () => {
|
||||
const menuTriggerSpy = spyOn(component.menuTrigger, 'closeMenu');
|
||||
component.onRevert();
|
||||
expect(menuTriggerSpy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should close the menu on confirm', () => {
|
||||
const menuTriggerSpy = spyOn(component.menuTrigger, 'closeMenu');
|
||||
component.onConfirm();
|
||||
expect(menuTriggerSpy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should open the menu on button click', () => {
|
||||
spyOn(component, 'onMenuOpen');
|
||||
openMenu();
|
||||
expect(component.onMenuOpen).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should correctly format predictionDateTime using adfLocalizedDate', () => {
|
||||
const formattedDate = localizedDatePipe.transform(component.predictionDateTime, 'MM/dd/yyyy');
|
||||
openMenu();
|
||||
const dateElement = fixture.debugElement.query(By.css('.adf-content-enrichment-menu__content__label span')).nativeElement;
|
||||
expect(dateElement.textContent).toContain(formattedDate);
|
||||
});
|
||||
|
||||
it('should render progress spinner with correct value', () => {
|
||||
openMenu();
|
||||
const spinnerElement = fixture.debugElement.query(By.css('.adf-accuracy-level-spinner'));
|
||||
expect(spinnerElement.nativeElement).toBeDefined();
|
||||
expect(spinnerElement.componentInstance.value).toEqual(component.confidencePercentage);
|
||||
});
|
||||
|
||||
it('should render previous human entered value', () => {
|
||||
openMenu();
|
||||
const previousValueElement = fixture.debugElement.query(By.css('.adf-content-enrichment-menu__content__last__value')).nativeElement;
|
||||
expect(previousValueElement.textContent).toContain(component.previousValue);
|
||||
});
|
||||
|
||||
it('should display "None" as previous value when previousValue is not defined', () => {
|
||||
component.prediction.previousValue = null;
|
||||
component.ngOnInit();
|
||||
openMenu();
|
||||
const previousValueElement = fixture.debugElement.query(By.css('.adf-content-enrichment-menu__content__last__value')).nativeElement;
|
||||
expect(previousValueElement.textContent).toContain('None');
|
||||
});
|
||||
|
||||
it('should render predicted value', () => {
|
||||
openMenu();
|
||||
const predictionValueElement = fixture.debugElement.query(By.css('.adf-content-enrichment-menu__content__current__value span')).nativeElement;
|
||||
expect(predictionValueElement.textContent).toContain(component.predictionValue);
|
||||
});
|
||||
});
|
@@ -0,0 +1,93 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright © 2005-2024 Hyland Software, Inc. and its affiliates. All rights reserved.
|
||||
*
|
||||
* 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, ElementRef, Input, OnInit, ViewChild, ViewEncapsulation } from '@angular/core';
|
||||
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
||||
import { MatTooltipModule } from '@angular/material/tooltip';
|
||||
import { MatMenuModule, MatMenuTrigger } from '@angular/material/menu';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { ConfigurableFocusTrap, ConfigurableFocusTrapFactory } from '@angular/cdk/a11y';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { Prediction, ReviewStatus } from '@alfresco/js-api';
|
||||
import { IconComponent } from '../../../icon';
|
||||
import { LocalizedDatePipe } from '../../../pipes';
|
||||
import { PredictionService } from '../../services';
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
selector: 'adf-content-enrichment-menu',
|
||||
templateUrl: './content-enrichment-menu.component.html',
|
||||
styleUrls: ['./content-enrichment-menu.component.scss'],
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
imports: [MatProgressSpinnerModule, MatTooltipModule, MatButtonModule, IconComponent, MatMenuModule, MatIconModule, TranslateModule, LocalizedDatePipe]
|
||||
})
|
||||
export class ContentEnrichmentMenuComponent implements OnInit {
|
||||
|
||||
@Input()
|
||||
prediction: Prediction;
|
||||
|
||||
@ViewChild('menuContainer', { static: false })
|
||||
menuContainer: ElementRef;
|
||||
|
||||
@ViewChild('menuTrigger', { static: false })
|
||||
menuTrigger: MatMenuTrigger;
|
||||
|
||||
focusTrap: ConfigurableFocusTrap;
|
||||
confidencePercentage: number;
|
||||
previousValue: any;
|
||||
predictionValue: any;
|
||||
predictionDateTime: Date;
|
||||
|
||||
constructor(private predictionService: PredictionService, private focusTrapFactory: ConfigurableFocusTrapFactory) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.confidencePercentage = this.prediction?.confidenceLevel * 100 || 0;
|
||||
this.previousValue = this.isDefined(this.prediction?.previousValue) ? this.prediction.previousValue : '';
|
||||
this.predictionValue = this.isDefined(this.prediction?.predictionValue) ? this.prediction.predictionValue : '';
|
||||
this.predictionDateTime = this.prediction?.predictionDateTime || null;
|
||||
}
|
||||
|
||||
onMenuOpen() {
|
||||
if (this.menuContainer && !this.focusTrap) {
|
||||
this.focusTrap = this.focusTrapFactory.create(this.menuContainer.nativeElement);
|
||||
}
|
||||
}
|
||||
|
||||
onRevert() {
|
||||
this.predictionService.reviewPrediction(this.prediction.id, ReviewStatus.REJECTED).subscribe(() => {
|
||||
this.predictionService.predictionStatusUpdated$.next({key: this.prediction.property, previousValue: this.previousValue});
|
||||
this.menuTrigger.closeMenu();
|
||||
});
|
||||
}
|
||||
|
||||
onConfirm() {
|
||||
this.predictionService.reviewPrediction(this.prediction.id, ReviewStatus.CONFIRMED).subscribe(() => {
|
||||
this.predictionService.predictionStatusUpdated$.next({key: this.prediction.property});
|
||||
this.menuTrigger.closeMenu();
|
||||
});
|
||||
}
|
||||
|
||||
onClosed() {
|
||||
this.focusTrap.destroy();
|
||||
this.focusTrap = null;
|
||||
}
|
||||
|
||||
private isDefined(value: string): boolean {
|
||||
return value !== undefined && value !== null;
|
||||
}
|
||||
}
|
18
lib/core/src/lib/prediction/index.ts
Normal file
18
lib/core/src/lib/prediction/index.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright © 2005-2024 Hyland Software, Inc. and its affiliates. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export * from './public-api';
|
@@ -0,0 +1,21 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright © 2005-2024 Hyland Software, Inc. and its affiliates. All rights reserved.
|
||||
*
|
||||
* 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 PredictionStatusUpdate {
|
||||
key: string;
|
||||
previousValue?: any;
|
||||
}
|
19
lib/core/src/lib/prediction/public-api.ts
Normal file
19
lib/core/src/lib/prediction/public-api.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright © 2005-2024 Hyland Software, Inc. and its affiliates. All rights reserved.
|
||||
*
|
||||
* 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 './services';
|
||||
export * from './interfaces/prediction-status-update.interface';
|
18
lib/core/src/lib/prediction/services/index.ts
Normal file
18
lib/core/src/lib/prediction/services/index.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright © 2005-2024 Hyland Software, Inc. and its affiliates. All rights reserved.
|
||||
*
|
||||
* 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 './prediction.service';
|
@@ -0,0 +1,54 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright © 2005-2024 Hyland Software, Inc. and its affiliates. All rights reserved.
|
||||
*
|
||||
* 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 { PredictionService } from './prediction.service';
|
||||
import { CoreTestingModule } from '../../testing/core.testing.module';
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import { Prediction, PredictionEntry, PredictionPaging, PredictionPagingList, ReviewStatus } from '@alfresco/js-api';
|
||||
|
||||
describe('PredictionService', () => {
|
||||
let service: PredictionService;
|
||||
|
||||
const mockPredictionPaging = (): PredictionPaging => {
|
||||
const prediction = new Prediction();
|
||||
prediction.id = 'test id';
|
||||
const predictionEntry = new PredictionEntry({ entry: prediction });
|
||||
const predictionPagingList = new PredictionPagingList({ entries: [predictionEntry] });
|
||||
return new PredictionPaging({ list: predictionPagingList });
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [CoreTestingModule]
|
||||
});
|
||||
service = TestBed.inject(PredictionService);
|
||||
});
|
||||
|
||||
it('should call getPredictions on PredictionsApi with nodeId', () => {
|
||||
spyOn(service.predictionsApi, 'getPredictions').and.returnValue(Promise.resolve(mockPredictionPaging()));
|
||||
|
||||
service.getPredictions('test id');
|
||||
expect(service.predictionsApi.getPredictions).toHaveBeenCalledWith('test id');
|
||||
});
|
||||
|
||||
it('should call reviewPrediction on PredictionsApi with predictionId and reviewStatus', () => {
|
||||
spyOn(service.predictionsApi, 'reviewPrediction').and.returnValue(Promise.resolve());
|
||||
|
||||
service.reviewPrediction('test id', ReviewStatus.CONFIRMED);
|
||||
expect(service.predictionsApi.reviewPrediction).toHaveBeenCalledWith('test id', ReviewStatus.CONFIRMED);
|
||||
});
|
||||
});
|
60
lib/core/src/lib/prediction/services/prediction.service.ts
Normal file
60
lib/core/src/lib/prediction/services/prediction.service.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright © 2005-2024 Hyland Software, Inc. and its affiliates. All rights reserved.
|
||||
*
|
||||
* 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 '../../services/alfresco-api.service';
|
||||
import { PredictionsApi, PredictionPaging, ReviewStatus } from '@alfresco/js-api';
|
||||
import { from, Observable, Subject } from 'rxjs';
|
||||
import { PredictionStatusUpdate } from '../interfaces/prediction-status-update.interface';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class PredictionService {
|
||||
private _predictionsApi: PredictionsApi;
|
||||
|
||||
/**
|
||||
* Gets emitted when prediction status updated
|
||||
*/
|
||||
predictionStatusUpdated$ = new Subject<PredictionStatusUpdate>();
|
||||
|
||||
get predictionsApi(): PredictionsApi {
|
||||
this._predictionsApi = this._predictionsApi ?? new PredictionsApi(this.apiService.getInstance());
|
||||
return this._predictionsApi;
|
||||
}
|
||||
|
||||
constructor(private apiService: AlfrescoApiService) {}
|
||||
|
||||
/**
|
||||
* Get predictions for a given node
|
||||
*
|
||||
* @param nodeId The identifier of node.
|
||||
* @returns Observable<PredictionPaging>
|
||||
*/
|
||||
getPredictions(nodeId: string): Observable<PredictionPaging> {
|
||||
return from(this.predictionsApi.getPredictions(nodeId));
|
||||
}
|
||||
|
||||
/**
|
||||
* Review a prediction
|
||||
*
|
||||
* @param predictionId The identifier of prediction.
|
||||
* @param reviewStatus Review status to apply.
|
||||
* @returns Observable<void>
|
||||
*/
|
||||
reviewPrediction(predictionId: string, reviewStatus: ReviewStatus): Observable<void> {
|
||||
return from(this.predictionsApi.reviewPrediction(predictionId, reviewStatus));
|
||||
}
|
||||
}
|
@@ -89,7 +89,8 @@
|
||||
--adf-danger-button-background: $adf-danger-button-background,
|
||||
--adf-secondary-button-background: $adf-secondary-button-background,
|
||||
|
||||
--adf-display-external-property-widget-preview-selection-color: mat.get-color-from-palette($foreground, secondary-text)
|
||||
--adf-display-external-property-widget-preview-selection-color: mat.get-color-from-palette($foreground, secondary-text),
|
||||
--adf-ai-border-gradient: $adf-ref-ai-border-gradient
|
||||
);
|
||||
|
||||
// propagates SCSS variables into the CSS variables scope
|
||||
|
@@ -27,3 +27,4 @@ $adf-ref-header-icon-color: inherit;
|
||||
$adf-ref-header-icon-border-radius: 50%;
|
||||
$adf-danger-button-background: #ba1b1b;
|
||||
$adf-secondary-button-background: #2121210d;
|
||||
$adf-ref-ai-border-gradient: linear-gradient(180deg, #94B7FF 0%, #9E00FF 100%);
|
||||
|
@@ -49,6 +49,7 @@ export * from './lib/blank-page/index';
|
||||
export * from './lib/search-text/index';
|
||||
export * from './lib/snackbar-content/index';
|
||||
export * from './lib/translation/index';
|
||||
export * from './lib/prediction/index';
|
||||
|
||||
export * from './lib/common/utils/index';
|
||||
export * from './lib/interface/index';
|
||||
|
Reference in New Issue
Block a user