diff --git a/src/app/core/shared/context.model.ts b/src/app/core/shared/context.model.ts index dcebf5794cc..2cf947f4807 100644 --- a/src/app/core/shared/context.model.ts +++ b/src/app/core/shared/context.model.ts @@ -41,4 +41,10 @@ export enum Context { Bitstream = 'bitstream', CoarNotify = 'coarNotify', + + /** + * 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 aee9fb980cf..d332fc7e9f2 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-entity-field/dso-edit-metadata-entity-field.component.html b/src/app/dso-shared/dso-edit-metadata/dso-edit-metadata-value-field/dso-edit-metadata-entity-field/dso-edit-metadata-entity-field.component.html new file mode 100644 index 00000000000..514f3147bb5 --- /dev/null +++ b/src/app/dso-shared/dso-edit-metadata/dso-edit-metadata-value-field/dso-edit-metadata-entity-field/dso-edit-metadata-entity-field.component.html @@ -0,0 +1,5 @@ + diff --git a/src/app/dso-shared/dso-edit-metadata/dso-edit-metadata-value-field/dso-edit-metadata-entity-field/dso-edit-metadata-entity-field.component.scss b/src/app/dso-shared/dso-edit-metadata/dso-edit-metadata-value-field/dso-edit-metadata-entity-field/dso-edit-metadata-entity-field.component.scss new file mode 100644 index 00000000000..e69de29bb2d diff --git a/src/app/dso-shared/dso-edit-metadata/dso-edit-metadata-value-field/dso-edit-metadata-entity-field/dso-edit-metadata-entity-field.component.spec.ts b/src/app/dso-shared/dso-edit-metadata/dso-edit-metadata-value-field/dso-edit-metadata-entity-field/dso-edit-metadata-entity-field.component.spec.ts new file mode 100644 index 00000000000..83bd25cc763 --- /dev/null +++ b/src/app/dso-shared/dso-edit-metadata/dso-edit-metadata-value-field/dso-edit-metadata-entity-field/dso-edit-metadata-entity-field.component.spec.ts @@ -0,0 +1,36 @@ +import { + ComponentFixture, + TestBed, +} from '@angular/core/testing'; + +import { EntityTypeDataService } from '../../../../core/data/entity-type-data.service'; +import { EntityTypeDataServiceStub } from '../../../../shared/testing/entity-type-data.service.stub'; +import { DsoEditMetadataEntityFieldComponent } from './dso-edit-metadata-entity-field.component'; + +describe('DsoEditMetadataEntityFieldComponent', () => { + let component: DsoEditMetadataEntityFieldComponent; + let fixture: ComponentFixture; + + let entityTypeService: EntityTypeDataServiceStub; + + beforeEach(async () => { + entityTypeService = new EntityTypeDataServiceStub(); + + await TestBed.configureTestingModule({ + declarations: [ + DsoEditMetadataEntityFieldComponent, + ], + providers: [ + { provide: EntityTypeDataService, useValue: entityTypeService }, + ], + }).compileComponents(); + + fixture = TestBed.createComponent(DsoEditMetadataEntityFieldComponent); + 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-entity-field/dso-edit-metadata-entity-field.component.ts b/src/app/dso-shared/dso-edit-metadata/dso-edit-metadata-value-field/dso-edit-metadata-entity-field/dso-edit-metadata-entity-field.component.ts new file mode 100644 index 00000000000..73cf16b2dab --- /dev/null +++ b/src/app/dso-shared/dso-edit-metadata/dso-edit-metadata-value-field/dso-edit-metadata-entity-field/dso-edit-metadata-entity-field.component.ts @@ -0,0 +1,42 @@ +import { + Component, + OnInit, +} from '@angular/core'; +import { Observable } from 'rxjs'; + +import { EntityTypeDataService } from '../../../../core/data/entity-type-data.service'; +import { ItemType } from '../../../../core/shared/item-relationships/item-type.model'; +import { getFirstSucceededRemoteListPayload } from '../../../../core/shared/operators'; +import { AbstractDsoEditMetadataValueFieldComponent } from '../abstract-dso-edit-metadata-value-field.component'; +import { EditMetadataValueFieldType } from '../dso-edit-metadata-field-type.enum'; +import { editMetadataValueFieldComponent } from '../dso-edit-metadata-value-field-loader/dso-edit-metadata-value-field.decorator'; + +/** + * The component used to gather input for entity-type metadata fields + */ +@Component({ + selector: 'ds-dso-edit-metadata-entity-field', + templateUrl: './dso-edit-metadata-entity-field.component.html', + styleUrls: ['./dso-edit-metadata-entity-field.component.scss'], +}) +@editMetadataValueFieldComponent(EditMetadataValueFieldType.ENTITY_TYPE) +export class DsoEditMetadataEntityFieldComponent extends AbstractDsoEditMetadataValueFieldComponent implements OnInit { + + /** + * List of all the existing entity types + */ + entities$: Observable; + + constructor( + protected entityTypeService: EntityTypeDataService, + ) { + super(); + } + + ngOnInit(): void { + this.entities$ = this.entityTypeService.findAll({ elementsPerPage: 100, currentPage: 1 }).pipe( + getFirstSucceededRemoteListPayload(), + ); + } + +} 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 00000000000..5a9b3c493ef --- /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,7 @@ +/** + * The edit metadata field tab types + */ +export enum EditMetadataValueFieldType { + PLAIN_TEXT = 'PLAIN_TEXT', + ENTITY_TYPE = 'ENTITY_TYPE', +} 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 00000000000..97e49ae39e9 --- /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 00000000000..e69de29bb2d 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 00000000000..f824cdd56d5 --- /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,27 @@ +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 00000000000..7935c53ab10 --- /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,17 @@ +import { Component } from '@angular/core'; + +import { AbstractDsoEditMetadataValueFieldComponent } from '../abstract-dso-edit-metadata-value-field.component'; +import { EditMetadataValueFieldType } from '../dso-edit-metadata-field-type.enum'; +import { editMetadataValueFieldComponent } from '../dso-edit-metadata-value-field-loader/dso-edit-metadata-value-field.decorator'; + +/** + * 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 00000000000..4918c3ed9a1 --- /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 00000000000..e9a82bc9bc4 --- /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,166 @@ +import { + Component, + ComponentRef, + EventEmitter, + Input, + OnChanges, + OnDestroy, + OnInit, + Output, + SimpleChanges, + ViewChild, + ViewContainerRef, +} from '@angular/core'; +import { Subscription } from 'rxjs'; + +import { Context } from '../../../../core/shared/context.model'; +import { DSpaceObject } from '../../../../core/shared/dspace-object.model'; +import { GenericConstructor } from '../../../../core/shared/generic-constructor'; +import { + hasNoValue, + hasValue, + isNotEmpty, +} from '../../../../shared/empty.util'; +import { ThemeService } from '../../../../shared/theme-support/theme.service'; +import { DsoEditMetadataValue } from '../../dso-edit-metadata-form'; +import { EditMetadataValueFieldType } from '../dso-edit-metadata-field-type.enum'; +import { getDsoEditMetadataValueFieldComponent } from './dso-edit-metadata-value-field.decorator'; +import { DsoEditMetadataValueFieldLoaderDirective } from './dso-edit-metadata-value-field-loader.directive'; + +@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 00000000000..130a9b7616b --- /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 00000000000..ed0193b6456 --- /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,63 @@ +import { Context } from '../../../../core/shared/context.model'; +import { + hasNoValue, + hasValue, +} from '../../../../shared/empty.util'; +import { + DEFAULT_CONTEXT, + DEFAULT_THEME, + resolveTheme, +} 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.ts b/src/app/dso-shared/dso-edit-metadata/dso-edit-metadata-value/dso-edit-metadata-value.component.ts index 11bbcfd9d5e..6930dbcd619 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 @@ -50,6 +50,7 @@ import { RelationshipDataService } from '../../../core/data/relationship-data.se import { MetadataService } from '../../../core/metadata/metadata.service'; import { Collection } from '../../../core/shared/collection.model'; import { ConfidenceType } from '../../../core/shared/confidence-type'; +import { Context } from '../../../core/shared/context.model'; import { DSpaceObject } from '../../../core/shared/dspace-object.model'; import { Item } from '../../../core/shared/item.model'; import { ItemMetadataRepresentation } from '../../../core/shared/metadata-representation/item/item-metadata-representation.model'; @@ -88,6 +89,7 @@ import { DsoEditMetadataChangeType, DsoEditMetadataValue, } from '../dso-edit-metadata-form'; +import { EditMetadataValueFieldType } from '../dso-edit-metadata-value-field/dso-edit-metadata-field-type.enum'; @Component({ selector: 'ds-dso-edit-metadata-value', @@ -100,12 +102,20 @@ import { * Component displaying a single editable row for a metadata value */ 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 */ @@ -129,11 +139,6 @@ export class DsoEditMetadataValueComponent implements OnInit, OnChanges { */ @Input() isOnlyValue = false; - /** - * MetadataField to edit - */ - @Input() mdField?: string; - /** * Emits when the user clicked edit */ @@ -186,6 +191,11 @@ export class DsoEditMetadataValueComponent implements OnInit, OnChanges { */ mdRepresentationName$: Observable; + /** + * The type of edit field that should be displayed + */ + fieldType: EditMetadataValueFieldType; + /** * Whether or not the authority field is currently being edited */ @@ -233,6 +243,12 @@ export class DsoEditMetadataValueComponent implements OnInit, OnChanges { this.initAuthorityProperties(); } + ngOnChanges(changes: SimpleChanges): void { + if (changes.mdField) { + this.fieldType = this.getFieldType(); + } + } + /** * Initialise potential properties of a virtual metadata value */ @@ -252,6 +268,17 @@ export class DsoEditMetadataValueComponent implements OnInit, OnChanges { ); } + /** + * Retrieves the {@link EditMetadataValueFieldType} to be displayed for the current field while in edit mode. + */ + getFieldType(): EditMetadataValueFieldType { + if (this.mdField === 'dspace.entity.type') { + return EditMetadataValueFieldType.ENTITY_TYPE; + } + return EditMetadataValueFieldType.PLAIN_TEXT; + } + + /** * Initialise potential properties of a authority controlled metadata field */ 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 f8b193f4a05..659f00e5398 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 @@ -40,6 +40,8 @@
[]): Observable>> { + return createSuccessfulRemoteDataObject$(createPaginatedList()); + } + }