From 2ba3f0b15ee1de39a8cda7e735b7ff990df1af4a Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Sat, 26 Oct 2024 16:42:44 +0200 Subject: [PATCH] 119915: Retrieve the edit metadata field dynamically --- src/app/core/shared/context.model.ts | 6 + ...-edit-metadata-field-values.component.html | 2 + ...so-edit-metadata-field-values.component.ts | 3 + ...dso-edit-metadata-value-field.component.ts | 48 ++++++ .../dso-edit-metadata-field-type.enum.ts | 6 + ...so-edit-metadata-text-field.component.html | 6 + ...so-edit-metadata-text-field.component.scss | 0 ...edit-metadata-text-field.component.spec.ts | 23 +++ .../dso-edit-metadata-text-field.component.ts | 16 ++ ...metadata-value-field-loader.component.html | 1 + ...t-metadata-value-field-loader.component.ts | 149 ++++++++++++++++++ ...t-metadata-value-field-loader.directive.ts | 17 ++ ...dso-edit-metadata-value-field.decorator.ts | 56 +++++++ .../dso-edit-metadata-value.component.html | 11 +- .../dso-edit-metadata-value.component.ts | 30 +++- .../dso-edit-metadata.component.html | 2 + .../dso-edit-metadata.component.ts | 3 + src/app/dso-shared/dso-shared.module.ts | 13 ++ 18 files changed, 388 insertions(+), 4 deletions(-) create mode 100644 src/app/dso-shared/dso-edit-metadata/dso-edit-metadata-value-field/abstract-dso-edit-metadata-value-field.component.ts create mode 100644 src/app/dso-shared/dso-edit-metadata/dso-edit-metadata-value-field/dso-edit-metadata-field-type.enum.ts create mode 100644 src/app/dso-shared/dso-edit-metadata/dso-edit-metadata-value-field/dso-edit-metadata-text-field/dso-edit-metadata-text-field.component.html create mode 100644 src/app/dso-shared/dso-edit-metadata/dso-edit-metadata-value-field/dso-edit-metadata-text-field/dso-edit-metadata-text-field.component.scss create mode 100644 src/app/dso-shared/dso-edit-metadata/dso-edit-metadata-value-field/dso-edit-metadata-text-field/dso-edit-metadata-text-field.component.spec.ts create mode 100644 src/app/dso-shared/dso-edit-metadata/dso-edit-metadata-value-field/dso-edit-metadata-text-field/dso-edit-metadata-text-field.component.ts create mode 100644 src/app/dso-shared/dso-edit-metadata/dso-edit-metadata-value-field/dso-edit-metadata-value-field-loader/dso-edit-metadata-value-field-loader.component.html create mode 100644 src/app/dso-shared/dso-edit-metadata/dso-edit-metadata-value-field/dso-edit-metadata-value-field-loader/dso-edit-metadata-value-field-loader.component.ts create mode 100644 src/app/dso-shared/dso-edit-metadata/dso-edit-metadata-value-field/dso-edit-metadata-value-field-loader/dso-edit-metadata-value-field-loader.directive.ts create mode 100644 src/app/dso-shared/dso-edit-metadata/dso-edit-metadata-value-field/dso-edit-metadata-value-field-loader/dso-edit-metadata-value-field.decorator.ts diff --git a/src/app/core/shared/context.model.ts b/src/app/core/shared/context.model.ts index b4c02bee63..a928de2bc8 100644 --- a/src/app/core/shared/context.model.ts +++ b/src/app/core/shared/context.model.ts @@ -39,4 +39,10 @@ export enum Context { MyDSpaceValidation = 'mydspaceValidation', Bitstream = 'bitstream', + + /** + * The Edit Metadata field Context values that are used in the Edit Item Metadata tab. + */ + AddMetadata = 'addMetadata', + EditMetadata = 'editMetadata', } diff --git a/src/app/dso-shared/dso-edit-metadata/dso-edit-metadata-field-values/dso-edit-metadata-field-values.component.html b/src/app/dso-shared/dso-edit-metadata/dso-edit-metadata-field-values/dso-edit-metadata-field-values.component.html index 9f74216d54..76ff6afe32 100644 --- a/src/app/dso-shared/dso-edit-metadata/dso-edit-metadata-field-values/dso-edit-metadata-field-values.component.html +++ b/src/app/dso-shared/dso-edit-metadata/dso-edit-metadata-field-values/dso-edit-metadata-field-values.component.html @@ -2,6 +2,8 @@ = new EventEmitter(); + +} diff --git a/src/app/dso-shared/dso-edit-metadata/dso-edit-metadata-value-field/dso-edit-metadata-field-type.enum.ts b/src/app/dso-shared/dso-edit-metadata/dso-edit-metadata-value-field/dso-edit-metadata-field-type.enum.ts new file mode 100644 index 0000000000..0f16a1b962 --- /dev/null +++ b/src/app/dso-shared/dso-edit-metadata/dso-edit-metadata-value-field/dso-edit-metadata-field-type.enum.ts @@ -0,0 +1,6 @@ +/** + * The edit metadata field tab types + */ +export enum EditMetadataValueFieldType { + PLAIN_TEXT = 'PLAIN_TEXT', +} diff --git a/src/app/dso-shared/dso-edit-metadata/dso-edit-metadata-value-field/dso-edit-metadata-text-field/dso-edit-metadata-text-field.component.html b/src/app/dso-shared/dso-edit-metadata/dso-edit-metadata-value-field/dso-edit-metadata-text-field/dso-edit-metadata-text-field.component.html new file mode 100644 index 0000000000..97e49ae39e --- /dev/null +++ b/src/app/dso-shared/dso-edit-metadata/dso-edit-metadata-value-field/dso-edit-metadata-text-field/dso-edit-metadata-text-field.component.html @@ -0,0 +1,6 @@ + diff --git a/src/app/dso-shared/dso-edit-metadata/dso-edit-metadata-value-field/dso-edit-metadata-text-field/dso-edit-metadata-text-field.component.scss b/src/app/dso-shared/dso-edit-metadata/dso-edit-metadata-value-field/dso-edit-metadata-text-field/dso-edit-metadata-text-field.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/app/dso-shared/dso-edit-metadata/dso-edit-metadata-value-field/dso-edit-metadata-text-field/dso-edit-metadata-text-field.component.spec.ts b/src/app/dso-shared/dso-edit-metadata/dso-edit-metadata-value-field/dso-edit-metadata-text-field/dso-edit-metadata-text-field.component.spec.ts new file mode 100644 index 0000000000..e3e2506732 --- /dev/null +++ b/src/app/dso-shared/dso-edit-metadata/dso-edit-metadata-value-field/dso-edit-metadata-text-field/dso-edit-metadata-text-field.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { DsoEditMetadataTextFieldComponent } from './dso-edit-metadata-text-field.component'; + +describe('DsoEditMetadataTextFieldComponent', () => { + let component: DsoEditMetadataTextFieldComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ + DsoEditMetadataTextFieldComponent, + ], + }).compileComponents(); + + fixture = TestBed.createComponent(DsoEditMetadataTextFieldComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/dso-shared/dso-edit-metadata/dso-edit-metadata-value-field/dso-edit-metadata-text-field/dso-edit-metadata-text-field.component.ts b/src/app/dso-shared/dso-edit-metadata/dso-edit-metadata-value-field/dso-edit-metadata-text-field/dso-edit-metadata-text-field.component.ts new file mode 100644 index 0000000000..9fe3b80316 --- /dev/null +++ b/src/app/dso-shared/dso-edit-metadata/dso-edit-metadata-value-field/dso-edit-metadata-text-field/dso-edit-metadata-text-field.component.ts @@ -0,0 +1,16 @@ +import { Component } from '@angular/core'; +import { editMetadataValueFieldComponent } from '../dso-edit-metadata-value-field-loader/dso-edit-metadata-value-field.decorator'; +import { EditMetadataValueFieldType } from '../dso-edit-metadata-field-type.enum'; +import { AbstractDsoEditMetadataValueFieldComponent } from '../abstract-dso-edit-metadata-value-field.component'; + +/** + * The component used to gather input for plain-text metadata fields + */ +@Component({ + selector: 'ds-dso-edit-metadata-text-field', + templateUrl: './dso-edit-metadata-text-field.component.html', + styleUrls: ['./dso-edit-metadata-text-field.component.scss'], +}) +@editMetadataValueFieldComponent(EditMetadataValueFieldType.PLAIN_TEXT) +export class DsoEditMetadataTextFieldComponent extends AbstractDsoEditMetadataValueFieldComponent { +} diff --git a/src/app/dso-shared/dso-edit-metadata/dso-edit-metadata-value-field/dso-edit-metadata-value-field-loader/dso-edit-metadata-value-field-loader.component.html b/src/app/dso-shared/dso-edit-metadata/dso-edit-metadata-value-field/dso-edit-metadata-value-field-loader/dso-edit-metadata-value-field-loader.component.html new file mode 100644 index 0000000000..4918c3ed9a --- /dev/null +++ b/src/app/dso-shared/dso-edit-metadata/dso-edit-metadata-value-field/dso-edit-metadata-value-field-loader/dso-edit-metadata-value-field-loader.component.html @@ -0,0 +1 @@ + diff --git a/src/app/dso-shared/dso-edit-metadata/dso-edit-metadata-value-field/dso-edit-metadata-value-field-loader/dso-edit-metadata-value-field-loader.component.ts b/src/app/dso-shared/dso-edit-metadata/dso-edit-metadata-value-field/dso-edit-metadata-value-field-loader/dso-edit-metadata-value-field-loader.component.ts new file mode 100644 index 0000000000..b9cb96ffd2 --- /dev/null +++ b/src/app/dso-shared/dso-edit-metadata/dso-edit-metadata-value-field/dso-edit-metadata-value-field-loader/dso-edit-metadata-value-field-loader.component.ts @@ -0,0 +1,149 @@ +import { Component, Input, ViewChild, ComponentRef, OnInit, OnChanges, OnDestroy, SimpleChanges, ViewContainerRef, Output, EventEmitter } from '@angular/core'; +import { Subscription } from 'rxjs'; +import { Context } from '../../../../core/shared/context.model'; +import { ThemeService } from '../../../../shared/theme-support/theme.service'; +import { DsoEditMetadataValueFieldLoaderDirective } from './dso-edit-metadata-value-field-loader.directive'; +import { hasNoValue, hasValue, isNotEmpty } from '../../../../shared/empty.util'; +import { GenericConstructor } from '../../../../core/shared/generic-constructor'; +import { getDsoEditMetadataValueFieldComponent } from './dso-edit-metadata-value-field.decorator'; +import { EditMetadataValueFieldType } from '../dso-edit-metadata-field-type.enum'; +import { DsoEditMetadataValue } from '../../dso-edit-metadata-form'; +import { DSpaceObject } from '../../../../core/shared/dspace-object.model'; + +@Component({ + selector: 'ds-dso-edit-metadata-value-field-loader', + templateUrl: './dso-edit-metadata-value-field-loader.component.html', +}) +export class DsoEditMetadataValueFieldLoaderComponent implements OnInit, OnChanges, OnDestroy { + + /** + * The optional context + */ + @Input() context: Context; + + /** + * The {@link DSpaceObject} + */ + @Input() dso: DSpaceObject; + + /** + * The type of the DSO, used to determines i18n messages + */ + @Input() dsoType: string; + + /** + * The type of the field + */ + @Input() type: EditMetadataValueFieldType; + + /** + * The metadata field + */ + @Input() mdField: string; + + /** + * Editable metadata value to show + */ + @Input() mdValue: DsoEditMetadataValue; + + /** + * Emits when the user clicked confirm + */ + @Output() confirm: EventEmitter = new EventEmitter(); + + /** + * Directive to determine where the dynamic child component is located + */ + @ViewChild(DsoEditMetadataValueFieldLoaderDirective, { static: true }) componentDirective: DsoEditMetadataValueFieldLoaderDirective; + + /** + * The reference to the dynamic component + */ + protected compRef: ComponentRef; + + /** + * Array to track all subscriptions and unsubscribe them onDestroy + */ + protected subs: Subscription[] = []; + + protected inAndOutputNames: (keyof this)[] = [ + 'context', + 'dso', + 'dsoType', + 'type', + 'mdField', + 'mdValue', + 'confirm', + ]; + + constructor( + protected themeService: ThemeService, + ) { + } + + public getComponent(): GenericConstructor { + return getDsoEditMetadataValueFieldComponent(this.type, this.context, this.themeService.getThemeName()); + } + + /** + * Set up the dynamic child component + */ + ngOnInit(): void { + this.instantiateComponent(); + } + + /** + * Whenever the inputs change, update the inputs of the dynamic component + */ + ngOnChanges(changes: SimpleChanges): void { + if (hasNoValue(this.compRef)) { + // sometimes the component has not been initialized yet, so it first needs to be initialized + // before being called again + this.instantiateComponent(changes); + } else { + // if an input or output has changed + if (this.inAndOutputNames.some((name: any) => hasValue(changes[name]))) { + this.connectInputsAndOutputs(); + if (this.compRef?.instance && 'ngOnChanges' in this.compRef.instance) { + (this.compRef.instance as any).ngOnChanges(changes); + } + } + } + } + + ngOnDestroy(): void { + this.subs + .filter((subscription: Subscription) => hasValue(subscription)) + .forEach((subscription: Subscription) => subscription.unsubscribe()); + } + + public instantiateComponent(changes?: SimpleChanges): void { + const component: GenericConstructor = this.getComponent(); + const viewContainerRef: ViewContainerRef = this.componentDirective.viewContainerRef; + viewContainerRef.clear(); + this.compRef = viewContainerRef.createComponent( + component, { + index: 0, + injector: undefined, + }, + ); + if (hasValue(changes)) { + this.ngOnChanges(changes); + } else { + this.connectInputsAndOutputs(); + } + } + + /** + * Connect the in and outputs of this component to the dynamic component, + * to ensure they're in sync + */ + protected connectInputsAndOutputs(): void { + if (isNotEmpty(this.inAndOutputNames) && hasValue(this.compRef) && hasValue(this.compRef.instance)) { + this.inAndOutputNames.filter((name: any) => this[name] !== undefined).forEach((name: any) => { + this.compRef.instance[name] = this[name]; + }); + } + } + +} diff --git a/src/app/dso-shared/dso-edit-metadata/dso-edit-metadata-value-field/dso-edit-metadata-value-field-loader/dso-edit-metadata-value-field-loader.directive.ts b/src/app/dso-shared/dso-edit-metadata/dso-edit-metadata-value-field/dso-edit-metadata-value-field-loader/dso-edit-metadata-value-field-loader.directive.ts new file mode 100644 index 0000000000..130a9b7616 --- /dev/null +++ b/src/app/dso-shared/dso-edit-metadata/dso-edit-metadata-value-field/dso-edit-metadata-value-field-loader/dso-edit-metadata-value-field-loader.directive.ts @@ -0,0 +1,17 @@ +import { + Directive, + ViewContainerRef, +} from '@angular/core'; + +/** + * Directive used as a hook to know where to inject the dynamic loaded component + */ +@Directive({ + selector: '[dsDsoEditMetadataValueFieldDirective]', +}) +export class DsoEditMetadataValueFieldLoaderDirective { + constructor( + public viewContainerRef: ViewContainerRef, + ) { + } +} diff --git a/src/app/dso-shared/dso-edit-metadata/dso-edit-metadata-value-field/dso-edit-metadata-value-field-loader/dso-edit-metadata-value-field.decorator.ts b/src/app/dso-shared/dso-edit-metadata/dso-edit-metadata-value-field/dso-edit-metadata-value-field-loader/dso-edit-metadata-value-field.decorator.ts new file mode 100644 index 0000000000..560d65bfbc --- /dev/null +++ b/src/app/dso-shared/dso-edit-metadata/dso-edit-metadata-value-field/dso-edit-metadata-value-field-loader/dso-edit-metadata-value-field.decorator.ts @@ -0,0 +1,56 @@ +import { hasNoValue, hasValue } from '../../../../shared/empty.util'; +import { Context } from '../../../../core/shared/context.model'; +import { resolveTheme, DEFAULT_THEME, DEFAULT_CONTEXT, } from '../../../../shared/object-collection/shared/listable-object/listable-object.decorator'; +import { EditMetadataValueFieldType } from '../dso-edit-metadata-field-type.enum'; + +export const map = new Map(); + +export const DEFAULT_EDIT_METADATA_FIELD_TYPE = EditMetadataValueFieldType.PLAIN_TEXT; + +/** + * Decorator function to store edit metadata field mapping + * + * @param type The edit metadata field type + * @param context The optional context the component represents + * @param theme The optional theme for the component + */ +export function editMetadataValueFieldComponent(type: EditMetadataValueFieldType, context: Context = DEFAULT_CONTEXT, theme = DEFAULT_THEME) { + return function decorator(component: any) { + if (hasNoValue(map.get(type))) { + map.set(type, new Map()); + } + if (hasNoValue(map.get(type).get(context))) { + map.get(type).set(context, new Map()); + } + map.get(type).get(context).set(theme, component); + }; +} + +/** + * Getter to retrieve a matching component by entity type, metadata representation and context + * + * @param type The edit metadata field type + * @param context The context to match + * @param theme the theme to match + */ +export function getDsoEditMetadataValueFieldComponent(type: EditMetadataValueFieldType, context: Context = DEFAULT_CONTEXT, theme = DEFAULT_THEME) { + if (type) { + const mapForEntity = map.get(type); + if (hasValue(mapForEntity)) { + const contextMap = mapForEntity.get(context); + if (hasValue(contextMap)) { + const match = resolveTheme(contextMap, theme); + if (hasValue(match)) { + return match; + } + if (hasValue(contextMap.get(DEFAULT_THEME))) { + return contextMap.get(DEFAULT_THEME); + } + } + if (hasValue(mapForEntity.get(DEFAULT_CONTEXT)) && hasValue(mapForEntity.get(DEFAULT_CONTEXT).get(DEFAULT_THEME))) { + return mapForEntity.get(DEFAULT_CONTEXT).get(DEFAULT_THEME); + } + } + } + return map.get(DEFAULT_EDIT_METADATA_FIELD_TYPE).get(DEFAULT_CONTEXT).get(DEFAULT_THEME); +} diff --git a/src/app/dso-shared/dso-edit-metadata/dso-edit-metadata-value/dso-edit-metadata-value.component.html b/src/app/dso-shared/dso-edit-metadata/dso-edit-metadata-value/dso-edit-metadata-value.component.html index 525b42610b..61270dfd5a 100644 --- a/src/app/dso-shared/dso-edit-metadata/dso-edit-metadata-value/dso-edit-metadata-value.component.html +++ b/src/app/dso-shared/dso-edit-metadata/dso-edit-metadata-value/dso-edit-metadata-value.component.html @@ -3,8 +3,15 @@ [ngClass]="{ 'ds-warning': mdValue.reordered || mdValue.change === DsoEditMetadataChangeTypeEnum.UPDATE, 'ds-danger': mdValue.change === DsoEditMetadataChangeTypeEnum.REMOVE, 'ds-success': mdValue.change === DsoEditMetadataChangeTypeEnum.ADD, 'h-100': isOnlyValue }">
{{ mdValue.newValue.value }}
- + + + +
{{ mdRepresentationName$ | async }} diff --git a/src/app/dso-shared/dso-edit-metadata/dso-edit-metadata-value/dso-edit-metadata-value.component.ts b/src/app/dso-shared/dso-edit-metadata/dso-edit-metadata-value/dso-edit-metadata-value.component.ts index 3fdcd381ab..9c1ca26a34 100644 --- a/src/app/dso-shared/dso-edit-metadata/dso-edit-metadata-value/dso-edit-metadata-value.component.ts +++ b/src/app/dso-shared/dso-edit-metadata/dso-edit-metadata-value/dso-edit-metadata-value.component.ts @@ -1,4 +1,4 @@ -import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; +import { Component, EventEmitter, Input, OnInit, Output, SimpleChanges, OnChanges } from '@angular/core'; import { DsoEditMetadataChangeType, DsoEditMetadataValue } from '../dso-edit-metadata-form'; import { Observable } from 'rxjs/internal/Observable'; import { @@ -12,6 +12,8 @@ import { map } from 'rxjs/operators'; import { getItemPageRoute } from '../../../item-page/item-page-routing-paths'; import { DSONameService } from '../../../core/breadcrumbs/dso-name.service'; import { EMPTY } from 'rxjs/internal/observable/empty'; +import { EditMetadataValueFieldType } from '../dso-edit-metadata-value-field/dso-edit-metadata-field-type.enum'; +import { Context } from '../../../core/shared/context.model'; @Component({ selector: 'ds-dso-edit-metadata-value', @@ -21,13 +23,21 @@ import { EMPTY } from 'rxjs/internal/observable/empty'; /** * Component displaying a single editable row for a metadata value */ -export class DsoEditMetadataValueComponent implements OnInit { +export class DsoEditMetadataValueComponent implements OnInit, OnChanges { + + @Input() context: Context; + /** * The parent {@link DSpaceObject} to display a metadata form for * Also used to determine metadata-representations in case of virtual metadata */ @Input() dso: DSpaceObject; + /** + * The metadata field that is being edited + */ + @Input() mdField: string; + /** * Editable metadata value to show */ @@ -97,6 +107,8 @@ export class DsoEditMetadataValueComponent implements OnInit { */ mdRepresentationName$: Observable; + fieldType: EditMetadataValueFieldType; + constructor(protected relationshipService: RelationshipDataService, protected dsoNameService: DSONameService) { } @@ -105,6 +117,12 @@ export class DsoEditMetadataValueComponent implements OnInit { this.initVirtualProperties(); } + ngOnChanges(changes: SimpleChanges): void { + if (changes.mdField) { + this.fieldType = this.getFieldType(); + } + } + /** * Initialise potential properties of a virtual metadata value */ @@ -123,4 +141,12 @@ export class DsoEditMetadataValueComponent implements OnInit { map((mdRepresentation: ItemMetadataRepresentation) => mdRepresentation ? this.dsoNameService.getName(mdRepresentation) : null), ); } + + /** + * Retrieves the {@link EditMetadataValueFieldType} to be displayed for the current field while in edit mode. + */ + getFieldType(): EditMetadataValueFieldType { + return EditMetadataValueFieldType.PLAIN_TEXT; + } + } diff --git a/src/app/dso-shared/dso-edit-metadata/dso-edit-metadata.component.html b/src/app/dso-shared/dso-edit-metadata/dso-edit-metadata.component.html index 24c3dc5cd7..8a50d6f125 100644 --- a/src/app/dso-shared/dso-edit-metadata/dso-edit-metadata.component.html +++ b/src/app/dso-shared/dso-edit-metadata/dso-edit-metadata.component.html @@ -36,6 +36,8 @@