mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-08 10:34:15 +00:00
finalised edit item page
This commit is contained in:
@@ -188,7 +188,7 @@
|
||||
"confirm": "Withdraw",
|
||||
"cancel": "Cancel",
|
||||
"success": "The item was withdrawn successfully",
|
||||
"error": "An error occured while withdrawing the item"
|
||||
"error": "An error occurred while withdrawing the item"
|
||||
},
|
||||
"reinstate": {
|
||||
"header": "Reinstate item: {{ id }}",
|
||||
@@ -196,7 +196,7 @@
|
||||
"confirm": "Reinstate",
|
||||
"cancel": "Cancel",
|
||||
"success": "The item was reinstated successfully",
|
||||
"error": "An error occured while reinstating the item"
|
||||
"error": "An error occurred while reinstating the item"
|
||||
},
|
||||
"private": {
|
||||
"header": "Make item private: {{ id }}",
|
||||
@@ -204,7 +204,7 @@
|
||||
"confirm": "Make it Private",
|
||||
"cancel": "Cancel",
|
||||
"success": "The item is now private",
|
||||
"error": "An error occured while making the item private"
|
||||
"error": "An error occurred while making the item private"
|
||||
},
|
||||
"public": {
|
||||
"header": "Make item public: {{ id }}",
|
||||
@@ -212,7 +212,7 @@
|
||||
"confirm": "Make it Public",
|
||||
"cancel": "Cancel",
|
||||
"success": "The item is now public",
|
||||
"error": "An error occured while making the item public"
|
||||
"error": "An error occurred while making the item public"
|
||||
},
|
||||
"delete": {
|
||||
"header": "Delete item: {{ id }}",
|
||||
@@ -220,7 +220,7 @@
|
||||
"confirm": "Delete",
|
||||
"cancel": "Cancel",
|
||||
"success": "The item has been deleted",
|
||||
"error": "An error occured while deleting the item"
|
||||
"error": "An error occurred while deleting the item"
|
||||
},
|
||||
"metadata": {
|
||||
"add-button": "Add",
|
||||
@@ -233,6 +233,14 @@
|
||||
"language": "Lang",
|
||||
"edit": "Edit"
|
||||
},
|
||||
"edit": {
|
||||
"buttons": {
|
||||
"edit": "Edit",
|
||||
"unedit": "Stop editing",
|
||||
"remove": "Remove",
|
||||
"undo": "Undo changes"
|
||||
}
|
||||
},
|
||||
"metadatafield": {
|
||||
"invalid": "Please choose a valid metadata field"
|
||||
},
|
||||
@@ -247,7 +255,7 @@
|
||||
},
|
||||
"invalid": {
|
||||
"title": "Metadata invalid",
|
||||
"content": "Please make sure all fields are valid"
|
||||
"content": "Your changes were not saved. Please make sure all fields are valid before you save."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,37 +1,21 @@
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<h2 class="border-bottom">{{'item.edit.head' | translate}}</h2>
|
||||
<div class="pt-2">
|
||||
<ngb-tabset [activeId]="(params$ | async)?.page || 'status'">
|
||||
<ngb-tab [id]="'status'" title="{{'item.edit.tabs.status.head' | translate}}">
|
||||
<ng-template ngbTabContent>
|
||||
<ds-item-status [item]="(itemRD$ | async)?.payload"></ds-item-status>
|
||||
</ng-template>
|
||||
</ngb-tab>
|
||||
<ngb-tab [id]="'bitstreams'" title="{{'item.edit.tabs.bitstreams.head' | translate}}">
|
||||
<ng-template ngbTabContent>
|
||||
|
||||
</ng-template>
|
||||
</ngb-tab>
|
||||
<ngb-tab [id]="'metadata'" title="{{'item.edit.tabs.metadata.head' | translate}}">
|
||||
<ng-template ngbTabContent>
|
||||
<ds-item-metadata [item]="(itemRD$ | async)?.payload">
|
||||
</ds-item-metadata>
|
||||
</ng-template>
|
||||
</ngb-tab>
|
||||
<ngb-tab [id]="'view'" title="{{'item.edit.tabs.view.head' | translate}}">
|
||||
<ng-template ngbTabContent>
|
||||
|
||||
</ng-template>
|
||||
</ngb-tab>
|
||||
<ngb-tab [id]="'curate'" title="{{'item.edit.tabs.curate.head' | translate}}">
|
||||
<ng-template ngbTabContent>
|
||||
|
||||
</ng-template>
|
||||
</ngb-tab>
|
||||
</ngb-tabset>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<h2 class="border-bottom">{{'item.edit.head' | translate}}</h2>
|
||||
<div class="pt-2">
|
||||
<ul class="nav nav-tabs justify-content-start">
|
||||
<li *ngFor="let page of pages" class="nav-item">
|
||||
<a class="nav-link"
|
||||
[ngClass]="{'active' : page === currentPage}"
|
||||
[routerLink]="'/items/' + (itemRD$ | async)?.payload.uuid + '/edit/' + page">
|
||||
{{'item.edit.tabs.' + page + '.head' | translate}}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="tab-pane active">
|
||||
<router-outlet></router-outlet>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -1,10 +1,11 @@
|
||||
import {fadeIn, fadeInOut} from '../../shared/animations/fade';
|
||||
import {ChangeDetectionStrategy, Component, OnInit} from '@angular/core';
|
||||
import { ActivatedRoute, Params } from '@angular/router';
|
||||
import {RemoteData} from '../../core/data/remote-data';
|
||||
import {Item} from '../../core/shared/item.model';
|
||||
import {Observable} from 'rxjs';
|
||||
import {map} from 'rxjs/operators';
|
||||
import { fadeIn, fadeInOut } from '../../shared/animations/fade';
|
||||
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
|
||||
import { ActivatedRoute, Params, Router } from '@angular/router';
|
||||
import { RemoteData } from '../../core/data/remote-data';
|
||||
import { Item } from '../../core/shared/item.model';
|
||||
import { Observable } from 'rxjs';
|
||||
import { map } from 'rxjs/operators';
|
||||
import { isNotEmpty } from '../../shared/empty.util';
|
||||
|
||||
@Component({
|
||||
selector: 'ds-edit-item-page',
|
||||
@@ -24,13 +25,27 @@ export class EditItemPageComponent implements OnInit {
|
||||
* The item to edit
|
||||
*/
|
||||
itemRD$: Observable<RemoteData<Item>>;
|
||||
params$: Observable<Params>;
|
||||
constructor(private route: ActivatedRoute) {
|
||||
|
||||
/**
|
||||
* The current page outlet string
|
||||
*/
|
||||
currentPage: string;
|
||||
|
||||
/**
|
||||
* All possible page outlet strings
|
||||
*/
|
||||
pages: string[];
|
||||
|
||||
constructor(private route: ActivatedRoute, private router: Router) {
|
||||
this.router.events.subscribe(() => {
|
||||
this.currentPage = this.route.snapshot.firstChild.routeConfig.path;
|
||||
});
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.pages = this.route.routeConfig.children
|
||||
.map((child: any) => child.path)
|
||||
.filter((path: string) => isNotEmpty(path)); // ignore reroutes
|
||||
this.itemRD$ = this.route.data.pipe(map((data) => data.item));
|
||||
this.params$ = this.route.params;
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -1,12 +1,14 @@
|
||||
import {ItemPageResolver} from '../item-page.resolver';
|
||||
import {NgModule} from '@angular/core';
|
||||
import {RouterModule} from '@angular/router';
|
||||
import {EditItemPageComponent} from './edit-item-page.component';
|
||||
import {ItemWithdrawComponent} from './item-withdraw/item-withdraw.component';
|
||||
import {ItemReinstateComponent} from './item-reinstate/item-reinstate.component';
|
||||
import {ItemPrivateComponent} from './item-private/item-private.component';
|
||||
import {ItemPublicComponent} from './item-public/item-public.component';
|
||||
import {ItemDeleteComponent} from './item-delete/item-delete.component';
|
||||
import { ItemPageResolver } from '../item-page.resolver';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule } from '@angular/router';
|
||||
import { EditItemPageComponent } from './edit-item-page.component';
|
||||
import { ItemWithdrawComponent } from './item-withdraw/item-withdraw.component';
|
||||
import { ItemReinstateComponent } from './item-reinstate/item-reinstate.component';
|
||||
import { ItemPrivateComponent } from './item-private/item-private.component';
|
||||
import { ItemPublicComponent } from './item-public/item-public.component';
|
||||
import { ItemDeleteComponent } from './item-delete/item-delete.component';
|
||||
import { ItemStatusComponent } from './item-status/item-status.component';
|
||||
import { ItemMetadataComponent } from './item-metadata/item-metadata.component';
|
||||
|
||||
const ITEM_EDIT_WITHDRAW_PATH = 'withdraw';
|
||||
const ITEM_EDIT_REINSTATE_PATH = 'reinstate';
|
||||
@@ -25,7 +27,36 @@ const ITEM_EDIT_DELETE_PATH = 'delete';
|
||||
component: EditItemPageComponent,
|
||||
resolve: {
|
||||
item: ItemPageResolver
|
||||
}
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
redirectTo: 'status'
|
||||
},
|
||||
{
|
||||
path: 'status',
|
||||
component: ItemStatusComponent
|
||||
},
|
||||
{
|
||||
path: 'bitstreams',
|
||||
/* TODO - change when bitstreams page exists */
|
||||
component: ItemStatusComponent
|
||||
},
|
||||
{
|
||||
path: 'metadata',
|
||||
component: ItemMetadataComponent
|
||||
},
|
||||
{
|
||||
path: 'view',
|
||||
/* TODO - change when view page exists */
|
||||
component: ItemStatusComponent
|
||||
},
|
||||
{
|
||||
path: 'curate',
|
||||
/* TODO - change when curate page exists */
|
||||
component: ItemStatusComponent
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
path: ITEM_EDIT_WITHDRAW_PATH,
|
||||
|
@@ -7,7 +7,7 @@
|
||||
<!--{{metadata?.uuid}}-->
|
||||
<td class="col-3">
|
||||
<div *ngIf="!(editable | async)">
|
||||
<span>{{metadata?.key}}</span>
|
||||
<span>{{metadata?.key?.split('.').join('.​')}}</span>
|
||||
</div>
|
||||
<div *ngIf="(editable | async)" class="field-container">
|
||||
<ds-input-suggestions [suggestions]="(metadataFieldSuggestions | async)"
|
||||
@@ -15,9 +15,12 @@
|
||||
(submitSuggestion)="update(suggestionControl.control)"
|
||||
(clickSuggestion)="update(suggestionControl.control)"
|
||||
(typeSuggestion)="update(suggestionControl.control)"
|
||||
(blur)="checkValidity(suggestionControl.control)"
|
||||
(findSuggestions)="findMetadataFieldSuggestions($event)"
|
||||
#suggestionControl="ngModel"
|
||||
[dsInListValidator]="metadataFields | async"
|
||||
[valid]="(valid | async)"
|
||||
dsAutoFocus autoFocusSelector=".suggestion_input"
|
||||
></ds-input-suggestions>
|
||||
</div>
|
||||
<small class="text-danger"
|
||||
@@ -43,17 +46,17 @@
|
||||
</td>
|
||||
<td class="col-2 text-center">
|
||||
<div class="btn-group">
|
||||
<button [disabled]="!(canSetEditable() | async)" *ngIf="!(editable | async)" (click)="setEditable(true)" class="btn btn-light btn-sm">
|
||||
<i class="fas fa-edit fa-fw text-primary"></i>
|
||||
<button [disabled]="!(canSetEditable() | async)" *ngIf="!(editable | async)" (click)="setEditable(true)" class="btn btn-primary btn-sm" title="{{'item.edit.metadata.edit.buttons.edit' | translate}}">
|
||||
<i class="fas fa-edit fa-fw"></i>
|
||||
</button>
|
||||
<button [disabled]="!(canSetUneditable() | async)" *ngIf="(editable | async)" (click)="setEditable(false)" class="btn btn-light btn-sm">
|
||||
<i class="fas fa-check fa-fw text-success"></i>
|
||||
<button [disabled]="!(canSetUneditable() | async)" *ngIf="(editable | async)" (click)="setEditable(false)" class="btn btn-success btn-sm" title="{{'item.edit.metadata.edit.buttons.unedit' | translate}}">
|
||||
<i class="fas fa-check fa-fw"></i>
|
||||
</button>
|
||||
<button [disabled]="!(canRemove() | async)" (click)="remove()" class="btn btn-light btn-sm">
|
||||
<i class="fas fa-trash-alt fa-fw text-danger"></i>
|
||||
<button [disabled]="!(canRemove() | async)" (click)="remove()" class="btn btn-danger btn-sm" title="{{'item.edit.metadata.edit.buttons.remove' | translate}}">
|
||||
<i class="fas fa-trash-alt fa-fw"></i>
|
||||
</button>
|
||||
<button [disabled]="!(canUndo() | async)" (click)="removeChangesFromField()" class="btn btn-light btn-sm">
|
||||
<i class="fas fa-undo-alt fa-fw text-warning"></i>
|
||||
<button [disabled]="!(canUndo() | async)" (click)="removeChangesFromField()" class="btn btn-warning btn-sm" title="{{'item.edit.metadata.edit.buttons.undo' | translate}}">
|
||||
<i class="fas fa-undo-alt fa-fw"></i>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
|
@@ -44,7 +44,7 @@ const metadatum = Object.assign(new Metadatum(), {
|
||||
language: 'en'
|
||||
});
|
||||
|
||||
const route = 'http://test-url.com/test-url';
|
||||
const url = 'http://test-url.com/test-url';
|
||||
const fieldUpdate = {
|
||||
field: metadatum,
|
||||
changeType: undefined
|
||||
@@ -92,7 +92,7 @@ describe('EditInPlaceFieldComponent', () => {
|
||||
de = fixture.debugElement.query(By.css('div.d-flex'));
|
||||
el = de.nativeElement;
|
||||
|
||||
comp.route = route;
|
||||
comp.url = url;
|
||||
comp.fieldUpdate = fieldUpdate;
|
||||
comp.metadata = metadatum;
|
||||
|
||||
@@ -104,8 +104,8 @@ describe('EditInPlaceFieldComponent', () => {
|
||||
comp.update();
|
||||
});
|
||||
|
||||
it('it should call saveChangeFieldUpdate on the objectUpdatesService with the correct route and metadata', () => {
|
||||
expect(objectUpdatesService.saveChangeFieldUpdate).toHaveBeenCalledWith(route, metadatum);
|
||||
it('it should call saveChangeFieldUpdate on the objectUpdatesService with the correct url and metadata', () => {
|
||||
expect(objectUpdatesService.saveChangeFieldUpdate).toHaveBeenCalledWith(url, metadatum);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -145,8 +145,8 @@ describe('EditInPlaceFieldComponent', () => {
|
||||
comp.setEditable(editable);
|
||||
});
|
||||
|
||||
it('it should call setEditableFieldUpdate on the objectUpdatesService with the correct route and uuid and false', () => {
|
||||
expect(objectUpdatesService.setEditableFieldUpdate).toHaveBeenCalledWith(route, metadatum.uuid, editable);
|
||||
it('it should call setEditableFieldUpdate on the objectUpdatesService with the correct url and uuid and false', () => {
|
||||
expect(objectUpdatesService.setEditableFieldUpdate).toHaveBeenCalledWith(url, metadatum.uuid, editable);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -203,8 +203,8 @@ describe('EditInPlaceFieldComponent', () => {
|
||||
comp.remove();
|
||||
});
|
||||
|
||||
it('it should call saveRemoveFieldUpdate on the objectUpdatesService with the correct route and metadata', () => {
|
||||
expect(objectUpdatesService.saveRemoveFieldUpdate).toHaveBeenCalledWith(route, metadatum);
|
||||
it('it should call saveRemoveFieldUpdate on the objectUpdatesService with the correct url and metadata', () => {
|
||||
expect(objectUpdatesService.saveRemoveFieldUpdate).toHaveBeenCalledWith(url, metadatum);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -213,8 +213,8 @@ describe('EditInPlaceFieldComponent', () => {
|
||||
comp.removeChangesFromField();
|
||||
});
|
||||
|
||||
it('it should call removeChangesFromField on the objectUpdatesService with the correct route and uuid', () => {
|
||||
expect(objectUpdatesService.removeSingleFieldUpdate).toHaveBeenCalledWith(route, metadatum.uuid);
|
||||
it('it should call removeChangesFromField on the objectUpdatesService with the correct url and uuid', () => {
|
||||
expect(objectUpdatesService.removeSingleFieldUpdate).toHaveBeenCalledWith(url, metadatum.uuid);
|
||||
});
|
||||
});
|
||||
|
||||
|
@@ -10,7 +10,6 @@ import { InputSuggestion } from '../../../../shared/input-suggestions/input-sugg
|
||||
import { FieldChangeType } from '../../../../core/data/object-updates/object-updates.actions';
|
||||
import { FieldUpdate } from '../../../../core/data/object-updates/object-updates.reducer';
|
||||
import { ObjectUpdatesService } from '../../../../core/data/object-updates/object-updates.service';
|
||||
import { inListValidator } from '../../../../shared/utils/validator.functions';
|
||||
import { getSucceededRemoteData } from '../../../../core/shared/operators';
|
||||
import { FormControl } from '@angular/forms';
|
||||
|
||||
@@ -28,9 +27,9 @@ export class EditInPlaceFieldComponent implements OnInit, OnChanges {
|
||||
*/
|
||||
@Input() fieldUpdate: FieldUpdate;
|
||||
/**
|
||||
* The current route of this page
|
||||
* The current url of this page
|
||||
*/
|
||||
@Input() route: string;
|
||||
@Input() url: string;
|
||||
/**
|
||||
* The metadatum of this field
|
||||
*/
|
||||
@@ -65,8 +64,8 @@ export class EditInPlaceFieldComponent implements OnInit, OnChanges {
|
||||
* Sets up an observable that keeps track of the current editable and valid state of this field
|
||||
*/
|
||||
ngOnInit(): void {
|
||||
this.editable = this.objectUpdatesService.isEditable(this.route, this.metadata.uuid);
|
||||
this.valid = this.objectUpdatesService.isValid(this.route, this.metadata.uuid);
|
||||
this.editable = this.objectUpdatesService.isEditable(this.url, this.metadata.uuid);
|
||||
this.valid = this.objectUpdatesService.isValid(this.url, this.metadata.uuid);
|
||||
this.metadataFields = this.findMetadataFields()
|
||||
}
|
||||
|
||||
@@ -74,32 +73,41 @@ export class EditInPlaceFieldComponent implements OnInit, OnChanges {
|
||||
* Sends a new change update for this field to the object updates service
|
||||
*/
|
||||
update(control?: FormControl) {
|
||||
this.objectUpdatesService.saveChangeFieldUpdate(this.route, this.metadata);
|
||||
this.objectUpdatesService.saveChangeFieldUpdate(this.url, this.metadata);
|
||||
if (hasValue(control)) {
|
||||
this.objectUpdatesService.setValidFieldUpdate(this.route, this.metadata.uuid, control.valid);
|
||||
this.checkValidity(control);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to check the validity of a form control
|
||||
* @param control The form control to check
|
||||
*/
|
||||
private checkValidity(control: FormControl) {
|
||||
control.updateValueAndValidity();
|
||||
this.objectUpdatesService.setValidFieldUpdate(this.url, this.metadata.uuid, control.valid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a new editable state for this field to the service to change it
|
||||
* @param editable The new editable state for this field
|
||||
*/
|
||||
setEditable(editable: boolean) {
|
||||
this.objectUpdatesService.setEditableFieldUpdate(this.route, this.metadata.uuid, editable);
|
||||
this.objectUpdatesService.setEditableFieldUpdate(this.url, this.metadata.uuid, editable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a new remove update for this field to the object updates service
|
||||
*/
|
||||
remove() {
|
||||
this.objectUpdatesService.saveRemoveFieldUpdate(this.route, this.metadata);
|
||||
this.objectUpdatesService.saveRemoveFieldUpdate(this.url, this.metadata);
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies the object updates service that the updates for the current field can be removed
|
||||
*/
|
||||
removeChangesFromField() {
|
||||
this.objectUpdatesService.removeSingleFieldUpdate(this.route, this.metadata.uuid);
|
||||
this.objectUpdatesService.removeSingleFieldUpdate(this.url, this.metadata.uuid);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -170,15 +178,7 @@ export class EditInPlaceFieldComponent implements OnInit, OnChanges {
|
||||
* @return an observable that emits true when the user should be able to remove this field and false when they should not
|
||||
*/
|
||||
canRemove(): Observable<boolean> {
|
||||
return this.editable.pipe(
|
||||
map((editable: boolean) => {
|
||||
if (editable) {
|
||||
return false;
|
||||
} else {
|
||||
return this.fieldUpdate.changeType !== FieldChangeType.REMOVE && this.fieldUpdate.changeType !== FieldChangeType.ADD;
|
||||
}
|
||||
})
|
||||
);
|
||||
return observableOf(this.fieldUpdate.changeType !== FieldChangeType.REMOVE && this.fieldUpdate.changeType !== FieldChangeType.ADD);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -186,7 +186,9 @@ export class EditInPlaceFieldComponent implements OnInit, OnChanges {
|
||||
* @return an observable that emits true when the user should be able to undo changes to this field and false when they should not
|
||||
*/
|
||||
canUndo(): Observable<boolean> {
|
||||
return observableOf(this.fieldUpdate.changeType >= 0);
|
||||
return this.editable.pipe(
|
||||
map((editable: boolean) => this.fieldUpdate.changeType >= 0 || editable)
|
||||
);
|
||||
}
|
||||
|
||||
protected isNotEmpty(value): boolean {
|
||||
|
@@ -5,8 +5,7 @@
|
||||
(click)="add()"><i
|
||||
class="fas fa-plus"></i> {{"item.edit.metadata.add-button" | translate}}
|
||||
</button>
|
||||
|
||||
<div class="btn-group btn-group-toggle my-2" data-toggle="buttons">
|
||||
<div class="my-2 spaced-btn-group">
|
||||
<button class="btn btn-danger" *ngIf="!(isReinstatable() | async)"
|
||||
[disabled]="!(hasChanges() | async)"
|
||||
(click)="discard()"><i
|
||||
@@ -32,24 +31,26 @@
|
||||
</tr>
|
||||
<tr *ngFor="let updateValue of ((updates$ | async)| dsObjectValues); trackBy: trackUpdate">
|
||||
<ds-edit-in-place-field [fieldUpdate]="updateValue || {}"
|
||||
[route]="route"></ds-edit-in-place-field>
|
||||
[url]="url"></ds-edit-in-place-field>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="btn-group btn-group-toggle my-2 float-right" data-toggle="buttons">
|
||||
<button class="btn btn-danger" *ngIf="!(isReinstatable() | async)"
|
||||
[disabled]="!(hasChanges() | async)"
|
||||
(click)="discard()"><i
|
||||
class="fas fa-times"></i> {{"item.edit.metadata.discard-button" | translate}}
|
||||
</button>
|
||||
<button class="btn btn-warning" *ngIf="isReinstatable() | async"
|
||||
(click)="reinstate()"><i
|
||||
class="fas fa-undo-alt"></i> {{"item.edit.metadata.reinstate-button" | translate}}
|
||||
</button>
|
||||
<button class="btn btn-primary" [disabled]="!(hasChanges() | async)"
|
||||
(click)="submit()"><i
|
||||
class="fas fa-save"></i> {{"item.edit.metadata.save-button" | translate}}
|
||||
</button>
|
||||
<div class="button-row">
|
||||
<div class="my-2 float-right spaced-btn-group">
|
||||
<button class="btn btn-danger" *ngIf="!(isReinstatable() | async)"
|
||||
[disabled]="!(hasChanges() | async)"
|
||||
(click)="discard()"><i
|
||||
class="fas fa-times"></i> {{"item.edit.metadata.discard-button" | translate}}
|
||||
</button>
|
||||
<button class="btn btn-warning" *ngIf="isReinstatable() | async"
|
||||
(click)="reinstate()"><i
|
||||
class="fas fa-undo-alt"></i> {{"item.edit.metadata.reinstate-button" | translate}}
|
||||
</button>
|
||||
<button class="btn btn-primary" [disabled]="!(hasChanges() | async)"
|
||||
(click)="submit()"><i
|
||||
class="fas fa-save"></i> {{"item.edit.metadata.save-button" | translate}}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -1,5 +1,13 @@
|
||||
@import '../../../../styles/variables.scss';
|
||||
|
||||
.button-row .btn {
|
||||
min-width: $button-min-width;
|
||||
.button-row {
|
||||
.spaced-btn-group > .btn {
|
||||
margin-right: 0.5 * $spacer;
|
||||
&:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
.btn {
|
||||
min-width: $button-min-width;
|
||||
}
|
||||
}
|
@@ -7,7 +7,7 @@ import { Metadatum } from '../../../core/shared/metadatum.model';
|
||||
import { TestScheduler } from 'rxjs/testing';
|
||||
import { SharedModule } from '../../../shared/shared.module';
|
||||
import { ObjectUpdatesService } from '../../../core/data/object-updates/object-updates.service';
|
||||
import { Router } from '@angular/router';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { NotificationsService } from '../../../shared/notifications/notifications.service';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { ItemDataService } from '../../../core/data/item-data.service';
|
||||
@@ -32,6 +32,8 @@ const infoNotification: INotification = new Notification('id', NotificationType.
|
||||
const warningNotification: INotification = new Notification('id', NotificationType.Warning, 'warning');
|
||||
const date = new Date();
|
||||
const router = new RouterStub();
|
||||
let routeStub;
|
||||
|
||||
let itemService;
|
||||
const notificationsService = jasmine.createSpyObj('notificationsService',
|
||||
{
|
||||
@@ -56,9 +58,9 @@ const metadatum3 = Object.assign(new Metadatum(), {
|
||||
value: 'Shakespeare, William',
|
||||
});
|
||||
|
||||
const route = 'http://test-url.com/test-url';
|
||||
const url = 'http://test-url.com/test-url';
|
||||
|
||||
router.url = route;
|
||||
router.url = url;
|
||||
|
||||
const fieldUpdate1 = {
|
||||
field: metadatum1,
|
||||
@@ -84,6 +86,11 @@ describe('ItemMetadataComponent', () => {
|
||||
update: observableOf(new RemoteData(false, false, true, undefined, item)),
|
||||
commitUpdates: {}
|
||||
});
|
||||
routeStub = {
|
||||
parent: {
|
||||
data: observableOf({ item: new RemoteData(false, false, true, null, item) })
|
||||
}
|
||||
};
|
||||
scheduler = getTestScheduler();
|
||||
objectUpdatesService = jasmine.createSpyObj('objectUpdatesService',
|
||||
{
|
||||
@@ -111,6 +118,9 @@ describe('ItemMetadataComponent', () => {
|
||||
{ provide: ItemDataService, useValue: itemService },
|
||||
{ provide: ObjectUpdatesService, useValue: objectUpdatesService },
|
||||
{ provide: Router, useValue: router },
|
||||
{
|
||||
provide: ActivatedRoute, useValue: routeStub
|
||||
},
|
||||
{ provide: NotificationsService, useValue: notificationsService },
|
||||
{ provide: GLOBAL_CONFIG, useValue: { notifications: { timeOut: 10 } } as any }
|
||||
], schemas: [
|
||||
@@ -118,16 +128,14 @@ describe('ItemMetadataComponent', () => {
|
||||
]
|
||||
}).compileComponents();
|
||||
})
|
||||
)
|
||||
;
|
||||
);
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(ItemMetadataComponent);
|
||||
comp = fixture.componentInstance; // EditInPlaceFieldComponent test instance
|
||||
de = fixture.debugElement.query(By.css('div.d-flex'));
|
||||
el = de.nativeElement;
|
||||
comp.item = item;
|
||||
comp.route = route;
|
||||
comp.url = url;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
@@ -137,8 +145,8 @@ describe('ItemMetadataComponent', () => {
|
||||
comp.add(md);
|
||||
});
|
||||
|
||||
it('it should call saveAddFieldUpdate on the objectUpdatesService with the correct route and metadata', () => {
|
||||
expect(objectUpdatesService.saveAddFieldUpdate).toHaveBeenCalledWith(route, md);
|
||||
it('it should call saveAddFieldUpdate on the objectUpdatesService with the correct url and metadata', () => {
|
||||
expect(objectUpdatesService.saveAddFieldUpdate).toHaveBeenCalledWith(url, md);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -147,8 +155,8 @@ describe('ItemMetadataComponent', () => {
|
||||
comp.discard();
|
||||
});
|
||||
|
||||
it('it should call discardFieldUpdates on the objectUpdatesService with the correct route and notification', () => {
|
||||
expect(objectUpdatesService.discardFieldUpdates).toHaveBeenCalledWith(route, infoNotification);
|
||||
it('it should call discardFieldUpdates on the objectUpdatesService with the correct url and notification', () => {
|
||||
expect(objectUpdatesService.discardFieldUpdates).toHaveBeenCalledWith(url, infoNotification);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -157,8 +165,8 @@ describe('ItemMetadataComponent', () => {
|
||||
comp.reinstate();
|
||||
});
|
||||
|
||||
it('it should call reinstateFieldUpdates on the objectUpdatesService with the correct route', () => {
|
||||
expect(objectUpdatesService.reinstateFieldUpdates).toHaveBeenCalledWith(route);
|
||||
it('it should call reinstateFieldUpdates on the objectUpdatesService with the correct url', () => {
|
||||
expect(objectUpdatesService.reinstateFieldUpdates).toHaveBeenCalledWith(url);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -167,10 +175,10 @@ describe('ItemMetadataComponent', () => {
|
||||
comp.submit();
|
||||
});
|
||||
|
||||
it('it should call reinstateFieldUpdates on the objectUpdatesService with the correct route and metadata', () => {
|
||||
expect(objectUpdatesService.getUpdatedFields).toHaveBeenCalledWith(route, comp.item.metadata);
|
||||
it('it should call reinstateFieldUpdates on the objectUpdatesService with the correct url and metadata', () => {
|
||||
expect(objectUpdatesService.getUpdatedFields).toHaveBeenCalledWith(url, comp.item.metadata);
|
||||
expect(itemService.update).toHaveBeenCalledWith(comp.item);
|
||||
expect(objectUpdatesService.getFieldUpdates).toHaveBeenCalledWith(route, comp.item.metadata);
|
||||
expect(objectUpdatesService.getFieldUpdates).toHaveBeenCalledWith(url, comp.item.metadata);
|
||||
});
|
||||
});
|
||||
|
||||
|
@@ -2,7 +2,7 @@ import { Component, Inject, Input, OnInit } from '@angular/core';
|
||||
import { Item } from '../../../core/shared/item.model';
|
||||
import { ItemDataService } from '../../../core/data/item-data.service';
|
||||
import { ObjectUpdatesService } from '../../../core/data/object-updates/object-updates.service';
|
||||
import { Router } from '@angular/router';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import { Observable } from 'rxjs';
|
||||
import {
|
||||
@@ -31,15 +31,15 @@ export class ItemMetadataComponent implements OnInit {
|
||||
/**
|
||||
* The item to display the edit page for
|
||||
*/
|
||||
@Input() item: Item;
|
||||
item: Item;
|
||||
/**
|
||||
* The current values and updates for all this item's metadata fields
|
||||
*/
|
||||
updates$: Observable<FieldUpdates>;
|
||||
/**
|
||||
* The current route of this page
|
||||
* The current url of this page
|
||||
*/
|
||||
route: string;
|
||||
url: string;
|
||||
/**
|
||||
* The time span for being able to undo discarding changes
|
||||
*/
|
||||
@@ -51,16 +51,25 @@ export class ItemMetadataComponent implements OnInit {
|
||||
private router: Router,
|
||||
private notificationsService: NotificationsService,
|
||||
private translateService: TranslateService,
|
||||
@Inject(GLOBAL_CONFIG) protected EnvConfig: GlobalConfig
|
||||
@Inject(GLOBAL_CONFIG) protected EnvConfig: GlobalConfig,
|
||||
private route: ActivatedRoute
|
||||
) {
|
||||
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.route.parent.data.pipe(map((data) => data.item))
|
||||
.pipe(
|
||||
first(),
|
||||
map((data: RemoteData<Item>) => data.payload)
|
||||
).subscribe((item: Item) => {
|
||||
this.item = item;
|
||||
});
|
||||
|
||||
this.discardTimeOut = this.EnvConfig.notifications.timeOut;
|
||||
this.route = this.router.url;
|
||||
if (this.route.indexOf('?') > 0) {
|
||||
this.route = this.route.substr(0, this.route.indexOf('?'));
|
||||
this.url = this.router.url;
|
||||
if (this.url.indexOf('?') > 0) {
|
||||
this.url = this.url.substr(0, this.url.indexOf('?'));
|
||||
}
|
||||
this.hasChanges().pipe(first()).subscribe((hasChanges) => {
|
||||
if (!hasChanges) {
|
||||
@@ -69,7 +78,8 @@ export class ItemMetadataComponent implements OnInit {
|
||||
this.checkLastModified();
|
||||
}
|
||||
});
|
||||
this.updates$ = this.objectUpdatesService.getFieldUpdates(this.route, this.item.metadata);
|
||||
this.updates$ = this.objectUpdatesService.getFieldUpdates(this.url, this.item.metadata);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -77,7 +87,8 @@ export class ItemMetadataComponent implements OnInit {
|
||||
* @param metadata The metadata to add, if no parameter is supplied, create a new Metadatum
|
||||
*/
|
||||
add(metadata: Metadatum = new Metadatum()) {
|
||||
this.objectUpdatesService.saveAddFieldUpdate(this.route, metadata);
|
||||
this.objectUpdatesService.saveAddFieldUpdate(this.url, metadata);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -88,21 +99,21 @@ export class ItemMetadataComponent implements OnInit {
|
||||
const title = this.translateService.instant('item.edit.metadata.notifications.discarded.title');
|
||||
const content = this.translateService.instant('item.edit.metadata.notifications.discarded.content');
|
||||
const undoNotification = this.notificationsService.info(title, content, { timeOut: this.discardTimeOut });
|
||||
this.objectUpdatesService.discardFieldUpdates(this.route, undoNotification);
|
||||
this.objectUpdatesService.discardFieldUpdates(this.url, undoNotification);
|
||||
}
|
||||
|
||||
/**
|
||||
* Request the object updates service to undo discarding all changes to this item
|
||||
*/
|
||||
reinstate() {
|
||||
this.objectUpdatesService.reinstateFieldUpdates(this.route);
|
||||
this.objectUpdatesService.reinstateFieldUpdates(this.url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends all initial values of this item to the object updates service
|
||||
*/
|
||||
private initializeOriginalFields() {
|
||||
this.objectUpdatesService.initialize(this.route, this.item.metadata, this.item.lastModified);
|
||||
this.objectUpdatesService.initialize(this.url, this.item.metadata, this.item.lastModified);
|
||||
}
|
||||
|
||||
/* Prevent unnecessary rerendering so fields don't lose focus **/
|
||||
@@ -117,42 +128,42 @@ export class ItemMetadataComponent implements OnInit {
|
||||
submit() {
|
||||
this.isValid().pipe(first()).subscribe((isValid) => {
|
||||
if (isValid) {
|
||||
const metadata$: Observable<Identifiable[]> = this.objectUpdatesService.getUpdatedFields(this.route, this.item.metadata) as Observable<Metadatum[]>;
|
||||
metadata$.pipe(
|
||||
first(),
|
||||
switchMap((metadata: Metadatum[]) => {
|
||||
const updatedItem: Item = Object.assign(cloneDeep(this.item), { metadata });
|
||||
return this.itemService.update(updatedItem);
|
||||
}),
|
||||
tap(() => this.itemService.commitUpdates()),
|
||||
getSucceededRemoteData()
|
||||
).subscribe(
|
||||
(rd: RemoteData<Item>) => {
|
||||
this.item = rd.payload;
|
||||
this.initializeOriginalFields();
|
||||
this.updates$ = this.objectUpdatesService.getFieldUpdates(this.route, this.item.metadata);
|
||||
}
|
||||
)
|
||||
} else {
|
||||
const title = this.translateService.instant('item.edit.metadata.notifications.invalid.title');
|
||||
const content = this.translateService.instant('item.edit.metadata.notifications.invalid.content');
|
||||
this.notificationsService.error(title, content);
|
||||
}
|
||||
});
|
||||
const metadata$: Observable<Identifiable[]> = this.objectUpdatesService.getUpdatedFields(this.url, this.item.metadata) as Observable<Metadatum[]>;
|
||||
metadata$.pipe(
|
||||
first(),
|
||||
switchMap((metadata: Metadatum[]) => {
|
||||
const updatedItem: Item = Object.assign(cloneDeep(this.item), { metadata });
|
||||
return this.itemService.update(updatedItem);
|
||||
}),
|
||||
tap(() => this.itemService.commitUpdates()),
|
||||
getSucceededRemoteData()
|
||||
).subscribe(
|
||||
(rd: RemoteData<Item>) => {
|
||||
this.item = rd.payload;
|
||||
this.initializeOriginalFields();
|
||||
this.updates$ = this.objectUpdatesService.getFieldUpdates(this.url, this.item.metadata);
|
||||
}
|
||||
)
|
||||
} else {
|
||||
const title = this.translateService.instant('item.edit.metadata.notifications.invalid.title');
|
||||
const content = this.translateService.instant('item.edit.metadata.notifications.invalid.content');
|
||||
this.notificationsService.error(title, content);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether or not there are currently updates for this item
|
||||
*/
|
||||
hasChanges(): Observable<boolean> {
|
||||
return this.objectUpdatesService.hasUpdates(this.route);
|
||||
return this.objectUpdatesService.hasUpdates(this.url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether or not the item is currently reinstatable
|
||||
*/
|
||||
isReinstatable(): Observable<boolean> {
|
||||
return this.objectUpdatesService.isReinstatable(this.route);
|
||||
return this.objectUpdatesService.isReinstatable(this.url);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -161,7 +172,7 @@ export class ItemMetadataComponent implements OnInit {
|
||||
*/
|
||||
private checkLastModified() {
|
||||
const currentVersion = this.item.lastModified;
|
||||
this.objectUpdatesService.getLastModified(this.route).pipe(first()).subscribe(
|
||||
this.objectUpdatesService.getLastModified(this.url).pipe(first()).subscribe(
|
||||
(updateVersion: Date) => {
|
||||
if (updateVersion.getDate() !== currentVersion.getDate()) {
|
||||
const title = this.translateService.instant('item.edit.metadata.notifications.outdated.title');
|
||||
@@ -173,7 +184,10 @@ export class ItemMetadataComponent implements OnInit {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the current page is entirely valid
|
||||
*/
|
||||
private isValid() {
|
||||
return this.objectUpdatesService.isValidPage(this.route);
|
||||
return this.objectUpdatesService.isValidPage(this.url);
|
||||
}
|
||||
}
|
||||
|
@@ -6,11 +6,13 @@ import { CommonModule } from '@angular/common';
|
||||
import { HostWindowServiceStub } from '../../../shared/testing/host-window-service-stub';
|
||||
import { HostWindowService } from '../../../shared/host-window.service';
|
||||
import { RouterTestingModule } from '@angular/router/testing';
|
||||
import { Router } from '@angular/router';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { RouterStub } from '../../../shared/testing/router-stub';
|
||||
import { Item } from '../../../core/shared/item.model';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import {CUSTOM_ELEMENTS_SCHEMA} from '@angular/core';
|
||||
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
|
||||
import { of as observableOf } from 'rxjs';
|
||||
import { RemoteData } from '../../../core/data/remote-data';
|
||||
|
||||
describe('ItemStatusComponent', () => {
|
||||
let comp: ItemStatusComponent;
|
||||
@@ -27,12 +29,19 @@ describe('ItemStatusComponent', () => {
|
||||
url: `${itemPageUrl}/edit`
|
||||
});
|
||||
|
||||
const routeStub = {
|
||||
parent: {
|
||||
data: observableOf({ item: new RemoteData(false, false, true, null, mockItem) })
|
||||
}
|
||||
};
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [CommonModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule.forRoot()],
|
||||
declarations: [ItemStatusComponent],
|
||||
providers: [
|
||||
{ provide: Router, useValue: routerStub },
|
||||
{ provide: ActivatedRoute, useValue: routeStub },
|
||||
{ provide: HostWindowService, useValue: new HostWindowServiceStub(0) }
|
||||
], schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
||||
}).compileComponents();
|
||||
@@ -41,7 +50,6 @@ describe('ItemStatusComponent', () => {
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(ItemStatusComponent);
|
||||
comp = fixture.componentInstance;
|
||||
comp.item = mockItem;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
@@ -65,4 +73,5 @@ describe('ItemStatusComponent', () => {
|
||||
expect(statusItemPage.textContent).toContain(itemPageUrl);
|
||||
});
|
||||
|
||||
});
|
||||
})
|
||||
;
|
||||
|
@@ -1,8 +1,11 @@
|
||||
import {ChangeDetectionStrategy, Component, Input, OnInit} from '@angular/core';
|
||||
import {fadeIn, fadeInOut} from '../../../shared/animations/fade';
|
||||
import {Item} from '../../../core/shared/item.model';
|
||||
import {Router} from '@angular/router';
|
||||
import {ItemOperation} from '../item-operation/itemOperation.model';
|
||||
import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core';
|
||||
import { fadeIn, fadeInOut } from '../../../shared/animations/fade';
|
||||
import { Item } from '../../../core/shared/item.model';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { ItemOperation } from '../item-operation/itemOperation.model';
|
||||
import { first, map } from 'rxjs/operators';
|
||||
import { Observable } from 'rxjs';
|
||||
import { RemoteData } from '../../../core/data/remote-data';
|
||||
|
||||
@Component({
|
||||
selector: 'ds-item-status',
|
||||
@@ -21,7 +24,7 @@ export class ItemStatusComponent implements OnInit {
|
||||
/**
|
||||
* The item to display the status for
|
||||
*/
|
||||
@Input() item: Item;
|
||||
itemRD$: Observable<RemoteData<Item>>;
|
||||
|
||||
/**
|
||||
* The data to show in the status
|
||||
@@ -37,39 +40,46 @@ export class ItemStatusComponent implements OnInit {
|
||||
* key: id value: url to action's component
|
||||
*/
|
||||
operations: ItemOperation[];
|
||||
|
||||
/**
|
||||
* The keys of the actions (to loop over)
|
||||
*/
|
||||
actionsKeys;
|
||||
|
||||
constructor(private router: Router) {
|
||||
constructor(private router: Router, private route: ActivatedRoute) {
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.statusData = Object.assign({
|
||||
id: this.item.id,
|
||||
handle: this.item.handle,
|
||||
lastModified: this.item.lastModified
|
||||
this.itemRD$ = this.route.parent.data.pipe(map((data) => data.item));
|
||||
this.itemRD$.pipe(
|
||||
first(),
|
||||
map((data: RemoteData<Item>) => data.payload)
|
||||
).subscribe((item: Item) => {
|
||||
this.statusData = Object.assign({
|
||||
id: item.id,
|
||||
handle: item.handle,
|
||||
lastModified: item.lastModified
|
||||
});
|
||||
this.statusDataKeys = Object.keys(this.statusData);
|
||||
/*
|
||||
The key is used to build messages
|
||||
i18n example: 'item.edit.tabs.status.buttons.<key>.label'
|
||||
The value is supposed to be a href for the button
|
||||
*/
|
||||
this.operations = [];
|
||||
if (item.isWithdrawn) {
|
||||
this.operations.push(new ItemOperation('reinstate', this.getCurrentUrl() + '/reinstate'));
|
||||
} else {
|
||||
this.operations.push(new ItemOperation('withdraw', this.getCurrentUrl() + '/withdraw'));
|
||||
}
|
||||
if (item.isDiscoverable) {
|
||||
this.operations.push(new ItemOperation('private', this.getCurrentUrl() + '/private'));
|
||||
} else {
|
||||
this.operations.push(new ItemOperation('public', this.getCurrentUrl() + '/public'));
|
||||
}
|
||||
this.operations.push(new ItemOperation('delete', this.getCurrentUrl() + '/delete'));
|
||||
});
|
||||
this.statusDataKeys = Object.keys(this.statusData);
|
||||
|
||||
/*
|
||||
The key is used to build messages
|
||||
i18n example: 'item.edit.tabs.status.buttons.<key>.label'
|
||||
The value is supposed to be a href for the button
|
||||
*/
|
||||
this.operations = [];
|
||||
if (this.item.isWithdrawn) {
|
||||
this.operations.push(new ItemOperation('reinstate', this.getCurrentUrl() + '/reinstate'));
|
||||
} else {
|
||||
this.operations.push(new ItemOperation('withdraw', this.getCurrentUrl() + '/withdraw'));
|
||||
}
|
||||
if (this.item.isDiscoverable) {
|
||||
this.operations.push(new ItemOperation('private', this.getCurrentUrl() + '/private'));
|
||||
} else {
|
||||
this.operations.push(new ItemOperation('public', this.getCurrentUrl() + '/public'));
|
||||
}
|
||||
this.operations.push(new ItemOperation('delete', this.getCurrentUrl() + '/delete'));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -15,7 +15,7 @@ export function getItemEditPath(id: string) {
|
||||
return new URLCombiner(getItemModulePath(),ITEM_EDIT_PATH.replace(/:id/, id)).toString()
|
||||
}
|
||||
|
||||
const ITEM_EDIT_PATH = ':id/edit/:page';
|
||||
const ITEM_EDIT_PATH = ':id/edit';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
@@ -39,7 +39,7 @@ const ITEM_EDIT_PATH = ':id/edit/:page';
|
||||
path: ITEM_EDIT_PATH,
|
||||
loadChildren: './edit-item-page/edit-item-page.module#EditItemPageModule',
|
||||
canActivate: [AuthenticatedGuard]
|
||||
}
|
||||
},
|
||||
])
|
||||
],
|
||||
providers: [
|
||||
|
@@ -22,10 +22,10 @@ export class MetadataField implements ListableObject {
|
||||
@autoserialize
|
||||
schema: MetadataSchema;
|
||||
|
||||
toString(): string {
|
||||
let key = this.schema.prefix + '.' + this.element;
|
||||
toString(separator: string = '.'): string {
|
||||
let key = this.schema.prefix + separator + this.element;
|
||||
if (isNotEmpty(this.qualifier)) {
|
||||
key += '.' + this.qualifier;
|
||||
key += separator + this.qualifier;
|
||||
}
|
||||
return key;
|
||||
}
|
||||
|
@@ -5,8 +5,10 @@
|
||||
(dsClickOutside)="close()">
|
||||
<input #inputField type="text" [(ngModel)]="value" [name]="name"
|
||||
class="form-control suggestion_input"
|
||||
[ngClass]="{'is-invalid': !valid}"
|
||||
[dsDebounce]="debounceTime" (onDebounce)="find($event)"
|
||||
[placeholder]="placeholder"
|
||||
(blur)="blur.emit($event);"
|
||||
[ngModelOptions]="{standalone: true}" autocomplete="off"/>
|
||||
<input type="submit" class="d-none"/>
|
||||
<div class="autocomplete dropdown-menu" [ngClass]="{'show': (show | async) && isNotEmpty(suggestions)}">
|
||||
|
@@ -60,6 +60,11 @@ export class InputSuggestionsComponent implements ControlValueAccessor, OnChange
|
||||
*/
|
||||
@Input() name;
|
||||
|
||||
/**
|
||||
* Whether or not the current input is valid
|
||||
*/
|
||||
@Input() valid;
|
||||
|
||||
/**
|
||||
* Output for when the form is submitted
|
||||
*/
|
||||
@@ -80,6 +85,11 @@ export class InputSuggestionsComponent implements ControlValueAccessor, OnChange
|
||||
*/
|
||||
@Output() findSuggestions = new EventEmitter();
|
||||
|
||||
/**
|
||||
* Emits event when the input field loses focus
|
||||
*/
|
||||
@Output() blur = new EventEmitter();
|
||||
|
||||
/**
|
||||
* Emits true when the list of suggestions should be shown
|
||||
*/
|
||||
@@ -238,4 +248,8 @@ export class InputSuggestionsComponent implements ControlValueAccessor, OnChange
|
||||
this._value = val;
|
||||
this.propagateChange(this._value);
|
||||
}
|
||||
|
||||
focus() {
|
||||
this.queryInput.nativeElement.focus();
|
||||
}
|
||||
}
|
||||
|
@@ -93,6 +93,7 @@ import { DeleteComColPageComponent } from './comcol-forms/delete-comcol-page/del
|
||||
import { LangSwitchComponent } from './lang-switch/lang-switch.component';
|
||||
import { ObjectValuesPipe } from './utils/object-values-pipe';
|
||||
import { InListValidator } from './utils/in-list-validator.directive';
|
||||
import { AutoFocusDirective } from './utils/auto-focus.directive';
|
||||
|
||||
const MODULES = [
|
||||
// Do NOT include UniversalModule, HttpModule, or JsonpModule here
|
||||
@@ -199,7 +200,8 @@ const DIRECTIVES = [
|
||||
DragClickDirective,
|
||||
DebounceDirective,
|
||||
ClickOutsideDirective,
|
||||
InListValidator
|
||||
InListValidator,
|
||||
AutoFocusDirective
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
|
22
src/app/shared/utils/auto-focus.directive.ts
Normal file
22
src/app/shared/utils/auto-focus.directive.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { Directive, AfterViewInit, ElementRef, Input } from '@angular/core';
|
||||
import { isNotEmpty } from '../empty.util';
|
||||
|
||||
@Directive({
|
||||
selector: '[dsAutoFocus]'
|
||||
})
|
||||
export class AutoFocusDirective implements AfterViewInit {
|
||||
|
||||
@Input() autoFocusSelector: string;
|
||||
|
||||
constructor(private el: ElementRef) {
|
||||
}
|
||||
|
||||
ngAfterViewInit() {
|
||||
if (isNotEmpty(this.autoFocusSelector)) {
|
||||
return this.el.nativeElement.querySelector(this.autoFocusSelector).focus();
|
||||
|
||||
} else {
|
||||
return this.el.nativeElement.focus();
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user