mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 01:54:15 +00:00
64961: Edit bitstream page foundations
This commit is contained in:
@@ -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",
|
||||
|
@@ -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>
|
||||
|
@@ -0,0 +1,8 @@
|
||||
:host {
|
||||
::ng-deep {
|
||||
.switch {
|
||||
position: absolute;
|
||||
top: $spacer*2.5;
|
||||
}
|
||||
}
|
||||
}
|
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -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);
|
||||
|
@@ -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>
|
@@ -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>();
|
||||
}
|
@@ -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);
|
||||
}
|
||||
}
|
@@ -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 = [
|
||||
|
Reference in New Issue
Block a user