-
-
{{'bitstream.edit.title' | translate}}
-
-
-
{{'bitstream.edit.bitstream' | translate}}
-
{{bitstream.name}}
+
+
+
+
+
+
+
+
{{bitstream?.name}} ({{bitstream?.sizeBytes | dsFileSize}})
+
-
+
+
diff --git a/src/app/+bitstream-page/edit-bitstream-page/edit-bitstream-page.component.scss b/src/app/+bitstream-page/edit-bitstream-page/edit-bitstream-page.component.scss
new file mode 100644
index 0000000000..d212b5347c
--- /dev/null
+++ b/src/app/+bitstream-page/edit-bitstream-page/edit-bitstream-page.component.scss
@@ -0,0 +1,8 @@
+:host {
+ ::ng-deep {
+ .switch {
+ position: absolute;
+ top: $spacer*2.5;
+ }
+ }
+}
diff --git a/src/app/+bitstream-page/edit-bitstream-page/edit-bitstream-page.component.ts b/src/app/+bitstream-page/edit-bitstream-page/edit-bitstream-page.component.ts
index e78030d7ff..c74c0005d1 100644
--- a/src/app/+bitstream-page/edit-bitstream-page/edit-bitstream-page.component.ts
+++ b/src/app/+bitstream-page/edit-bitstream-page/edit-bitstream-page.component.ts
@@ -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
>;
+ 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();
+ }
}
}
diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.ts
index 455c1075ef..145ffd8282 100644
--- a/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.ts
+++ b/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.ts
@@ -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 | 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);
diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/models/custom-switch/custom-switch.component.html b/src/app/shared/form/builder/ds-dynamic-form-ui/models/custom-switch/custom-switch.component.html
new file mode 100644
index 0000000000..9d059b4bee
--- /dev/null
+++ b/src/app/shared/form/builder/ds-dynamic-form-ui/models/custom-switch/custom-switch.component.html
@@ -0,0 +1,20 @@
+
+
+
+
+
+
diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/models/custom-switch/custom-switch.component.scss b/src/app/shared/form/builder/ds-dynamic-form-ui/models/custom-switch/custom-switch.component.scss
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/models/custom-switch/custom-switch.component.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/models/custom-switch/custom-switch.component.ts
new file mode 100644
index 0000000000..5bab244587
--- /dev/null
+++ b/src/app/shared/form/builder/ds-dynamic-form-ui/models/custom-switch/custom-switch.component.ts
@@ -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();
+ @Output() remove = new EventEmitter();
+ @Output() blur = new EventEmitter();
+ @Output() change = new EventEmitter();
+ @Output() focus = new EventEmitter();
+}
diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/models/custom-switch/custom-switch.model.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/models/custom-switch/custom-switch.model.ts
new file mode 100644
index 0000000000..73360caa3e
--- /dev/null
+++ b/src/app/shared/form/builder/ds-dynamic-form-ui/models/custom-switch/custom-switch.model.ts
@@ -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);
+ }
+}
diff --git a/src/app/shared/shared.module.ts b/src/app/shared/shared.module.ts
index 164c0ccce9..835e33e3cf 100644
--- a/src/app/shared/shared.module.ts
+++ b/src/app/shared/shared.module.ts
@@ -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 = [