64961: Edit bitstream page foundations

This commit is contained in:
Kristof De Langhe
2019-09-12 17:49:45 +02:00
parent a1ac80e53a
commit 91c6b76230
10 changed files with 327 additions and 17 deletions

View File

@@ -84,6 +84,13 @@
"auth.messages.expired": "Your session has expired. Please log in again.",
"bitstream.edit.title": "Edit bitstream",
"bitstream.edit.bitstream": "Bitstream: ",
"bitstream.edit.form.description.label": "Description",
"bitstream.edit.form.description.hint": "Optionally, provide a brief description of the file, for example \"Main article\" or \"Experiment data readings\".",
"bitstream.edit.form.embargo.label": "Embargo until specific date",
"bitstream.edit.form.embargo.hint": "The first day from which access is allowed. <b>This date cannot be modified on this form.</b> To set an embargo date for a bitstream, go to the <i>Item Status</i> tab, click <i>Authorizations...<i/>, create or edit the bitstream's <i>READ</i> policy, and set the <i>Start Date</i> as desired.",
"bitstream.edit.form.fileName.label": "Filename",
"bitstream.edit.form.fileName.hint": "Change the filename for the bitstream. Note that this will change the display bitstream URL, but old links will still resolve as long as the sequence ID does not change.",
"bitstream.edit.form.primaryBitstream.label": "Primary bitstream",
"browse.comcol.by.author": "By Author",
"browse.comcol.by.dateissued": "By Issue Date",
"browse.comcol.by.subject": "By Subject",
@@ -733,4 +740,4 @@
"uploader.or": ", or",
"uploader.processing": "Processing",
"uploader.queue-lenght": "Queue length"
}
}

View File

@@ -1,13 +1,22 @@
<div class="container">
<div class="row">
<div class="col-12 mb-4">
<h2>{{'bitstream.edit.title' | translate}}</h2>
<ng-container *ngVar="(bitstreamRD$ | async)?.payload as bitstream">
<div *ngIf="bitstream">
<span class="font-weight-bold">{{'bitstream.edit.bitstream' | translate}}</span>
<span>{{bitstream.name}}</span>
<div class="col-md-2">
<ds-thumbnail [thumbnail]="bitstream"></ds-thumbnail>
</div>
<div class="col-md-10">
<div class="container">
<div class="row">
<div class="col-12">
<h3>{{bitstream?.name}} <span class="text-muted">({{bitstream?.sizeBytes | dsFileSize}})</span></h3>
</div>
</div>
</ng-container>
</div>
<ds-form *ngIf="bitstream"
[formId]="'edit-bitstream-form-id'"
[formGroup]="formGroup"
[formModel]="formModel"
[formLayout]="formLayout"
(submitForm)="onSubmit()"></ds-form>
</div>
</div>
</div>

View File

@@ -0,0 +1,8 @@
:host {
::ng-deep {
.switch {
position: absolute;
top: $spacer*2.5;
}
}
}

View File

@@ -1,30 +1,248 @@
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
import { Observable } from 'rxjs/internal/Observable';
import { RemoteData } from '../../core/data/remote-data';
import { ChangeDetectionStrategy, Component, OnDestroy, OnInit } from '@angular/core';
import { Bitstream } from '../../core/shared/bitstream.model';
import { ActivatedRoute } from '@angular/router';
import { map } from 'rxjs/operators';
import { Subscription } from 'rxjs/internal/Subscription';
import {
DynamicFormControlModel, DynamicFormGroupModel, DynamicFormLayout, DynamicFormService,
DynamicInputModel,
DynamicTextAreaModel
} from '@ng-dynamic-forms/core';
import { FormGroup } from '@angular/forms';
import { TranslateService } from '@ngx-translate/core';
import { DynamicCustomSwitchModel } from '../../shared/form/builder/ds-dynamic-form-ui/models/custom-switch/custom-switch.model';
@Component({
selector: 'ds-edit-bitstream-page',
styleUrls: ['./edit-bitstream-page.component.scss'],
templateUrl: './edit-bitstream-page.component.html',
changeDetection: ChangeDetectionStrategy.OnPush
})
/**
* Page component for editing a bitstream
*/
export class EditBitstreamPageComponent implements OnInit {
export class EditBitstreamPageComponent implements OnInit, OnDestroy {
/**
* The bitstream to edit
*/
bitstreamRD$: Observable<RemoteData<Bitstream>>;
bitstream: Bitstream;
constructor(private route: ActivatedRoute) {
/**
* @type {string} Key prefix used to generate form messages
*/
KEY_PREFIX = 'bitstream.edit.form.';
/**
* @type {string} Key suffix used to generate form labels
*/
LABEL_KEY_SUFFIX = '.label';
/**
* @type {string} Key suffix used to generate form labels
*/
HINT_KEY_SUFFIX = '.hint';
/**
* The Dynamic Input Model for the file's name
*/
fileNameModel = new DynamicInputModel({
id: 'fileName',
name: 'fileName',
required: true,
validators: {
required: null
},
errorMessages: {
required: 'You must provide a file name for the bitstream'
}
});
/**
* The Dynamic Switch Model for the file's name
*/
primaryBitstreamModel = new DynamicCustomSwitchModel({
id: 'primaryBitstream',
name: 'primaryBitstream'
});
/**
* The Dynamic TextArea Model for the file's description
*/
descriptionModel = new DynamicTextAreaModel({
id: 'description',
name: 'description'
});
/**
* The Dynamic Input Model for the file's embargo (disabled on this page)
*/
embargoModel = new DynamicInputModel({
id: 'embargo',
name: 'embargo',
disabled: true
});
/**
* All input models in a simple array for easier iterations
*/
inputModels = [this.fileNameModel, this.primaryBitstreamModel, this.descriptionModel, this.embargoModel];
/**
* The dynamic form fields used for editing the information of a bitstream
* @type {(DynamicInputModel | DynamicTextAreaModel)[]}
*/
formModel: DynamicFormControlModel[] = [
new DynamicFormGroupModel({
id: 'fileNamePrimaryContainer',
group: [
this.fileNameModel,
this.primaryBitstreamModel
]
}),
new DynamicFormGroupModel({
id: 'descriptionContainer',
group: [
this.descriptionModel
]
}),
new DynamicFormGroupModel({
id: 'embargoContainer',
group: [
this.embargoModel
]
})
];
/**
* Layout used for structuring the form inputs
*/
formLayout: DynamicFormLayout = {
fileName: {
grid: {
host: 'col col-sm-8 d-inline-block'
}
},
primaryBitstream: {
grid: {
host: 'col col-sm-4 d-inline-block switch'
}
},
description: {
grid: {
host: 'col-12 d-inline-block'
}
},
embargo: {
grid: {
host: 'col-12 d-inline-block'
}
},
fileNamePrimaryContainer: {
grid: {
host: 'row position-relative'
}
},
descriptionContainer: {
grid: {
host: 'row'
}
},
embargoContainer: {
grid: {
host: 'row'
}
}
};
/**
* The form group of this form
*/
formGroup: FormGroup;
/**
* The subscription on the bitstream
*/
sub: Subscription;
constructor(private route: ActivatedRoute,
private formService: DynamicFormService,
private translate: TranslateService) {
}
/**
* Initialize the component
* - Create a FormGroup using the FormModel defined earlier
* - Subscribe on the route data to fetch the bitstream to edit and update the form values
* - Translate the form labels and hints
*/
ngOnInit(): void {
this.bitstreamRD$ = this.route.data.pipe(map((data) => data.bitstream));
this.formGroup = this.formService.createFormGroup(this.formModel);
this.sub = this.route.data.pipe(map((data) => data.bitstream)).subscribe((bitstreamRD) => {
this.bitstream = bitstreamRD.payload;
this.updateForm(this.bitstream);
});
this.updateFieldTranslations();
this.translate.onLangChange
.subscribe(() => {
this.updateFieldTranslations();
});
}
/**
* Update the current form values with bitstream properties
* @param bitstream
*/
updateForm(bitstream: Bitstream) {
this.formGroup.patchValue({
fileNamePrimaryContainer: {
fileName: bitstream.name,
primaryBitstream: false
},
descriptionContainer: {
description: bitstream.description
}
});
}
/**
* Used to update translations of labels and hints on init and on language change
*/
private updateFieldTranslations() {
this.inputModels.forEach(
(fieldModel: DynamicFormControlModel) => {
this.updateFieldTranslation(fieldModel);
}
);
}
/**
* Update the translations of a DynamicFormControlModel
* @param fieldModel
*/
private updateFieldTranslation(fieldModel) {
fieldModel.label = this.translate.instant(this.KEY_PREFIX + fieldModel.id + this.LABEL_KEY_SUFFIX);
if (fieldModel.id !== this.primaryBitstreamModel.id) {
fieldModel.hint = this.translate.instant(this.KEY_PREFIX + fieldModel.id + this.HINT_KEY_SUFFIX);
}
}
/**
* Check for changes against the bitstream and send update requests to the REST API
*/
onSubmit() {
// TODO: Check for changes against the bitstream and send requests to the REST API accordingly
console.log(this.formGroup.getRawValue());
}
/**
* Unsubscribe from open subscriptions
*/
ngOnDestroy(): void {
if (this.sub) {
this.sub.unsubscribe();
}
}
}

View File

@@ -68,6 +68,8 @@ import { DsDynamicFormArrayComponent } from './models/array-group/dynamic-form-a
import { DsDynamicRelationGroupComponent } from './models/relation-group/dynamic-relation-group.components';
import { DYNAMIC_FORM_CONTROL_TYPE_RELATION_GROUP } from './models/relation-group/dynamic-relation-group.model';
import { DsDatePickerInlineComponent } from './models/date-picker-inline/dynamic-date-picker-inline.component';
import { DYNAMIC_FORM_CONTROL_TYPE_CUSTOM_SWITCH } from './models/custom-switch/custom-switch.model';
import { CustomSwitchComponent } from './models/custom-switch/custom-switch.component';
export function dsDynamicFormControlMapFn(model: DynamicFormControlModel): Type<DynamicFormControl> | null {
switch (model.type) {
@@ -125,6 +127,9 @@ export function dsDynamicFormControlMapFn(model: DynamicFormControlModel): Type<
case DYNAMIC_FORM_CONTROL_TYPE_LOOKUP_NAME:
return DsDynamicLookupComponent;
case DYNAMIC_FORM_CONTROL_TYPE_CUSTOM_SWITCH:
return CustomSwitchComponent;
default:
return null;
}
@@ -176,6 +181,10 @@ export class DsDynamicFormControlContainerComponent extends DynamicFormControlCo
super(componentFactoryResolver, layoutService, validationService);
}
get isCheckbox(): boolean {
return this.model.type === DYNAMIC_FORM_CONTROL_TYPE_CHECKBOX || this.model.type === DYNAMIC_FORM_CONTROL_TYPE_CUSTOM_SWITCH;
}
ngOnChanges(changes: SimpleChanges) {
if (changes) {
super.ngOnChanges(changes);

View File

@@ -0,0 +1,20 @@
<div [formGroup]="group" class="form-check custom-control custom-switch" [class.disabled]="model.disabled">
<input type="checkbox" class="form-check-input custom-control-input"
[checked]="model.checked"
[class.is-invalid]="showErrorMessages"
[dynamicId]="bindId && model.id"
[formControlName]="model.id"
[indeterminate]="model.indeterminate"
[name]="model.name"
[ngClass]="getClass('element', 'control')"
[required]="model.required"
[tabindex]="model.tabIndex"
[value]="model.value"
(blur)="onBlur($event)"
(change)="onChange($event)"
(focus)="onFocus($event)"/>
<label class="form-check-label custom-control-label" [for]="bindId && model.id">
<span [innerHTML]="model.label"
[ngClass]="[getClass('element', 'label'), getClass('grid', 'label')]"></span>
</label>
</div>

View File

@@ -0,0 +1,20 @@
import { DynamicNGBootstrapCheckboxComponent } from '@ng-dynamic-forms/ui-ng-bootstrap';
import { Component, EventEmitter, Input, Output } from '@angular/core';
import { FormGroup } from '@angular/forms';
import { DynamicCustomSwitchModel } from './custom-switch.model';
@Component({
selector: 'ds-custom-switch',
styleUrls: ['./custom-switch.component.scss'],
templateUrl: './custom-switch.component.html',
})
export class CustomSwitchComponent extends DynamicNGBootstrapCheckboxComponent {
@Input() bindId = true;
@Input() group: FormGroup;
@Input() model: DynamicCustomSwitchModel;
@Output() selected = new EventEmitter<number>();
@Output() remove = new EventEmitter<number>();
@Output() blur = new EventEmitter<any>();
@Output() change = new EventEmitter<any>();
@Output() focus = new EventEmitter<any>();
}

View File

@@ -0,0 +1,16 @@
import {
DynamicCheckboxModel,
DynamicCheckboxModelConfig,
DynamicFormControlLayout,
serializable
} from '@ng-dynamic-forms/core';
export const DYNAMIC_FORM_CONTROL_TYPE_CUSTOM_SWITCH = 'CUSTOM_SWITCH';
export class DynamicCustomSwitchModel extends DynamicCheckboxModel {
@serializable() readonly type: string = DYNAMIC_FORM_CONTROL_TYPE_CUSTOM_SWITCH;
constructor(config: DynamicCheckboxModelConfig, layout?: DynamicFormControlLayout) {
super(config, layout);
}
}

View File

@@ -142,6 +142,7 @@ import { AbstractTrackableComponent } from './trackable/abstract-trackable.compo
import { TypedItemSearchResultGridElementComponent } from './object-grid/item-grid-element/item-types/typed-item-search-result-grid-element.component';
import { PublicationGridElementComponent } from './object-grid/item-grid-element/item-types/publication/publication-grid-element.component';
import { ItemTypeBadgeComponent } from './object-list/item-type-badge/item-type-badge.component';
import { CustomSwitchComponent } from './form/builder/ds-dynamic-form-ui/models/custom-switch/custom-switch.component';
const MODULES = [
// Do NOT include UniversalModule, HttpModule, or JsonpModule here
@@ -265,7 +266,8 @@ const COMPONENTS = [
BrowseByComponent,
ItemTypeBadgeComponent,
BrowseByComponent,
AbstractTrackableComponent
AbstractTrackableComponent,
CustomSwitchComponent
];
const ENTRY_COMPONENTS = [
@@ -309,7 +311,8 @@ const ENTRY_COMPONENTS = [
StartsWithTextComponent,
PlainTextMetadataListElementComponent,
ItemMetadataListElementComponent,
MetadataRepresentationListElementComponent
MetadataRepresentationListElementComponent,
CustomSwitchComponent
];
const SHARED_ITEM_PAGE_COMPONENTS = [