mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-10 03:23:07 +00:00
Manage different level of confidence in the submission
This commit is contained in:
@@ -49,33 +49,60 @@ module.exports = {
|
||||
// NOTE: every how many minutes submission is saved automatically
|
||||
timer: 5
|
||||
},
|
||||
metadata: {
|
||||
// NOTE: allow to set icons used to represent metadata belonging to a relation group
|
||||
icons: [
|
||||
icons: {
|
||||
metadata: [
|
||||
/**
|
||||
* NOTE: example of configuration
|
||||
* {
|
||||
* // NOTE: metadata name
|
||||
* name: 'dc.author',
|
||||
* config: {
|
||||
* // NOTE: used when metadata value has an authority
|
||||
* withAuthority: {
|
||||
* // NOTE: fontawesome (v4.x) icon classes and bootstrap color utility classes can be used
|
||||
* style: 'fa-user'
|
||||
* },
|
||||
* // NOTE: used when metadata value has not an authority
|
||||
* withoutAuthority: {
|
||||
* style: 'fa-user text-muted'
|
||||
* }
|
||||
* }
|
||||
* // NOTE: fontawesome (v4.x) icon classes and bootstrap utility classes can be used
|
||||
* style: 'fa-user'
|
||||
* }
|
||||
*/
|
||||
{
|
||||
// NOTE: metadata name
|
||||
name: 'dc.author',
|
||||
// NOTE: fontawesome (v4.x) icon classes and bootstrap utility classes can be used
|
||||
style: 'fa-user'
|
||||
},
|
||||
// default configuration
|
||||
{
|
||||
name: 'default',
|
||||
config: {}
|
||||
style: ''
|
||||
}
|
||||
]
|
||||
],
|
||||
authority: {
|
||||
confidence: [
|
||||
/**
|
||||
* NOTE: example of configuration
|
||||
* {
|
||||
* // NOTE: confidence value
|
||||
* value: 'dc.author',
|
||||
* // NOTE: fontawesome (v4.x) icon classes and bootstrap utility classes can be used
|
||||
* style: 'fa-user'
|
||||
* }
|
||||
*/
|
||||
{
|
||||
value: 600,
|
||||
style: 'text-success'
|
||||
},
|
||||
{
|
||||
value: 500,
|
||||
style: 'text-info'
|
||||
},
|
||||
{
|
||||
value: 400,
|
||||
style: 'text-warning'
|
||||
},
|
||||
// default configuration
|
||||
{
|
||||
value: 'default',
|
||||
style: 'text-muted'
|
||||
},
|
||||
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
// Angular Universal settings
|
||||
|
44
src/app/core/integration/models/confidence-type.ts
Normal file
44
src/app/core/integration/models/confidence-type.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
export enum ConfidenceType {
|
||||
/**
|
||||
* This authority value has been confirmed as accurate by an
|
||||
* interactive user or authoritative policy
|
||||
*/
|
||||
CF_ACCEPTED = 600,
|
||||
|
||||
/**
|
||||
* Value is singular and valid but has not been seen and accepted
|
||||
* by a human, so its provenance is uncertain.
|
||||
*/
|
||||
CF_UNCERTAIN = 500,
|
||||
|
||||
/**
|
||||
* There are multiple matching authority values of equal validity.
|
||||
*/
|
||||
CF_AMBIGUOUS = 400,
|
||||
|
||||
/**
|
||||
* There are no matching answers from the authority.
|
||||
*/
|
||||
CF_NOTFOUND = 300,
|
||||
|
||||
/**
|
||||
* The authority encountered an internal failure - this preserves a
|
||||
* record in the metadata of why there is no value.
|
||||
*/
|
||||
CF_FAILED = 200,
|
||||
|
||||
/**
|
||||
* The authority recommends this submission be rejected.
|
||||
*/
|
||||
CF_REJECTED = 100,
|
||||
|
||||
/**
|
||||
* No reasonable confidence value is available
|
||||
*/
|
||||
CF_NOVALUE = 0,
|
||||
|
||||
/**
|
||||
* Value has not been set (DB default).
|
||||
*/
|
||||
CF_UNSET = -1
|
||||
}
|
@@ -0,0 +1,95 @@
|
||||
import {
|
||||
Directive,
|
||||
ElementRef, EventEmitter,
|
||||
HostListener,
|
||||
Inject,
|
||||
Input,
|
||||
OnChanges,
|
||||
Output,
|
||||
Renderer2,
|
||||
SimpleChanges
|
||||
} from '@angular/core';
|
||||
|
||||
import { findIndex } from 'lodash';
|
||||
|
||||
import { AuthorityValue } from '../../core/integration/models/authority.value';
|
||||
import { FormFieldMetadataValueObject } from '../form/builder/models/form-field-metadata-value.model';
|
||||
import { ConfidenceType } from '../../core/integration/models/confidence-type';
|
||||
import { isNotEmpty, isNull } from '../empty.util';
|
||||
import { GLOBAL_CONFIG, GlobalConfig } from '../../../config';
|
||||
import { ConfidenceIconConfig } from '../../../config/submission-config.interface';
|
||||
|
||||
@Directive({
|
||||
selector: '[dsAuthorityConfidenceState]'
|
||||
})
|
||||
export class AuthorityConfidenceStateDirective implements OnChanges {
|
||||
|
||||
@Input() authorityValue: AuthorityValue | FormFieldMetadataValueObject | string;
|
||||
|
||||
private previousClass: string = null;
|
||||
private newClass: string;
|
||||
|
||||
@Output() whenClickOnConfidenceNotAccepted: EventEmitter<ConfidenceType> = new EventEmitter<ConfidenceType>();
|
||||
|
||||
@HostListener('click') onClick() {
|
||||
if (isNotEmpty(this.authorityValue) && this.getConfidenceByValue(this.authorityValue) !== ConfidenceType.CF_ACCEPTED) {
|
||||
this.whenClickOnConfidenceNotAccepted.emit(this.getConfidenceByValue(this.authorityValue));
|
||||
}
|
||||
}
|
||||
|
||||
constructor(
|
||||
@Inject(GLOBAL_CONFIG) protected EnvConfig: GlobalConfig,
|
||||
private elem: ElementRef,
|
||||
private renderer: Renderer2
|
||||
) {
|
||||
}
|
||||
|
||||
ngOnChanges(changes: SimpleChanges): void {
|
||||
if (!changes.authorityValue.firstChange) {
|
||||
this.previousClass = this.getClassByConfidence(this.getConfidenceByValue(changes.authorityValue.previousValue))
|
||||
}
|
||||
this.newClass = this.getClassByConfidence(this.getConfidenceByValue(changes.authorityValue.currentValue));
|
||||
|
||||
if (isNull(this.previousClass)) {
|
||||
this.renderer.addClass(this.elem.nativeElement, this.newClass);
|
||||
} else if (this.previousClass !== this.newClass) {
|
||||
this.renderer.removeClass(this.elem.nativeElement, this.previousClass);
|
||||
this.renderer.addClass(this.elem.nativeElement, this.newClass);
|
||||
}
|
||||
}
|
||||
|
||||
ngAfterViewInit() {
|
||||
if (isNull(this.previousClass)) {
|
||||
this.renderer.addClass(this.elem.nativeElement, this.newClass);
|
||||
} else if (this.previousClass !== this.newClass) {
|
||||
this.renderer.removeClass(this.elem.nativeElement, this.previousClass);
|
||||
this.renderer.addClass(this.elem.nativeElement, this.newClass);
|
||||
}
|
||||
}
|
||||
|
||||
private getConfidenceByValue(value: any): ConfidenceType {
|
||||
let confidence: ConfidenceType = ConfidenceType.CF_UNSET;
|
||||
|
||||
if (isNotEmpty(value) && value instanceof AuthorityValue && value.hasAuthority()) {
|
||||
confidence = ConfidenceType.CF_ACCEPTED;
|
||||
}
|
||||
|
||||
if (isNotEmpty(value) && value instanceof FormFieldMetadataValueObject) {
|
||||
confidence = value.confidence;
|
||||
}
|
||||
|
||||
return confidence;
|
||||
}
|
||||
|
||||
private getClassByConfidence(confidence: any): string {
|
||||
const confidenceIcons: ConfidenceIconConfig[] = this.EnvConfig.submission.icons.authority.confidence;
|
||||
|
||||
const confidenceIndex: number = findIndex(confidenceIcons, {value: confidence});
|
||||
|
||||
const defaultconfidenceIndex: number = findIndex(confidenceIcons, {value: 'default'});
|
||||
const defaultClass: string = (defaultconfidenceIndex !== -1) ? confidenceIcons[defaultconfidenceIndex].style : '';
|
||||
|
||||
return (confidenceIndex !== -1) ? confidenceIcons[confidenceIndex].style : defaultClass;
|
||||
}
|
||||
|
||||
}
|
@@ -20,7 +20,8 @@
|
||||
[ngClass]="{'chip-selected disabled': (editable && c.editMode) || dragged == i}"
|
||||
(click)="chipsSelected($event, i);">
|
||||
<span>
|
||||
<ng-container *ngIf="c.hasIcons()">
|
||||
<i *ngIf="showIcons && !c.isNestedItem()" dsAuthorityConfidenceState [authorityValue]="c.item" class="fa fa-circle-o" aria-hidden="true"></i>
|
||||
<ng-container *ngIf="showIcons && c.hasIcons()">
|
||||
<i *ngFor="let icon of c.icons; let l = last"
|
||||
[ngbTooltip]="tipContent"
|
||||
triggers="manual"
|
||||
@@ -28,6 +29,8 @@
|
||||
class="fa {{icon.style}}"
|
||||
[class.mr-1]="!l"
|
||||
[class.mr-2]="l"
|
||||
dsAuthorityConfidenceState
|
||||
[authorityValue]="c.item[icon.metadata] || c.item"
|
||||
aria-hidden="true"
|
||||
(dragstart)="tooltip.close();"
|
||||
(mouseover)="showTooltip(t, i, icon.metadata)"
|
||||
|
@@ -19,6 +19,7 @@ export class ChipsComponent implements OnChanges {
|
||||
@Input() chips: Chips;
|
||||
@Input() wrapperClass: string;
|
||||
@Input() editable = true;
|
||||
@Input() showIcons = false;
|
||||
|
||||
@Output() selected: EventEmitter<number> = new EventEmitter<number>();
|
||||
@Output() remove: EventEmitter<number> = new EventEmitter<number>();
|
||||
|
@@ -3,7 +3,6 @@ import { isNotEmpty } from '../../empty.util';
|
||||
|
||||
export interface ChipsItemIcon {
|
||||
metadata: string;
|
||||
hasAuthority: boolean;
|
||||
style: string;
|
||||
tooltip?: any;
|
||||
}
|
||||
@@ -11,7 +10,7 @@ export interface ChipsItemIcon {
|
||||
export class ChipsItem {
|
||||
public id: string;
|
||||
public display: string;
|
||||
public item: any;
|
||||
private _item: any;
|
||||
public editMode?: boolean;
|
||||
public icons?: ChipsItemIcon[];
|
||||
|
||||
@@ -25,7 +24,7 @@ export class ChipsItem {
|
||||
editMode?: boolean) {
|
||||
|
||||
this.id = uniqueId();
|
||||
this.item = item;
|
||||
this._item = item;
|
||||
this.fieldToDisplay = fieldToDisplay;
|
||||
this.objToDisplay = objToDisplay;
|
||||
this.setDisplayText();
|
||||
@@ -33,6 +32,21 @@ export class ChipsItem {
|
||||
this.icons = icons || [];
|
||||
}
|
||||
|
||||
public set item(item) {
|
||||
this._item = item;
|
||||
}
|
||||
|
||||
public get item() {
|
||||
return this._item;
|
||||
}
|
||||
|
||||
isNestedItem(): boolean {
|
||||
return (isNotEmpty(this.item)
|
||||
&& isObject(this.item)
|
||||
&& isNotEmpty(this.objToDisplay)
|
||||
&& this.item[this.objToDisplay]);
|
||||
}
|
||||
|
||||
hasIcons(): boolean {
|
||||
return isNotEmpty(this.icons);
|
||||
}
|
||||
@@ -46,7 +60,7 @@ export class ChipsItem {
|
||||
}
|
||||
|
||||
updateItem(item: any): void {
|
||||
this.item = item;
|
||||
this._item = item;
|
||||
this.setDisplayText();
|
||||
}
|
||||
|
||||
@@ -55,10 +69,10 @@ export class ChipsItem {
|
||||
}
|
||||
|
||||
private setDisplayText(): void {
|
||||
let value = this.item;
|
||||
if (isObject(this.item)) {
|
||||
let value = this._item;
|
||||
if (isObject(this._item)) {
|
||||
// Check If displayField is in an internal object
|
||||
const obj = this.objToDisplay ? this.item[this.objToDisplay] : this.item;
|
||||
const obj = this.objToDisplay ? this._item[this.objToDisplay] : this._item;
|
||||
|
||||
if (isObject(obj) && obj) {
|
||||
value = obj[this.fieldToDisplay] || obj.value;
|
||||
|
@@ -3,34 +3,20 @@ import { BehaviorSubject } from 'rxjs/BehaviorSubject';
|
||||
import { ChipsItem, ChipsItemIcon } from './chips-item.model';
|
||||
import { hasValue, isNotEmpty } from '../../empty.util';
|
||||
import { PLACEHOLDER_PARENT_METADATA } from '../../form/builder/ds-dynamic-form-ui/models/dynamic-group/dynamic-group.model';
|
||||
|
||||
export interface IconsConfig {
|
||||
withAuthority?: {
|
||||
style: string;
|
||||
};
|
||||
|
||||
withoutAuthority?: {
|
||||
style: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface MetadataIconsConfig {
|
||||
name: string;
|
||||
config: IconsConfig;
|
||||
}
|
||||
import { MetadataIconConfig } from '../../../../config/submission-config.interface';
|
||||
|
||||
export class Chips {
|
||||
chipsItems: BehaviorSubject<ChipsItem[]>;
|
||||
displayField: string;
|
||||
displayObj: string;
|
||||
iconsConfig: MetadataIconsConfig[];
|
||||
iconsConfig: MetadataIconConfig[];
|
||||
|
||||
private _items: ChipsItem[];
|
||||
|
||||
constructor(items: any[] = [],
|
||||
displayField: string = 'display',
|
||||
displayObj?: string,
|
||||
iconsConfig?: MetadataIconsConfig[]) {
|
||||
iconsConfig?: MetadataIconConfig[]) {
|
||||
|
||||
this.displayField = displayField;
|
||||
this.displayObj = displayObj;
|
||||
@@ -115,8 +101,8 @@ export class Chips {
|
||||
private getChipsIcons(item) {
|
||||
const icons = [];
|
||||
const defaultConfigIndex: number = findIndex(this.iconsConfig, {name: 'default'});
|
||||
const defaultConfig: IconsConfig = (defaultConfigIndex !== -1) ? this.iconsConfig[defaultConfigIndex].config : undefined;
|
||||
let config: IconsConfig;
|
||||
const defaultConfig: MetadataIconConfig = (defaultConfigIndex !== -1) ? this.iconsConfig[defaultConfigIndex] : undefined;
|
||||
let config: MetadataIconConfig;
|
||||
let configIndex: number;
|
||||
let value: any;
|
||||
|
||||
@@ -126,26 +112,31 @@ export class Chips {
|
||||
value = item[metadata];
|
||||
configIndex = findIndex(this.iconsConfig, {name: metadata});
|
||||
|
||||
config = (configIndex !== -1) ? this.iconsConfig[configIndex].config : defaultConfig;
|
||||
config = (configIndex !== -1) ? this.iconsConfig[configIndex] : defaultConfig;
|
||||
|
||||
if (hasValue(value) && isNotEmpty(config) && !this.hasPlaceholder(value)) {
|
||||
|
||||
let icon: ChipsItemIcon;
|
||||
const hasAuthority: boolean = !!(isObject(value) && ((value.hasOwnProperty('authority') && value.authority) || (value.hasOwnProperty('id') && value.id)));
|
||||
|
||||
// Set icons
|
||||
if ((this.displayObj && this.displayObj === metadata && hasAuthority)
|
||||
// Set icon
|
||||
icon = {
|
||||
metadata,
|
||||
style: config.style
|
||||
};
|
||||
|
||||
icons.push(icon);
|
||||
/* if ((this.displayObj && this.displayObj === metadata && hasAuthority)
|
||||
|| (this.displayObj && this.displayObj !== metadata)) {
|
||||
|
||||
icon = {
|
||||
metadata,
|
||||
hasAuthority: hasAuthority,
|
||||
style: (hasAuthority) ? config.withAuthority.style : config.withoutAuthority.style
|
||||
style: config.style
|
||||
};
|
||||
}
|
||||
if (icon) {
|
||||
icons.push(icon);
|
||||
}
|
||||
}*/
|
||||
}
|
||||
});
|
||||
|
||||
|
@@ -68,6 +68,7 @@
|
||||
*ngIf="chips && chips.hasItems()"
|
||||
[chips]="chips"
|
||||
[editable]="true"
|
||||
[showIcons]="true"
|
||||
(selected)="onChipSelected($event)"></ds-chips>
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -15,7 +15,7 @@ import { Subscription } from 'rxjs/Subscription';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { filter, flatMap, map, mergeMap, scan } from 'rxjs/operators';
|
||||
import { DynamicFormControlModel, DynamicFormGroupModel, DynamicInputModel } from '@ng-dynamic-forms/core';
|
||||
import { isEqual } from 'lodash';
|
||||
import { isEqual, isObject } from 'lodash';
|
||||
|
||||
import { DynamicGroupModel, PLACEHOLDER_PARENT_METADATA } from './dynamic-group.model';
|
||||
import { FormBuilderService } from '../../../form-builder.service';
|
||||
@@ -32,6 +32,8 @@ import { hasOnlyEmptyProperties } from '../../../../../object.util';
|
||||
import { IntegrationSearchOptions } from '../../../../../../core/integration/models/integration-options.model';
|
||||
import { AuthorityService } from '../../../../../../core/integration/authority.service';
|
||||
import { IntegrationData } from '../../../../../../core/integration/integration-data';
|
||||
import { FormFieldMetadataValueObject } from '../../../models/form-field-metadata-value.model';
|
||||
import { AuthorityValue } from '../../../../../../core/integration/models/authority.value';
|
||||
|
||||
@Component({
|
||||
selector: 'ds-dynamic-group',
|
||||
@@ -219,7 +221,7 @@ export class DsDynamicGroupComponent implements OnDestroy, OnInit {
|
||||
let returnObj = Object.create({});
|
||||
returnObj = Object.keys(valueObj).map((fieldName) => {
|
||||
let return$: Observable<any>;
|
||||
if (valueObj[fieldName].hasAuthority() && isNotEmpty(valueObj[fieldName].authority)) {
|
||||
if (isObject(valueObj[fieldName]) && valueObj[fieldName].hasAuthority() && isNotEmpty(valueObj[fieldName].authority)) {
|
||||
const fieldId = fieldName.replace(/\./g, '_');
|
||||
const model = this.formBuilderService.findById(fieldId, this.formModel);
|
||||
const searchOptions: IntegrationSearchOptions = new IntegrationSearchOptions(
|
||||
@@ -231,7 +233,13 @@ export class DsDynamicGroupComponent implements OnDestroy, OnInit {
|
||||
1);
|
||||
|
||||
return$ = this.authorityService.getEntryByValue(searchOptions)
|
||||
.map((result: IntegrationData) => result.payload[0]);
|
||||
.map((result: IntegrationData) => Object.assign(
|
||||
new FormFieldMetadataValueObject(),
|
||||
valueObj[fieldName],
|
||||
{
|
||||
otherInformation: (result.payload[0] as AuthorityValue).otherInformation
|
||||
})
|
||||
);
|
||||
} else {
|
||||
return$ = Observable.of(valueObj[fieldName]);
|
||||
}
|
||||
@@ -262,6 +270,7 @@ export class DsDynamicGroupComponent implements OnDestroy, OnInit {
|
||||
}, []),
|
||||
filter((modelValues: any[]) => this.model.value.length === modelValues.length)
|
||||
).subscribe((modelValue) => {
|
||||
this.model.valueUpdates.next(modelValue);
|
||||
this.initChips(modelValue);
|
||||
}));
|
||||
}
|
||||
@@ -272,7 +281,7 @@ export class DsDynamicGroupComponent implements OnDestroy, OnInit {
|
||||
initChipsValue,
|
||||
'value',
|
||||
this.model.mandatoryField,
|
||||
this.EnvConfig.submission.metadata.icons);
|
||||
this.EnvConfig.submission.icons.metadata);
|
||||
this.subs.push(
|
||||
this.chips.chipsItems
|
||||
.subscribe((subItems: any[]) => {
|
||||
|
@@ -4,7 +4,12 @@
|
||||
|
||||
<div class="form-row align-items-center">
|
||||
<!--Simple lookup, first field -->
|
||||
<div class="col">
|
||||
<div class="col right-addon">
|
||||
<i dsAuthorityConfidenceState
|
||||
class="fa fa-circle-o fa-2x fa-fw position-absolute mt-1 p-0"
|
||||
aria-hidden="true"
|
||||
[authorityValue]="model.value"
|
||||
(whenClickOnConfidenceNotAccepted)="whenClickOnConfidenceNotAccepted(sdRef, $event)"></i>
|
||||
<input class="form-control"
|
||||
[attr.autoComplete]="model.autoComplete"
|
||||
[class.is-invalid]="showErrorMessages"
|
||||
@@ -41,19 +46,29 @@
|
||||
(click)="$event.stopPropagation(); sdRef.close();"
|
||||
(input)="onInput($event)">
|
||||
</div>
|
||||
<div *ngIf="!isInputDisabled()" class="col-auto text-center">
|
||||
<div class="col-auto text-center">
|
||||
<button ngbDropdownAnchor
|
||||
class="btn btn-secondary"
|
||||
class="btn btn-secondary"
|
||||
type="button"
|
||||
[disabled]="model.readOnly || isSearchDisabled()"
|
||||
[hidden]="isInputDisabled()"
|
||||
(click)="sdRef.open(); search(); $event.stopPropagation();">{{'form.search' | translate}}
|
||||
</button>
|
||||
</div>
|
||||
<div *ngIf="isInputDisabled()" class="col-auto text-center">
|
||||
<button class="btn btn-secondary"
|
||||
type="button"
|
||||
[disabled]="model.readOnly"
|
||||
(click)="remove($event)">{{'form.remove' | translate}}
|
||||
ngbTooltip="{{'form.edit-help' | translate}}"
|
||||
placement="top"
|
||||
[disabled]="isEditDisabled()"
|
||||
(click)="switchEditMode()">{{'form.edit' | translate}}
|
||||
</button>
|
||||
<button *ngIf="editMode" class="btn btn-secondary"
|
||||
type="button"
|
||||
ngbTooltip="{{'form.save-help' | translate}}"
|
||||
placement="top"
|
||||
[disabled]="!hasEmptyValue()"
|
||||
(click)="saveChanges()">{{'form.save' | translate}}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -19,11 +19,11 @@
|
||||
}
|
||||
|
||||
/* align fa-spin */
|
||||
.left-addon .fa-spin {
|
||||
.left-addon .fa {
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.right-addon .fa-spin {
|
||||
.right-addon .fa {
|
||||
right: 0;
|
||||
}
|
||||
|
||||
|
@@ -11,6 +11,8 @@ import { Subscription } from 'rxjs/Subscription';
|
||||
import { FormFieldMetadataValueObject } from '../../../models/form-field-metadata-value.model';
|
||||
import { AuthorityValue } from '../../../../../../core/integration/models/authority.value';
|
||||
import { DynamicLookupNameModel } from './dynamic-lookup-name.model';
|
||||
import { ConfidenceType } from '../../../../../../core/integration/models/confidence-type';
|
||||
import { NgbDropdown } from '@ng-bootstrap/ng-bootstrap';
|
||||
|
||||
@Component({
|
||||
selector: 'ds-dynamic-lookup',
|
||||
@@ -27,6 +29,7 @@ export class DsDynamicLookupComponent implements OnDestroy, OnInit {
|
||||
@Output() change: EventEmitter<any> = new EventEmitter<any>();
|
||||
@Output() focus: EventEmitter<any> = new EventEmitter<any>();
|
||||
|
||||
public editMode = false;
|
||||
public firstInputValue = '';
|
||||
public secondInputValue = '';
|
||||
public loading = false;
|
||||
@@ -40,6 +43,10 @@ export class DsDynamicLookupComponent implements OnDestroy, OnInit {
|
||||
private cdr: ChangeDetectorRef) {
|
||||
}
|
||||
|
||||
inputFormatter = (x: { display: string }, y: number) => {
|
||||
return y === 1 ? this.firstInputValue : this.secondInputValue;
|
||||
};
|
||||
|
||||
ngOnInit() {
|
||||
this.searchOptions = new IntegrationSearchOptions(
|
||||
this.model.authorityOptions.scope,
|
||||
@@ -55,38 +62,33 @@ export class DsDynamicLookupComponent implements OnDestroy, OnInit {
|
||||
.subscribe((value) => {
|
||||
if (isEmpty(value)) {
|
||||
this.resetFields();
|
||||
} else {
|
||||
} else if (!this.editMode) {
|
||||
this.setInputsValue(this.model.value);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public formatItemForInput(item: any, field: number): string {
|
||||
if (isUndefined(item) || isNull(item)) {
|
||||
return '';
|
||||
}
|
||||
return (typeof item === 'string') ? item : this.inputFormatter(item, field);
|
||||
}
|
||||
|
||||
inputFormatter = (x: { display: string }, y: number) => {
|
||||
return y === 1 ? this.firstInputValue : this.secondInputValue;
|
||||
};
|
||||
|
||||
onInput(event) {
|
||||
if (!this.model.authorityOptions.closed) {
|
||||
if (isNotEmpty(this.getCurrentValue())) {
|
||||
const currentValue = new FormFieldMetadataValueObject(this.getCurrentValue());
|
||||
this.onSelect(currentValue);
|
||||
} else {
|
||||
this.remove();
|
||||
protected getCurrentValue(): string {
|
||||
let result = '';
|
||||
if (!this.isLookupName()) {
|
||||
result = this.firstInputValue;
|
||||
} else {
|
||||
if (isNotEmpty(this.firstInputValue)) {
|
||||
result = this.firstInputValue;
|
||||
}
|
||||
if (isNotEmpty(this.secondInputValue)) {
|
||||
result = isEmpty(result)
|
||||
? this.secondInputValue
|
||||
: this.firstInputValue + (this.model as DynamicLookupNameModel).separator + ' ' + this.secondInputValue;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
onScroll() {
|
||||
if (!this.loading && this.pageInfo.currentPage <= this.pageInfo.totalPages) {
|
||||
this.searchOptions.currentPage++;
|
||||
this.search();
|
||||
protected resetFields() {
|
||||
this.firstInputValue = '';
|
||||
if (this.isLookupName()) {
|
||||
this.secondInputValue = '';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -110,24 +112,110 @@ export class DsDynamicLookupComponent implements OnDestroy, OnInit {
|
||||
}
|
||||
}
|
||||
|
||||
protected getCurrentValue(): string {
|
||||
let result = '';
|
||||
if (!this.isLookupName()) {
|
||||
result = this.firstInputValue;
|
||||
} else {
|
||||
if (isNotEmpty(this.firstInputValue)) {
|
||||
result = this.firstInputValue;
|
||||
}
|
||||
if (isNotEmpty(this.secondInputValue)) {
|
||||
result = isEmpty(result)
|
||||
? this.secondInputValue
|
||||
: this.firstInputValue + (this.model as DynamicLookupNameModel).separator + ' ' + this.secondInputValue;
|
||||
}
|
||||
public formatItemForInput(item: any, field: number): string {
|
||||
if (isUndefined(item) || isNull(item)) {
|
||||
return '';
|
||||
}
|
||||
return result;
|
||||
return (typeof item === 'string') ? item : this.inputFormatter(item, field);
|
||||
}
|
||||
|
||||
search() {
|
||||
public hasAuthorityValue() {
|
||||
return hasValue(this.model.value)
|
||||
&& this.model.value.hasAuthority();
|
||||
}
|
||||
|
||||
public hasEmptyValue() {
|
||||
return isNotEmpty(this.getCurrentValue());
|
||||
}
|
||||
|
||||
public clearFields() {
|
||||
// Clear inputs whether there is no results and authority is closed
|
||||
if (this.model.authorityOptions.closed) {
|
||||
this.resetFields();
|
||||
}
|
||||
}
|
||||
|
||||
public isEditDisabled() {
|
||||
return !this.hasAuthorityValue();
|
||||
}
|
||||
|
||||
public isInputDisabled() {
|
||||
return (this.model.authorityOptions.closed && this.hasAuthorityValue() && !this.editMode);
|
||||
}
|
||||
|
||||
public isLookupName() {
|
||||
return (this.model instanceof DynamicLookupNameModel);
|
||||
}
|
||||
|
||||
public isSearchDisabled() {
|
||||
return isEmpty(this.firstInputValue);
|
||||
}
|
||||
|
||||
public onBlurEvent(event: Event) {
|
||||
this.blur.emit(event);
|
||||
}
|
||||
|
||||
public onFocusEvent(event) {
|
||||
this.focus.emit(event);
|
||||
}
|
||||
|
||||
public onInput(event) {
|
||||
if (!this.model.authorityOptions.closed) {
|
||||
if (isNotEmpty(this.getCurrentValue())) {
|
||||
const currentValue = new FormFieldMetadataValueObject(this.getCurrentValue());
|
||||
if (!this.editMode) {
|
||||
this.onSelect(currentValue);
|
||||
}
|
||||
} else {
|
||||
this.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public onScroll() {
|
||||
if (!this.loading && this.pageInfo.currentPage <= this.pageInfo.totalPages) {
|
||||
this.searchOptions.currentPage++;
|
||||
this.search();
|
||||
}
|
||||
}
|
||||
|
||||
public onSelect(event) {
|
||||
this.group.markAsDirty();
|
||||
this.model.valueUpdates.next(event);
|
||||
this.setInputsValue(event);
|
||||
this.change.emit(event);
|
||||
this.optionsList = null;
|
||||
this.pageInfo = null;
|
||||
}
|
||||
|
||||
public openChange(isOpened: boolean) {
|
||||
if (!isOpened) {
|
||||
if (this.model.authorityOptions.closed && !this.hasAuthorityValue()) {
|
||||
this.setInputsValue('');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public remove() {
|
||||
this.group.markAsPristine();
|
||||
this.model.valueUpdates.next(null);
|
||||
this.change.emit(null);
|
||||
}
|
||||
|
||||
public saveChanges() {
|
||||
if (isNotEmpty(this.getCurrentValue())) {
|
||||
const newValue = Object.assign(new AuthorityValue(), this.model.value, {
|
||||
display: this.getCurrentValue(),
|
||||
value: this.getCurrentValue()
|
||||
});
|
||||
this.onSelect(newValue);
|
||||
} else {
|
||||
this.remove();
|
||||
}
|
||||
this.switchEditMode();
|
||||
}
|
||||
|
||||
public search() {
|
||||
this.optionsList = null;
|
||||
this.pageInfo = null;
|
||||
|
||||
@@ -145,68 +233,17 @@ export class DsDynamicLookupComponent implements OnDestroy, OnInit {
|
||||
});
|
||||
}
|
||||
|
||||
clearFields() {
|
||||
// Clear inputs whether there is no results and authority is closed
|
||||
if (this.model.authorityOptions.closed) {
|
||||
this.resetFields();
|
||||
public switchEditMode() {
|
||||
this.editMode = !this.editMode;
|
||||
}
|
||||
|
||||
public whenClickOnConfidenceNotAccepted(sdRef: NgbDropdown, confidence: ConfidenceType) {
|
||||
if (!this.model.readOnly) {
|
||||
sdRef.open();
|
||||
this.search();
|
||||
}
|
||||
}
|
||||
|
||||
protected resetFields() {
|
||||
this.firstInputValue = '';
|
||||
if (this.isLookupName()) {
|
||||
this.secondInputValue = '';
|
||||
}
|
||||
}
|
||||
|
||||
onSelect(event) {
|
||||
this.group.markAsDirty();
|
||||
this.model.valueUpdates.next(event);
|
||||
this.setInputsValue(event);
|
||||
this.change.emit(event);
|
||||
this.optionsList = null;
|
||||
this.pageInfo = null;
|
||||
}
|
||||
|
||||
isInputDisabled() {
|
||||
return this.model.authorityOptions.closed && hasValue(this.model.value);
|
||||
}
|
||||
|
||||
isLookupName() {
|
||||
return (this.model instanceof DynamicLookupNameModel);
|
||||
}
|
||||
|
||||
isSearchDisabled() {
|
||||
// if (this.firstInputValue === ''
|
||||
// && (this.isLookupName ? this.secondInputValue === '' : true)) {
|
||||
// return true;
|
||||
// }
|
||||
// return false;
|
||||
return isEmpty(this.firstInputValue);
|
||||
}
|
||||
|
||||
remove() {
|
||||
this.group.markAsPristine();
|
||||
this.model.valueUpdates.next(null);
|
||||
this.change.emit(null);
|
||||
}
|
||||
|
||||
openChange(isOpened: boolean) {
|
||||
if (!isOpened) {
|
||||
if (this.model.authorityOptions.closed) {
|
||||
this.setInputsValue('');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onBlurEvent(event: Event) {
|
||||
this.blur.emit(event);
|
||||
}
|
||||
|
||||
onFocusEvent(event) {
|
||||
this.focus.emit(event);
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
if (hasValue(this.sub)) {
|
||||
this.sub.unsubscribe();
|
||||
|
@@ -5,9 +5,10 @@
|
||||
|
||||
<ds-chips [chips]="chips"
|
||||
[editable]="false"
|
||||
[showIcons]="model.hasAuthority"
|
||||
[wrapperClass]="'border-bottom border-light'">
|
||||
|
||||
<input *ngIf="!searchOptions"
|
||||
<input *ngIf="!model.hasAuthority"
|
||||
class="border-0 form-control-plaintext tag-input flex-grow-1 mt-1 mb-1 chips-sort-ignore"
|
||||
type="text"
|
||||
[class.pl-3]="chips.hasItems()"
|
||||
@@ -20,7 +21,7 @@
|
||||
(keyup)="onKeyUp($event)" />
|
||||
|
||||
|
||||
<input *ngIf="searchOptions"
|
||||
<input *ngIf="model.hasAuthority"
|
||||
class="border-0 form-control-plaintext tag-input flex-grow-1 mt-1 mb-1 chips-sort-ignore"
|
||||
type="text"
|
||||
[(ngModel)]="currentValue"
|
||||
@@ -42,7 +43,8 @@
|
||||
(selectItem)="onSelectItem($event)"
|
||||
(keypress)="preventEventsPropagation($event)"
|
||||
(keydown)="preventEventsPropagation($event)"
|
||||
(keyup)="onKeyUp($event)"/>
|
||||
(keyup)="onKeyUp($event)"
|
||||
#instance="ngbTypeahead"/>
|
||||
<i *ngIf="searching" class="fa fa-circle-o-notch fa-spin fa-2x fa-fw text-primary position-absolute mt-1 p-0" aria-hidden="true"></i>
|
||||
</ds-chips>
|
||||
|
||||
|
@@ -1,15 +1,15 @@
|
||||
import { ChangeDetectorRef, Component, EventEmitter, Inject, Input, OnInit, Output } from '@angular/core';
|
||||
import { ChangeDetectorRef, Component, EventEmitter, Inject, Input, OnInit, Output, ViewChild } from '@angular/core';
|
||||
import { FormGroup } from '@angular/forms';
|
||||
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { NgbTypeaheadSelectItemEvent } from '@ng-bootstrap/ng-bootstrap';
|
||||
import { NgbTypeahead, NgbTypeaheadSelectItemEvent } from '@ng-bootstrap/ng-bootstrap';
|
||||
import { isEqual } from 'lodash';
|
||||
|
||||
import { AuthorityService } from '../../../../../../core/integration/authority.service';
|
||||
import { DynamicTagModel } from './dynamic-tag.model';
|
||||
import { IntegrationSearchOptions } from '../../../../../../core/integration/models/integration-options.model';
|
||||
import { Chips } from '../../../../../chips/models/chips.model';
|
||||
import { hasValue, isNotEmpty } from '../../../../../empty.util';
|
||||
import { isEqual } from 'lodash';
|
||||
import { GlobalConfig } from '../../../../../../../config/global-config.interface';
|
||||
import { GLOBAL_CONFIG } from '../../../../../../../config';
|
||||
|
||||
@@ -28,6 +28,8 @@ export class DsDynamicTagComponent implements OnInit {
|
||||
@Output() change: EventEmitter<any> = new EventEmitter<any>();
|
||||
@Output() focus: EventEmitter<any> = new EventEmitter<any>();
|
||||
|
||||
@ViewChild('instance') instance: NgbTypeahead;
|
||||
|
||||
chips: Chips;
|
||||
hasAuthority: boolean;
|
||||
|
||||
@@ -82,7 +84,11 @@ export class DsDynamicTagComponent implements OnInit {
|
||||
this.model.authorityOptions.metadata);
|
||||
}
|
||||
|
||||
this.chips = new Chips(this.model.value, 'display');
|
||||
this.chips = new Chips(
|
||||
this.model.value,
|
||||
'display',
|
||||
null,
|
||||
this.EnvConfig.submission.icons.metadata);
|
||||
|
||||
this.chips.chipsItems
|
||||
.subscribe((subItems: any[]) => {
|
||||
@@ -108,7 +114,7 @@ export class DsDynamicTagComponent implements OnInit {
|
||||
}
|
||||
|
||||
onBlur(event: Event) {
|
||||
if (isNotEmpty(this.currentValue)) {
|
||||
if (isNotEmpty(this.currentValue) && !this.instance.isPopupOpen()) {
|
||||
this.addTagsToChips();
|
||||
}
|
||||
this.blur.emit(event);
|
||||
|
@@ -22,6 +22,12 @@
|
||||
|
||||
<div class="position-relative right-addon">
|
||||
<i *ngIf="searching" class="fa fa-circle-o-notch fa-spin fa-2x fa-fw text-primary position-absolute mt-1 p-0" aria-hidden="true"></i>
|
||||
<i *ngIf="!searching"
|
||||
dsAuthorityConfidenceState
|
||||
class="fa fa-circle-o fa-2x fa-fw position-absolute mt-1 p-0"
|
||||
aria-hidden="true"
|
||||
[authorityValue]="currentValue"
|
||||
(whenClickOnConfidenceNotAccepted)="whenClickOnConfidenceNotAccepted($event)"></i>
|
||||
<input class="form-control"
|
||||
[attr.autoComplete]="model.autoComplete"
|
||||
[class.is-invalid]="showErrorMessages"
|
||||
|
@@ -6,8 +6,13 @@
|
||||
}
|
||||
|
||||
/* align fa-spin */
|
||||
.left-addon .fa-spin { left: 0;}
|
||||
.right-addon .fa-spin { right: 0;}
|
||||
.left-addon .fa {
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.right-addon .fa {
|
||||
right: 0;
|
||||
}
|
||||
|
||||
:host /deep/ .dropdown-menu {
|
||||
width: 100% !important;
|
||||
|
@@ -2,6 +2,8 @@ import { ChangeDetectorRef, Component, EventEmitter, Input, OnInit, Output } fro
|
||||
import { FormGroup } from '@angular/forms';
|
||||
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { debounceTime, distinctUntilChanged, map, merge, switchMap, tap } from 'rxjs/operators';
|
||||
import { Subject } from 'rxjs/Subject';
|
||||
import { NgbTypeaheadSelectItemEvent } from '@ng-bootstrap/ng-bootstrap';
|
||||
|
||||
import { AuthorityService } from '../../../../../../core/integration/authority.service';
|
||||
@@ -9,6 +11,7 @@ import { DynamicTypeaheadModel } from './dynamic-typeahead.model';
|
||||
import { IntegrationSearchOptions } from '../../../../../../core/integration/models/integration-options.model';
|
||||
import { isEmpty, isNotEmpty } from '../../../../../empty.util';
|
||||
import { FormFieldMetadataValueObject } from '../../../models/form-field-metadata-value.model';
|
||||
import { ConfidenceType } from '../../../../../../core/integration/models/confidence-type';
|
||||
|
||||
@Component({
|
||||
selector: 'ds-dynamic-typeahead',
|
||||
@@ -28,7 +31,8 @@ export class DsDynamicTypeaheadComponent implements OnInit {
|
||||
searching = false;
|
||||
searchOptions: IntegrationSearchOptions;
|
||||
searchFailed = false;
|
||||
hideSearchingWhenUnsubscribed = new Observable(() => () => this.changeSearchingStatus(false));
|
||||
hideSearchingWhenUnsubscribed$ = new Observable(() => () => this.changeSearchingStatus(false));
|
||||
click$ = new Subject<string>();
|
||||
currentValue: any;
|
||||
inputValue: any;
|
||||
|
||||
@@ -36,12 +40,13 @@ export class DsDynamicTypeaheadComponent implements OnInit {
|
||||
return (typeof x === 'object') ? x.display : x
|
||||
};
|
||||
|
||||
search = (text$: Observable<string>) =>
|
||||
text$
|
||||
.debounceTime(300)
|
||||
.distinctUntilChanged()
|
||||
.do(() => this.changeSearchingStatus(true))
|
||||
.switchMap((term) => {
|
||||
search = (text$: Observable<string>) => {
|
||||
return text$.pipe(
|
||||
merge(this.click$),
|
||||
debounceTime(200),
|
||||
distinctUntilChanged(),
|
||||
tap(() => this.changeSearchingStatus(true)),
|
||||
switchMap((term) => {
|
||||
if (term === '' || term.length < this.model.minChars) {
|
||||
return Observable.of({list: []});
|
||||
} else {
|
||||
@@ -60,10 +65,12 @@ export class DsDynamicTypeaheadComponent implements OnInit {
|
||||
return Observable.of({list: []});
|
||||
});
|
||||
}
|
||||
})
|
||||
.map((results) => results.list)
|
||||
.do(() => this.changeSearchingStatus(false))
|
||||
.merge(this.hideSearchingWhenUnsubscribed);
|
||||
}),
|
||||
map((results) => results.list),
|
||||
tap(() => this.changeSearchingStatus(false)),
|
||||
merge(this.hideSearchingWhenUnsubscribed$)
|
||||
)
|
||||
};
|
||||
|
||||
constructor(private authorityService: AuthorityService, private cdr: ChangeDetectorRef) {
|
||||
}
|
||||
@@ -88,8 +95,7 @@ export class DsDynamicTypeaheadComponent implements OnInit {
|
||||
|
||||
onInput(event) {
|
||||
if (!this.model.authorityOptions.closed && isNotEmpty(event.target.value)) {
|
||||
const valueObj = new FormFieldMetadataValueObject(event.target.value);
|
||||
this.inputValue = valueObj;
|
||||
this.inputValue = new FormFieldMetadataValueObject(event.target.value);
|
||||
this.model.valueUpdates.next(this.inputValue);
|
||||
}
|
||||
}
|
||||
@@ -120,4 +126,10 @@ export class DsDynamicTypeaheadComponent implements OnInit {
|
||||
this.model.valueUpdates.next(event.item);
|
||||
this.change.emit(event.item);
|
||||
}
|
||||
|
||||
public whenClickOnConfidenceNotAccepted(confidence: ConfidenceType) {
|
||||
if (!this.model.readOnly) {
|
||||
this.click$.next(this.formatter(this.currentValue));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,4 +1,5 @@
|
||||
import { isNotEmpty, isNotNull } from '../../../empty.util';
|
||||
import { isEmpty, isNotEmpty, isNotNull } from '../../../empty.util';
|
||||
import { ConfidenceType } from '../../../../core/integration/models/confidence-type';
|
||||
|
||||
export class FormFieldMetadataValueObject {
|
||||
metadata?: string;
|
||||
@@ -6,7 +7,7 @@ export class FormFieldMetadataValueObject {
|
||||
display: string;
|
||||
language: any;
|
||||
authority: string;
|
||||
confidence: number;
|
||||
confidence: ConfidenceType;
|
||||
place: number;
|
||||
closed: boolean;
|
||||
label: string;
|
||||
@@ -17,7 +18,7 @@ export class FormFieldMetadataValueObject {
|
||||
authority: string = null,
|
||||
display: string = null,
|
||||
place: number = 0,
|
||||
confidence: number = -1,
|
||||
confidence: number = null,
|
||||
otherInformation: any = null,
|
||||
metadata: string = null) {
|
||||
this.value = isNotNull(value) ? ((typeof value === 'string') ? value.trim() : value) : null;
|
||||
@@ -26,10 +27,12 @@ export class FormFieldMetadataValueObject {
|
||||
this.display = display || value;
|
||||
|
||||
this.confidence = confidence;
|
||||
if (authority != null) {
|
||||
this.confidence = 600;
|
||||
if (authority != null && isEmpty(confidence)) {
|
||||
this.confidence = ConfidenceType.CF_ACCEPTED;
|
||||
} else if (isNotEmpty(confidence)) {
|
||||
this.confidence = confidence;
|
||||
} else {
|
||||
this.confidence = ConfidenceType.CF_UNSET;
|
||||
}
|
||||
|
||||
this.place = place;
|
||||
|
@@ -83,6 +83,7 @@ import { InputSuggestionsComponent } from './input-suggestions/input-suggestions
|
||||
import { CapitalizePipe } from './utils/capitalize.pipe';
|
||||
import { MomentModule } from 'angular2-moment';
|
||||
import { ObjectKeysPipe } from './utils/object-keys-pipe';
|
||||
import { AuthorityConfidenceStateDirective } from './authority-confidence/authority-confidence-state.directive';
|
||||
|
||||
const MODULES = [
|
||||
// Do NOT include UniversalModule, HttpModule, or JsonpModule here
|
||||
@@ -183,7 +184,8 @@ const DIRECTIVES = [
|
||||
VarDirective,
|
||||
DragClickDirective,
|
||||
DebounceDirective,
|
||||
ClickOutsideDirective
|
||||
ClickOutsideDirective,
|
||||
AuthorityConfidenceStateDirective
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
|
@@ -1,16 +1,28 @@
|
||||
import { Config } from './config.interface';
|
||||
import { MetadataIconsConfig } from '../app/shared/chips/models/chips.model';
|
||||
|
||||
interface AutosaveConfig extends Config {
|
||||
metadata: string[],
|
||||
timer: number
|
||||
}
|
||||
|
||||
interface MetadataConfig extends Config {
|
||||
icons: MetadataIconsConfig[]
|
||||
interface IconsConfig extends Config {
|
||||
metadata: MetadataIconConfig[],
|
||||
authority: {
|
||||
confidence: ConfidenceIconConfig[];
|
||||
}
|
||||
}
|
||||
|
||||
export interface MetadataIconConfig extends Config {
|
||||
name: string,
|
||||
style: string;
|
||||
}
|
||||
|
||||
export interface ConfidenceIconConfig extends Config {
|
||||
value: any,
|
||||
style: string;
|
||||
}
|
||||
|
||||
export interface SubmissionConfig extends Config {
|
||||
autosave: AutosaveConfig,
|
||||
metadata: MetadataConfig
|
||||
icons: IconsConfig
|
||||
}
|
||||
|
Reference in New Issue
Block a user