97075: Margin fixes + metadata field autofocus and validation

This commit is contained in:
Kristof De Langhe
2022-12-01 13:07:19 +01:00
parent 105f6cb954
commit 6be343cccf
4 changed files with 69 additions and 23 deletions

View File

@@ -1,18 +1,18 @@
<div class="item-metadata" *ngIf="form"> <div class="item-metadata" *ngIf="form">
<div class="button-row top d-flex my-2 space-children-mr"> <div class="button-row top d-flex my-2 space-children-mr ml-gap">
<button class="mr-auto btn btn-success" [disabled]="form.newValue || (saving$ | async)" <button class="mr-auto btn btn-success" [disabled]="form.newValue || (saving$ | async)"
(click)="add()"><i class="fas fa-plus"></i> (click)="add()"><i class="fas fa-plus"></i>
<span class="d-none d-sm-inline">&nbsp;{{ dsoType + '.edit.metadata.add-button' | translate }}</span> <span class="d-none d-sm-inline">&nbsp;{{ dsoType + '.edit.metadata.add-button' | translate }}</span>
</button> </button>
<button class="btn btn-warning ml-2" *ngIf="isReinstatable" [disabled]="(saving$ | async)" <button class="btn btn-warning ml-1" *ngIf="isReinstatable" [disabled]="(saving$ | async)"
(click)="reinstate()"><i class="fas fa-undo-alt"></i> (click)="reinstate()"><i class="fas fa-undo-alt"></i>
<span class="d-none d-sm-inline">&nbsp;{{ dsoType + '.edit.metadata.reinstate-button' | translate }}</span> <span class="d-none d-sm-inline">&nbsp;{{ dsoType + '.edit.metadata.reinstate-button' | translate }}</span>
</button> </button>
<button class="btn btn-primary ml-2" [disabled]="!hasChanges || (saving$ | async)" <button class="btn btn-primary ml-1" [disabled]="!hasChanges || (saving$ | async)"
(click)="submit()"><i class="fas fa-save"></i> (click)="submit()"><i class="fas fa-save"></i>
<span class="d-none d-sm-inline">&nbsp;{{ dsoType + '.edit.metadata.save-button' | translate }}</span> <span class="d-none d-sm-inline">&nbsp;{{ dsoType + '.edit.metadata.save-button' | translate }}</span>
</button> </button>
<button class="btn btn-danger ml-2" *ngIf="!isReinstatable" <button class="btn btn-danger ml-1" *ngIf="!isReinstatable"
[disabled]="!hasChanges || (saving$ | async)" [disabled]="!hasChanges || (saving$ | async)"
(click)="discard()"><i class="fas fa-times"></i> (click)="discard()"><i class="fas fa-times"></i>
<span class="d-none d-sm-inline">&nbsp;{{ dsoType + '.edit.metadata.discard-button' | translate }}</span> <span class="d-none d-sm-inline">&nbsp;{{ dsoType + '.edit.metadata.discard-button' | translate }}</span>
@@ -31,10 +31,10 @@
</div> </div>
<div class="d-flex flex-row ds-field-row" *ngIf="form.newValue"> <div class="d-flex flex-row ds-field-row" *ngIf="form.newValue">
<div class="lbl-cell ds-success"> <div class="lbl-cell ds-success">
<ds-metadata-field-selector [(mdField)]="newMdField"></ds-metadata-field-selector> <ds-metadata-field-selector [(mdField)]="newMdField" [autofocus]="true"></ds-metadata-field-selector>
</div> </div>
<div class="flex-grow-1 ds-drop-list"> <div class="flex-grow-1 ds-drop-list">
<div class="d-flex flex-row ds-value-row ds-success"> <div class="d-flex flex-row ds-value-row ds-success h-100">
<div class="flex-grow-1 ds-flex-cell ds-value-cell d-flex align-items-center"> <div class="flex-grow-1 ds-flex-cell ds-value-cell d-flex align-items-center">
<textarea class="form-control" rows="2" [(ngModel)]="form.newValue.newValue.value"></textarea> <textarea class="form-control" rows="2" [(ngModel)]="form.newValue.newValue.value"></textarea>
</div> </div>
@@ -44,16 +44,19 @@
<div class="text-center ds-flex-cell ds-edit-cell"> <div class="text-center ds-flex-cell ds-edit-cell">
<div class="btn-group edit-field"> <div class="btn-group edit-field">
<button class="btn btn-outline-success btn-sm ng-star-inserted" ngbTooltip="Confirm changes" <button class="btn btn-outline-success btn-sm ng-star-inserted" ngbTooltip="Confirm changes"
[disabled]="!newMdField || (saving$ | async)" (click)="form.setMetadataField(newMdField); onValueSaved()"> [disabled]="!newMdField || (saving$ | async) || (loadingFieldValidation$ | async)" (click)="setMetadataField()">
<i class="fas fa-check fa-fw"></i> <i class="fas fa-check fa-fw"></i>
</button> </button>
<button class="btn btn-outline-danger btn-sm" ngbTooltip="Remove" [disabled]="true"> <button class="btn btn-outline-danger btn-sm" ngbTooltip="Remove"
[disabled]="true">
<i class="fas fa-trash-alt fa-fw"></i> <i class="fas fa-trash-alt fa-fw"></i>
</button> </button>
<button class="btn btn-outline-warning btn-sm" ngbTooltip="Undo changes" [disabled]="(saving$ | async)" (click)="form.newValue = undefined"> <button class="btn btn-outline-warning btn-sm" ngbTooltip="Undo changes"
[disabled]="(saving$ | async) || (loadingFieldValidation$ | async)" (click)="form.newValue = undefined">
<i class="fas fa-undo-alt fa-fw"></i> <i class="fas fa-undo-alt fa-fw"></i>
</button> </button>
<button class="btn btn-outline-secondary ds-drag-handle btn-sm disabled" ngbTooltip="Drag to reorder" [disabled]="true"> <button class="btn btn-outline-secondary ds-drag-handle btn-sm disabled" ngbTooltip="Drag to reorder"
[disabled]="true">
<i class="fas fa-grip-vertical fa-fw"></i> <i class="fas fa-grip-vertical fa-fw"></i>
</button> </button>
</div> </div>
@@ -109,15 +112,15 @@
<div *ngIf="isEmpty"> <div *ngIf="isEmpty">
<ds-alert [content]="dsoType + '.edit.metadata.empty'" [type]="AlertTypeEnum.Info"></ds-alert> <ds-alert [content]="dsoType + '.edit.metadata.empty'" [type]="AlertTypeEnum.Info"></ds-alert>
</div> </div>
<div class="button-row bottom"> <div class="button-row bottom d-inline-block w-100">
<div class="mt-2 float-right space-children-mr ml-gap"> <div class="mt-2 float-right space-children-mr ml-gap">
<button class="btn btn-warning ml-2" *ngIf="isReinstatable" [disabled]="(saving$ | async)" <button class="btn btn-warning" *ngIf="isReinstatable" [disabled]="(saving$ | async)"
(click)="reinstate()"><i class="fas fa-undo-alt"></i> {{ dsoType + '.edit.metadata.reinstate-button' | translate }} (click)="reinstate()"><i class="fas fa-undo-alt"></i> {{ dsoType + '.edit.metadata.reinstate-button' | translate }}
</button> </button>
<button class="btn btn-primary ml-2" [disabled]="!hasChanges || (saving$ | async)" <button class="btn btn-primary" [disabled]="!hasChanges || (saving$ | async)"
(click)="submit()"><i class="fas fa-save"></i> {{ dsoType + '.edit.metadata.save-button' | translate }} (click)="submit()"><i class="fas fa-save"></i> {{ dsoType + '.edit.metadata.save-button' | translate }}
</button> </button>
<button class="btn btn-danger ml-2" *ngIf="!isReinstatable" <button class="btn btn-danger" *ngIf="!isReinstatable"
[disabled]="!hasChanges || (saving$ | async)" [disabled]="!hasChanges || (saving$ | async)"
(click)="discard()"><i class="fas fa-times"></i> {{ dsoType + '.edit.metadata.discard-button' | translate }} (click)="discard()"><i class="fas fa-times"></i> {{ dsoType + '.edit.metadata.discard-button' | translate }}
</button> </button>

View File

@@ -1,7 +1,7 @@
import { Component, Injector, Input, OnDestroy, OnInit } from '@angular/core'; import { Component, Injector, Input, OnDestroy, OnInit, ViewChild } from '@angular/core';
import { AlertType } from '../../shared/alert/aletr-type'; import { AlertType } from '../../shared/alert/aletr-type';
import { DSpaceObject } from '../../core/shared/dspace-object.model'; import { DSpaceObject } from '../../core/shared/dspace-object.model';
import { DsoEditMetadataChangeType, DsoEditMetadataForm } from './dso-edit-metadata-form'; import { DsoEditMetadataChangeType, DsoEditMetadataForm, DsoEditMetadataValue } from './dso-edit-metadata-form';
import { map, switchMap } from 'rxjs/operators'; import { map, switchMap } from 'rxjs/operators';
import { ActivatedRoute, Data } from '@angular/router'; import { ActivatedRoute, Data } from '@angular/router';
import { combineLatest as observableCombineLatest } from 'rxjs/internal/observable/combineLatest'; import { combineLatest as observableCombineLatest } from 'rxjs/internal/observable/combineLatest';
@@ -23,6 +23,7 @@ import { ResourceType } from '../../core/shared/resource-type';
import { NotificationsService } from '../../shared/notifications/notifications.service'; import { NotificationsService } from '../../shared/notifications/notifications.service';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { DataService } from '../../core/data/data.service'; import { DataService } from '../../core/data/data.service';
import { MetadataFieldSelectorComponent } from './metadata-field-selector/metadata-field-selector.component';
@Component({ @Component({
selector: 'ds-dso-edit-metadata', selector: 'ds-dso-edit-metadata',
@@ -31,6 +32,7 @@ import { DataService } from '../../core/data/data.service';
}) })
export class DsoEditMetadataComponent implements OnInit, OnDestroy { export class DsoEditMetadataComponent implements OnInit, OnDestroy {
@Input() dso: DSpaceObject; @Input() dso: DSpaceObject;
@ViewChild(MetadataFieldSelectorComponent) metadataFieldSelectorComponent: MetadataFieldSelectorComponent;
updateDataService: UpdateDataService<DSpaceObject>; updateDataService: UpdateDataService<DSpaceObject>;
dsoType: string; dsoType: string;
@@ -41,6 +43,7 @@ export class DsoEditMetadataComponent implements OnInit, OnDestroy {
hasChanges: boolean; hasChanges: boolean;
isEmpty: boolean; isEmpty: boolean;
saving$: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false); saving$: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
loadingFieldValidation$: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
/** /**
* The AlertType enumeration for access in the component's template * The AlertType enumeration for access in the component's template
@@ -120,6 +123,17 @@ export class DsoEditMetadataComponent implements OnInit, OnDestroy {
}); });
} }
setMetadataField() {
this.loadingFieldValidation$.next(true);
this.metadataFieldSelectorComponent.validate().subscribe((valid) => {
this.loadingFieldValidation$.next(false);
if (valid) {
this.form.setMetadataField(this.newMdField);
this.onValueSaved();
}
});
}
add(): void { add(): void {
this.newMdField = undefined; this.newMdField = undefined;
this.form.add(); this.form.add();

View File

@@ -1,11 +1,12 @@
<div class="w-100 position-relative"> <div class="w-100 position-relative">
<input type="text" <input type="text" #mdFieldInput
class="form-control" class="form-control" [ngClass]="{ 'is-invalid': showInvalid }"
[value]="mdField" [value]="mdField"
[formControl]="input" [formControl]="input"
(focusin)="query$.next(mdField)" (focusin)="query$.next(mdField)"
(dsClickOutside)="query$.next(null)" (dsClickOutside)="query$.next(null)"
(click)="$event.stopPropagation();" /> (click)="$event.stopPropagation();" />
<div class="invalid-feedback show-feedback" *ngIf="showInvalid">Invalid metadata field, please pick an existing one from the suggestions when searching</div>
<div class="autocomplete dropdown-menu" [ngClass]="{'show': (mdFieldOptions$ | async)?.length > 0}"> <div class="autocomplete dropdown-menu" [ngClass]="{'show': (mdFieldOptions$ | async)?.length > 0}">
<div class="dropdown-list"> <div class="dropdown-list">
<div *ngFor="let mdFieldOption of (mdFieldOptions$ | async)"> <div *ngFor="let mdFieldOption of (mdFieldOptions$ | async)">

View File

@@ -1,15 +1,25 @@
import { Component, EventEmitter, Input, OnDestroy, OnInit, Output } from '@angular/core'; import {
import { switchMap, debounceTime, distinctUntilChanged } from 'rxjs/operators'; AfterViewInit,
Component,
ElementRef,
EventEmitter,
Input,
OnDestroy,
OnInit,
Output,
ViewChild
} from '@angular/core';
import { switchMap, debounceTime, distinctUntilChanged, map, tap, take } from 'rxjs/operators';
import { followLink } from '../../../shared/utils/follow-link-config.model'; import { followLink } from '../../../shared/utils/follow-link-config.model';
import { import {
getAllSucceededRemoteData, getAllSucceededRemoteData, getFirstCompletedRemoteData, getFirstSucceededRemoteData,
metadataFieldsToString metadataFieldsToString
} from '../../../core/shared/operators'; } from '../../../core/shared/operators';
import { Observable } from 'rxjs/internal/Observable'; import { Observable } from 'rxjs/internal/Observable';
import { RegistryService } from '../../../core/registry/registry.service'; import { RegistryService } from '../../../core/registry/registry.service';
import { FormControl } from '@angular/forms'; import { FormControl } from '@angular/forms';
import { BehaviorSubject } from 'rxjs/internal/BehaviorSubject'; import { BehaviorSubject } from 'rxjs/internal/BehaviorSubject';
import { hasValue } from '../../../shared/empty.util'; import { hasValue, isNotEmpty } from '../../../shared/empty.util';
import { Subscription } from 'rxjs/internal/Subscription'; import { Subscription } from 'rxjs/internal/Subscription';
@Component({ @Component({
@@ -17,9 +27,11 @@ import { Subscription } from 'rxjs/internal/Subscription';
styleUrls: ['./metadata-field-selector.component.scss'], styleUrls: ['./metadata-field-selector.component.scss'],
templateUrl: './metadata-field-selector.component.html' templateUrl: './metadata-field-selector.component.html'
}) })
export class MetadataFieldSelectorComponent implements OnInit, OnDestroy { export class MetadataFieldSelectorComponent implements OnInit, OnDestroy, AfterViewInit {
@Input() mdField: string; @Input() mdField: string;
@Input() autofocus = false;
@Output() mdFieldChange = new EventEmitter<string>(); @Output() mdFieldChange = new EventEmitter<string>();
@ViewChild('mdFieldInput', { static: true }) mdFieldInput: ElementRef;
mdFieldOptions$: Observable<string[]>; mdFieldOptions$: Observable<string[]>;
public input: FormControl = new FormControl(); public input: FormControl = new FormControl();
@@ -27,6 +39,7 @@ export class MetadataFieldSelectorComponent implements OnInit, OnDestroy {
query$: BehaviorSubject<string> = new BehaviorSubject<string>(null); query$: BehaviorSubject<string> = new BehaviorSubject<string>(null);
debounceTime = 300; debounceTime = 300;
selectedValueLoading = false; selectedValueLoading = false;
showInvalid = false;
subs: Subscription[] = []; subs: Subscription[] = [];
@@ -49,6 +62,7 @@ export class MetadataFieldSelectorComponent implements OnInit, OnDestroy {
this.mdFieldOptions$ = this.query$.pipe( this.mdFieldOptions$ = this.query$.pipe(
distinctUntilChanged(), distinctUntilChanged(),
switchMap((query) => { switchMap((query) => {
this.showInvalid = false;
if (query !== null) { if (query !== null) {
return this.registryService.queryMetadataFields(query, null, true, false, followLink('schema')).pipe( return this.registryService.queryMetadataFields(query, null, true, false, followLink('schema')).pipe(
getAllSucceededRemoteData(), getAllSucceededRemoteData(),
@@ -61,6 +75,20 @@ export class MetadataFieldSelectorComponent implements OnInit, OnDestroy {
); );
} }
ngAfterViewInit(): void {
this.mdFieldInput.nativeElement.focus();
}
validate(): Observable<boolean> {
return this.registryService.queryMetadataFields(this.mdField, null, true, false, followLink('schema')).pipe(
getFirstSucceededRemoteData(),
metadataFieldsToString(),
take(1),
map((fields: string[]) => fields.indexOf(this.mdField) > -1),
tap((exists) => this.showInvalid = !exists),
);
}
select(mdFieldOption: string) { select(mdFieldOption: string) {
this.selectedValueLoading = true; this.selectedValueLoading = true;
this.input.setValue(mdFieldOption); this.input.setValue(mdFieldOption);