From 62ad7fb68e091be16b32f4d52ab3734a85d0608f Mon Sep 17 00:00:00 2001 From: Florent Poittevin <florent.poittevin@unige.ch> Date: Fri, 10 Jan 2020 14:18:18 +0100 Subject: [PATCH] feat: 865 Manage DIP --- .../deposit-form.presentational.html | 2 +- .../dip-form/dip-form.presentational.html | 99 +++++ .../dip-form/dip-form.presentational.scss | 3 + .../dip-form/dip-form.presentational.ts | 132 +++++++ .../dip-detail-edit.routable.html | 40 ++ .../dip-detail-edit.routable.scss | 27 ++ .../dip-detail-edit.routable.ts | 174 +++++++++ .../routables/dip-file/dip-file.routable.html | 43 +++ .../routables/dip-file/dip-file.routable.scss | 51 +++ .../routables/dip-file/dip-file.routable.ts | 276 ++++++++++++++ .../routables/dip-list/dip-list.routable.ts | 137 +++++++ .../dip-metadata/dip-metadata.routable.html | 12 + .../dip-metadata/dip-metadata.routable.scss | 8 + .../dip-metadata/dip-metadata.routable.ts | 61 +++ .../preservation/dip/dip-routing.module.ts | 77 ++++ .../features/preservation/dip/dip.module.ts | 55 +++ .../preservation/dip/enums/dip-tab.enum.ts | 4 + .../preservation/dip/helpers/dip.helper.ts | 34 ++ .../dip/models/dip-data-file.model.ts | 4 + .../dip/models/dip-extended.model.ts | 4 + .../stores/data-file/dip-data-file.action.ts | 104 +++++ .../stores/data-file/dip-data-file.state.ts | 116 ++++++ .../dip-data-file-status-history.action.ts | 27 ++ .../dip-data-file-status-history.state.ts | 44 +++ .../preservation/dip/stores/dip.action.ts | 130 +++++++ .../preservation/dip/stores/dip.state.ts | 167 ++++++++ .../dip-status-history.action.ts | 27 ++ .../dip-status-history.state.ts | 44 +++ .../preservation-routing.module.ts | 9 + .../preservation/preservation.module.ts | 10 +- .../preservation/stores/preservation.state.ts | 9 +- src/app/shared/enums/api.enum.ts | 6 +- src/app/shared/enums/local-state.enum.ts | 5 + src/app/shared/enums/routes.enum.ts | 5 +- .../shared/utils/store-route-local.util.ts | 6 + src/assets/i18n/de.json | 360 ++++++++++-------- src/assets/i18n/en.json | 76 +++- src/assets/i18n/fr.json | 78 +++- 38 files changed, 2286 insertions(+), 180 deletions(-) create mode 100644 src/app/features/preservation/dip/components/presentationals/dip-form/dip-form.presentational.html create mode 100644 src/app/features/preservation/dip/components/presentationals/dip-form/dip-form.presentational.scss create mode 100644 src/app/features/preservation/dip/components/presentationals/dip-form/dip-form.presentational.ts create mode 100644 src/app/features/preservation/dip/components/routables/dip-detail-edit/dip-detail-edit.routable.html create mode 100644 src/app/features/preservation/dip/components/routables/dip-detail-edit/dip-detail-edit.routable.scss create mode 100644 src/app/features/preservation/dip/components/routables/dip-detail-edit/dip-detail-edit.routable.ts create mode 100644 src/app/features/preservation/dip/components/routables/dip-file/dip-file.routable.html create mode 100644 src/app/features/preservation/dip/components/routables/dip-file/dip-file.routable.scss create mode 100644 src/app/features/preservation/dip/components/routables/dip-file/dip-file.routable.ts create mode 100644 src/app/features/preservation/dip/components/routables/dip-list/dip-list.routable.ts create mode 100644 src/app/features/preservation/dip/components/routables/dip-metadata/dip-metadata.routable.html create mode 100644 src/app/features/preservation/dip/components/routables/dip-metadata/dip-metadata.routable.scss create mode 100644 src/app/features/preservation/dip/components/routables/dip-metadata/dip-metadata.routable.ts create mode 100644 src/app/features/preservation/dip/dip-routing.module.ts create mode 100644 src/app/features/preservation/dip/dip.module.ts create mode 100644 src/app/features/preservation/dip/enums/dip-tab.enum.ts create mode 100644 src/app/features/preservation/dip/helpers/dip.helper.ts create mode 100644 src/app/features/preservation/dip/models/dip-data-file.model.ts create mode 100644 src/app/features/preservation/dip/models/dip-extended.model.ts create mode 100644 src/app/features/preservation/dip/stores/data-file/dip-data-file.action.ts create mode 100644 src/app/features/preservation/dip/stores/data-file/dip-data-file.state.ts create mode 100644 src/app/features/preservation/dip/stores/data-file/status-history/dip-data-file-status-history.action.ts create mode 100644 src/app/features/preservation/dip/stores/data-file/status-history/dip-data-file-status-history.state.ts create mode 100644 src/app/features/preservation/dip/stores/dip.action.ts create mode 100644 src/app/features/preservation/dip/stores/dip.state.ts create mode 100644 src/app/features/preservation/dip/stores/status-history/dip-status-history.action.ts create mode 100644 src/app/features/preservation/dip/stores/status-history/dip-status-history.state.ts diff --git a/src/app/features/deposit/components/presentationals/deposit-form/deposit-form.presentational.html b/src/app/features/deposit/components/presentationals/deposit-form/deposit-form.presentational.html index 2dda8114f..ef70fcfb5 100644 --- a/src/app/features/deposit/components/presentationals/deposit-form/deposit-form.presentational.html +++ b/src/app/features/deposit/components/presentationals/deposit-form/deposit-form.presentational.html @@ -296,7 +296,7 @@ type="submit" [disabled]="!form.valid || !form.dirty" > - {{'deposit.submit' | translate }} + {{'deposit.submit' | translate}} </button> </div> </form> diff --git a/src/app/features/preservation/dip/components/presentationals/dip-form/dip-form.presentational.html b/src/app/features/preservation/dip/components/presentationals/dip-form/dip-form.presentational.html new file mode 100644 index 000000000..f1c4a431b --- /dev/null +++ b/src/app/features/preservation/dip/components/presentationals/dip-form/dip-form.presentational.html @@ -0,0 +1,99 @@ +<form [formGroup]="form" + class="form-two-columns" + (ngSubmit)="onSubmit()" +> + <div class="two-columns-wrapper"> + <div class="left-part"> + <dlcm-shared-searchable-single-select *ngIf="getFormControl(formDefinition.organizationalUnitId) as fd" + solidifyValidation + [resourceNameSpace]="sharedOrgUnitActionNameSpace" + [state]="sharedOrgUnitState" + [formControl]="fd" + [required]="formValidationHelper.hasRequiredField(fd)" + [labelKey]="'name'" + [valueKey]="'resId'" + [placeholder]="'deposit.organizationUnit' | translate" + (valueChange)="orgUnitSelectionChange($event)" + [readonly]="true" + > + </dlcm-shared-searchable-single-select> + + <mat-form-field *ngIf="getFormControl(formDefinition.resId) as fd"> + <input [formControl]="fd" + [placeholder]="'preservation.dip.form.resId' | translate" + matInput + > + </mat-form-field> + + <mat-form-field *ngIf="getFormControl(formDefinition.name) as fd"> + <input [formControl]="fd" + [placeholder]="'preservation.dip.form.name' | translate" + matInput + > + </mat-form-field> + + <mat-form-field *ngIf="getFormControl(formDefinition.description) as fd"> + <input [formControl]="fd" + [placeholder]="'preservation.dip.form.description' | translate" + matInput + > + </mat-form-field> + + <mat-form-field *ngIf="getFormControl(formDefinition.status) as fd"> + <mat-label>{{'preservation.dip.form.status' | translate }}</mat-label> + <mat-select [formControl]="fd" + > + <mat-option *ngFor="let packageStatus of packageStatusEnumValues" + [value]="packageStatus.key" + > + {{packageStatus.value | translate}} + </mat-option> + </mat-select> + </mat-form-field> + </div> + <div class="right-part"> + <mat-form-field *ngIf="getFormControl(formDefinition.access) as fd"> + <mat-label>{{'preservation.dip.form.access' | translate }}</mat-label> + <mat-select [formControl]="fd" + > + <mat-option *ngFor="let accessLevel of accessEnumValues" + [value]="accessLevel.key" + > + {{accessLevel.value | translate}} + </mat-option> + </mat-select> + </mat-form-field> + + <mat-form-field *ngIf="getFormControl(formDefinition.complianceLevel) as fd"> + <input [formControl]="fd" + [value]="model.info.complianceLevel | translate" + [placeholder]="'preservation.dip.form.complianceLevel' | translate" + matInput + > + </mat-form-field> + + <ng-container *ngIf="readonly && model.aipIds && model.aipIds.length > 0 && model.info?.status === packageStatusEnum.COMPLETED"> + <div *ngFor="let aip of model.aipIds" + class="link" + > + <mat-label class="disabled">{{'preservation.dip.form.aip' | translate}} : {{aip}}</mat-label> + <a *ngFor="let storagion of listStoragions" + [href]="getAipPath(aip, storagion.index)" + > + {{'app.aip.storage' | translate:{number: storagion.index} }} + </a> + </div> + </ng-container> + </div> + </div> + <div class="submit-button"> + <button *ngIf="!readonly" + mat-flat-button + color="primary" + type="submit" + [disabled]="!form.valid || !form.dirty" + > + {{'preservation.dip.form.submit' | translate }} + </button> + </div> +</form> diff --git a/src/app/features/preservation/dip/components/presentationals/dip-form/dip-form.presentational.scss b/src/app/features/preservation/dip/components/presentationals/dip-form/dip-form.presentational.scss new file mode 100644 index 000000000..2315c003d --- /dev/null +++ b/src/app/features/preservation/dip/components/presentationals/dip-form/dip-form.presentational.scss @@ -0,0 +1,3 @@ +@import "../sass/abstracts/mixins"; +@import "../sass/abstracts/variables"; +@import "../../src/app/shared/components/presentationals/shared-abstract-form/shared-abstract-form.presentational.scss"; diff --git a/src/app/features/preservation/dip/components/presentationals/dip-form/dip-form.presentational.ts b/src/app/features/preservation/dip/components/presentationals/dip-form/dip-form.presentational.ts new file mode 100644 index 000000000..afb394497 --- /dev/null +++ b/src/app/features/preservation/dip/components/presentationals/dip-form/dip-form.presentational.ts @@ -0,0 +1,132 @@ +import { + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + Input, + OnInit, + Output, +} from "@angular/core"; +import { + FormBuilder, + Validators, +} from "@angular/forms"; +import {MatDialog} from "@angular/material/dialog"; +import {Dip} from "@app/generated-api"; +import {SharedAbstractFormPresentational} from "@app/shared/components/presentationals/shared-abstract-form/shared-abstract-form.presentational"; +import {BaseFormDefinition} from "@app/shared/models/base-form-definition.model"; +import {environment} from "@environments/environment"; +import {DipExtended} from "@preservation/dip/models/dip-extended.model"; +import {AccessLevelEnumHelper} from "@shared/enums/business/access-level-enum.helper"; +import { + PackageStatusEnum, + PackageStatusEnumHelper, +} from "@shared/enums/business/package-status.enum"; +import { + AppRoutesEnum, + PreservationPlanningRoutesEnum, +} from "@shared/enums/routes.enum"; +import {Storage} from "@shared/models/storage.model"; +import {BreakpointService} from "@shared/services/breakpoint.service"; +import {sharedOrgUnitActionNameSpace} from "@shared/stores/organizational-unit/shared-organizational-unit.action"; +import {SharedOrganizationalUnitState} from "@shared/stores/organizational-unit/shared-organizational-unit.state"; +import { + BehaviorSubject, + Observable, +} from "rxjs"; +import { + KeyValue, + ObservableUtil, + PropertyName, + ResourceNameSpace, + SolidifyValidator, + urlSeparator, +} from "solidify-frontend"; + +@Component({ + selector: "dlcm-dip-form", + templateUrl: "./dip-form.presentational.html", + styleUrls: ["./dip-form.presentational.scss"], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class DipFormPresentational extends SharedAbstractFormPresentational<Dip> implements OnInit { + formDefinition: FormComponentFormDefinition = new FormComponentFormDefinition(); + + accessEnumValues: KeyValue[] = AccessLevelEnumHelper.getListKeyValue(); + packageStatusEnumValues: KeyValue[] = PackageStatusEnumHelper.getListKeyValue(); + + sharedOrgUnitActionNameSpace: ResourceNameSpace = sharedOrgUnitActionNameSpace; + sharedOrgUnitState: typeof SharedOrganizationalUnitState = SharedOrganizationalUnitState; + + @Input() + isReady: boolean = false; + + listStoragions: Storage[] = environment.storagionUrls; + + get packageStatusEnum(): typeof PackageStatusEnum { + return PackageStatusEnum; + } + + private readonly _orgUnitValueBS: BehaviorSubject<string | undefined> = new BehaviorSubject<string | undefined>(undefined); + @Output("orgUnitChange") + readonly orgUnitValueObs: Observable<string | undefined> = ObservableUtil.asObservable(this._orgUnitValueBS); + + constructor(protected readonly _changeDetectorRef: ChangeDetectorRef, + private readonly _fb: FormBuilder, + private readonly dialog: MatDialog, + public readonly breakpointService: BreakpointService) { + super(_changeDetectorRef); + } + + ngOnInit(): void { + super.ngOnInit(); + } + + protected initNewForm(): void { + } + + protected bindFormTo(dip: DipExtended): void { + this.form = this._fb.group({ + [this.formDefinition.organizationalUnitId]: [dip.info.organizationalUnitId, [SolidifyValidator]], + [this.formDefinition.resId]: [dip.resId, [SolidifyValidator]], + [this.formDefinition.name]: [dip.info.name, [Validators.required, SolidifyValidator]], + [this.formDefinition.description]: [dip.info.description, [SolidifyValidator]], + [this.formDefinition.status]: [dip.info.status, [SolidifyValidator]], + [this.formDefinition.access]: [dip.info.access, [SolidifyValidator]], + [this.formDefinition.complianceLevel]: [dip.info.complianceLevel, [SolidifyValidator]], + }); + this.isValidWhenDisable = this.form.valid; + } + + protected treatmentBeforeSubmit(dip: DipExtended): DipExtended { + const dipToSubmit: DipExtended = { + info: { + name: this.form.get(this.formDefinition.name).value, + description: this.form.get(this.formDefinition.description).value, + }, + }; + return dipToSubmit; + } + + // @Override + protected disableSpecificField(): void { + this.form.get(this.formDefinition.resId).disable(); + this.form.get(this.formDefinition.status).disable(); + this.form.get(this.formDefinition.access).disable(); + this.form.get(this.formDefinition.complianceLevel).disable(); + } + + getAipPath(aipId: string, storagionIndex: number): string { + return "#" + urlSeparator + AppRoutesEnum.preservation + urlSeparator + PreservationPlanningRoutesEnum.aip + urlSeparator + storagionIndex + urlSeparator + PreservationPlanningRoutesEnum.aipDetail + urlSeparator + aipId; + } +} + +class FormComponentFormDefinition extends BaseFormDefinition { + @PropertyName() organizationalUnitId: string; + @PropertyName() resId: string; + @PropertyName() name: string; + @PropertyName() description: string; + @PropertyName() status: string; + @PropertyName() access: string; + @PropertyName() complianceLevel: string; + @PropertyName() aip: string; +} diff --git a/src/app/features/preservation/dip/components/routables/dip-detail-edit/dip-detail-edit.routable.html b/src/app/features/preservation/dip/components/routables/dip-detail-edit/dip-detail-edit.routable.html new file mode 100644 index 000000000..db50badde --- /dev/null +++ b/src/app/features/preservation/dip/components/routables/dip-detail-edit/dip-detail-edit.routable.html @@ -0,0 +1,40 @@ +<div> + <dlcm-shared-button-status-history class="history" + (showHistory)="showHistory()" + ></dlcm-shared-button-status-history> + + <dlcm-button-toolbar-detail [mode]="isEdit ? 'edit' : 'detail'" + [currentModel]="currentObs | async" + [deleteAvailable]="false" + [listExtraButtons]="listExtraButtons" + (editChange)="edit()" + (deleteChange)="delete()" + (backToDetailChange)="backToDetail()" + (backToListChange)="backToList()" + > + </dlcm-button-toolbar-detail> +</div> + +<mat-tab-group #matTabGroup + mat-align-tabs="center" + animationDuration="0ms" + [selectedIndex]="tabSelected" + (selectedIndexChange)="navigate($event)" +> + <mat-tab> + <ng-template mat-tab-label> + <mat-icon class="tab-header-icon">assignment</mat-icon> + <span class="tab-header-label">{{'preservation.dip.tab.details' | translate}}</span> + </ng-template> + </mat-tab> + <mat-tab> + <ng-template mat-tab-label> + <mat-icon class="tab-header-icon">insert_drive_file</mat-icon> + <span class="tab-header-label">{{'preservation.dip.tab.datafiles' | translate}}</span> + </ng-template> + </mat-tab> +</mat-tab-group> + +<div class="tab-content"> + <router-outlet></router-outlet> +</div> diff --git a/src/app/features/preservation/dip/components/routables/dip-detail-edit/dip-detail-edit.routable.scss b/src/app/features/preservation/dip/components/routables/dip-detail-edit/dip-detail-edit.routable.scss new file mode 100644 index 000000000..6157c9dad --- /dev/null +++ b/src/app/features/preservation/dip/components/routables/dip-detail-edit/dip-detail-edit.routable.scss @@ -0,0 +1,27 @@ +@import "../sass/abstracts/variables"; +@import "../sass/abstracts/mixins"; +@import "./../../../../../../shared/components/routables/shared-abstract-detail-edit-common/shared-abstract-detail-edit-common.routable"; + +$height-alert: 30px; + +:host { + $padding-top: $height-alert + 10px; + padding-top: $padding-top; + + @include respond-to-breakpoint("xs") { + $padding-top: $height-alert + 10px + 20px; + padding-top: $padding-top; + } + + .tab-header-icon { + } + + .tab-header-label { + padding-left: 10px; + + @include respond-to-breakpoint("xs") { + display: none; + } + } +} + diff --git a/src/app/features/preservation/dip/components/routables/dip-detail-edit/dip-detail-edit.routable.ts b/src/app/features/preservation/dip/components/routables/dip-detail-edit/dip-detail-edit.routable.ts new file mode 100644 index 000000000..94b9336fa --- /dev/null +++ b/src/app/features/preservation/dip/components/routables/dip-detail-edit/dip-detail-edit.routable.ts @@ -0,0 +1,174 @@ +import { + ChangeDetectorRef, + Component, + OnDestroy, + OnInit, + ViewChild, +} from "@angular/core"; +import {MatDialog} from "@angular/material"; +import {MatTabGroup} from "@angular/material/tabs"; +import { + ActivatedRoute, + NavigationCancel, + NavigationEnd, + Router, +} from "@angular/router"; +import {DipStateModel} from "@app/features/preservation/dip/stores/dip.state"; +import {Dip} from "@app/generated-api"; +import {LocalStateEnum} from "@app/shared/enums/local-state.enum"; +import {Navigate} from "@ngxs/router-plugin"; +import { + Actions, + Store, +} from "@ngxs/store"; +import {DipFormPresentational} from "@preservation/dip/components/presentationals/dip-form/dip-form.presentational"; +import {DipTabEnum} from "@preservation/dip/enums/dip-tab.enum"; +import {DipHelper} from "@preservation/dip/helpers/dip.helper"; +import { + PreservationDipAction, + preservationDipActionNameSpace, +} from "@preservation/dip/stores/dip.action"; +import {PreservationDipStatusHistoryAction} from "@preservation/dip/stores/status-history/dip-status-history.action"; +import {DipStatusHistoryState} from "@preservation/dip/stores/status-history/dip-status-history.state"; +import {SharedHistoryDialog} from "@shared/components/dialogs/shared-history/shared-history.dialog"; +import {SharedAbstractDetailEditRoutable} from "@shared/components/routables/shared-abstract-detail-edit/shared-abstract-detail-edit.routable"; +import {PackageStatusEnum} from "@shared/enums/business/package-status.enum"; +import { + DepositRoutesEnum, + RoutesEnum, +} from "@shared/enums/routes.enum"; +import {ExtraButtonToolbar} from "@shared/models/extra-button-toolbar.model"; +import {StatusHistoryDialog} from "@shared/models/status-history-dialog.model"; +import {StatusHistory} from "@shared/models/status-history.model"; +import {SecurityService} from "@shared/services/security.service"; +import {Observable} from "rxjs"; +import {tap} from "rxjs/internal/operators/tap"; +import { + distinctUntilChanged, + filter, +} from "rxjs/operators"; +import { + isNullOrUndefined, + MemoizedUtil, + QueryParameters, + TRANSLATE, +} from "solidify-frontend"; + +@Component({ + selector: "dlcm-dip-detail-edit-routable", + templateUrl: "./dip-detail-edit.routable.html", + styleUrls: ["./dip-detail-edit.routable.scss"], +}) +export class DipDetailEditRoutable extends SharedAbstractDetailEditRoutable<Dip, DipStateModel> implements OnInit, OnDestroy { + historyObs: Observable<StatusHistory[]> = MemoizedUtil.select(this._store, DipStatusHistoryState, state => state.history); + isLoadingObs: Observable<boolean> = MemoizedUtil.isLoading(this._store, DipStatusHistoryState); + queryParametersObs: Observable<QueryParameters> = MemoizedUtil.select(this._store, DipStatusHistoryState, state => state.queryParameters); + + @ViewChild("formPresentational", {static: false}) + readonly formPresentational: DipFormPresentational; + + @ViewChild("matTabGroup", {static: false}) + readonly matTabGroup: MatTabGroup; + + message: string; + tabSelected: DipTabEnum; + + get packageStatusEnum(): typeof PackageStatusEnum { + return PackageStatusEnum; + } + + readonly KEY_PARAM_NAME: keyof Dip & string = undefined; + + constructor(protected readonly _store: Store, + protected readonly _route: ActivatedRoute, + protected readonly _actions$: Actions, + protected readonly _changeDetector: ChangeDetectorRef, + protected readonly _dialog: MatDialog, + protected readonly _router: Router, + private readonly _securityService: SecurityService) { + super(_store, _route, _actions$, _changeDetector, _dialog, LocalStateEnum.preservation_dip, preservationDipActionNameSpace, LocalStateEnum.preservation); + } + + ngOnInit(): void { + super.ngOnInit(); + + this.retrieveCurrentModelWithUrl(); + + this._computeCurrentTab(); + this.subscribe(this._router.events + .pipe( + filter(event => event instanceof NavigationCancel || event instanceof NavigationEnd), + distinctUntilChanged(), + tap(event => { + this._computeCurrentTab(); + this.matTabGroup.selectedIndex = this.tabSelected; + }), + ), + ); + } + + ngOnDestroy(): void { + super.ngOnDestroy(); + this.cleanState(); + } + + private _computeCurrentTab(): void { + const tabRouteSelected = DipHelper.getTabRouteSelected(this._route); + this.tabSelected = DipHelper.getTabSelectedIndexWithRoute(tabRouteSelected); + } + + getSubResourceWithParentId(id: string): void { + } + + showHistory(): void { + const dialogRef = this._dialog.open(SharedHistoryDialog, { + width: "800px", + data: { + parentId: null, + resourceResId: this._resId, + name: this._state, + statusHistory: this.historyObs, + isLoading: this.isLoadingObs, + queryParametersObs: this.queryParametersObs, + state: PreservationDipStatusHistoryAction, + } as StatusHistoryDialog, + }); + } + + listExtraButtons: ExtraButtonToolbar<Dip>[] = [ + { + color: "accent", + icon: "file_download", + displayCondition: current => !this.isEdit && !isNullOrUndefined(current) && !isNullOrUndefined(current.info) && current.info.status === PackageStatusEnum.COMPLETED, + callback: () => this.download(), + labelToTranslate: TRANSLATE("preservation.dip.button.download"), + }, + { + color: "primary", + icon: "play_circle_filled", + displayCondition: current => !this.isEdit && !isNullOrUndefined(current) && !isNullOrUndefined(current.info) && current.info.status === PackageStatusEnum.IN_ERROR, + callback: () => this.resume(), + labelToTranslate: TRANSLATE("preservation.dip.button.resume"), + }, + ]; + + // @Override + edit(): void { + const tabRouteSelected = DipHelper.getTabRouteSelected(this._route); + this._store.dispatch(new Navigate([RoutesEnum.preservationDipDetail, this._resId, tabRouteSelected, DepositRoutesEnum.edit])); + } + + navigate(tabSelected: DipTabEnum): void { + const tabRouteSelected = DipHelper.getTabRouteSelectedWithTabIndex(tabSelected); + const path = [RoutesEnum.preservationDipDetail, this._resId, tabRouteSelected]; + this._store.dispatch(new Navigate(path)); + } + + download(): void { + this._store.dispatch(new PreservationDipAction.Download(this._resId)); + } + + resume(): void { + this._store.dispatch(new PreservationDipAction.Resume(this._resId)); + } +} diff --git a/src/app/features/preservation/dip/components/routables/dip-file/dip-file.routable.html b/src/app/features/preservation/dip/components/routables/dip-file/dip-file.routable.html new file mode 100644 index 000000000..d60f63010 --- /dev/null +++ b/src/app/features/preservation/dip/components/routables/dip-file/dip-file.routable.html @@ -0,0 +1,43 @@ +<div> + <div class="tab-content"> + + <div class="file-uploaded"> + <h1>{{'deposit.file.title.filesOfDeposit' | translate}}</h1> + + <div class="file-uploaded-header"> + <button mat-icon-button + mat-button + (click)="refresh()" + [matTooltip]="'deposit.file.tree.refresh' | translate" + [matTooltipPosition]="'left'" + [class.spinning]="isLoadingDataFileObs | async" + [disabled]="isLoadingDataFileObs | async" + > + <fa-icon icon="sync-alt"></fa-icon> + </button> + </div> + + <div class="file-viewer"> + <div class="file-list-wrapper" + [dlcmSpinner]="isLoadingDataFileObs | async" + > + <dlcm-shared-data-table *ngIf="columns" + [columns]="columns" + [actions]="actions" + [isLoading]="isLoadingDataFileObs | async" + [datas]="listDataFileObs | async" + [queryParameters]="queryParametersObs | async" + (queryParametersChange)="onQueryParametersEvent($event)" + (selectChange)="showDetail($event)" + > + </dlcm-shared-data-table> + </div> + + <ng-template #noFile> + <label>{{'deposit.file.tree.noFile' | translate}}</label> + </ng-template> + </div> + </div> + + </div> +</div> diff --git a/src/app/features/preservation/dip/components/routables/dip-file/dip-file.routable.scss b/src/app/features/preservation/dip/components/routables/dip-file/dip-file.routable.scss new file mode 100644 index 000000000..06cbc8968 --- /dev/null +++ b/src/app/features/preservation/dip/components/routables/dip-file/dip-file.routable.scss @@ -0,0 +1,51 @@ +@import "../sass/abstracts/variables"; +@import "../sass/abstracts/mixins"; +@import "../../../../../../shared/components/routables/shared-abstract-detail-edit/shared-abstract-detail-edit.routable"; + +:host { + padding: 0; + + .tab-content { + + .file-uploaded { + + .file-uploaded-header { + display: flex; + justify-content: space-between; + align-items: center; + position: relative; + min-height: 35px; + + fa-icon { + font-size: 20px; + } + + button { + transition-property: transform; + transition-duration: 1s; + + position: absolute; + top: -10px; + right: 10px; + + &.spinning { + animation-name: rotate; + animation-duration: 2s; + animation-iteration-count: infinite; + animation-timing-function: linear; + } + + @keyframes rotate { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } + } + } + } + } + } + +} diff --git a/src/app/features/preservation/dip/components/routables/dip-file/dip-file.routable.ts b/src/app/features/preservation/dip/components/routables/dip-file/dip-file.routable.ts new file mode 100644 index 000000000..c55df2267 --- /dev/null +++ b/src/app/features/preservation/dip/components/routables/dip-file/dip-file.routable.ts @@ -0,0 +1,276 @@ +import { + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + OnInit, + ViewChild, +} from "@angular/core"; +import {MatDialog} from "@angular/material"; +import {ActivatedRoute} from "@angular/router"; +import {DipStateModel} from "@app/features/preservation/dip/stores/dip.state"; +import {Dip} from "@app/generated-api"; +import {environment} from "@environments/environment"; +import { + Actions, + Store, +} from "@ngxs/store"; +import {DipFormPresentational} from "@preservation/dip/components/presentationals/dip-form/dip-form.presentational"; +import {DipDataFile} from "@preservation/dip/models/dip-data-file.model"; +import {PreservationDipDataFileAction} from "@preservation/dip/stores/data-file/dip-data-file.action"; +import {DipDataFileState} from "@preservation/dip/stores/data-file/dip-data-file.state"; +import {PreservationDipDataFileStatusHistoryAction} from "@preservation/dip/stores/data-file/status-history/dip-data-file-status-history.action"; +import {DipDataFileStatusHistoryState} from "@preservation/dip/stores/data-file/status-history/dip-data-file-status-history.state"; +import {preservationDipActionNameSpace} from "@preservation/dip/stores/dip.action"; +import { + SharedFileDetailDialog, + SharedFileDetailDialogData, +} from "@shared/components/dialogs/shared-file-detail/shared-file-detail.dialog"; +import {SharedAbstractDetailEditRoutable} from "@shared/components/routables/shared-abstract-detail-edit/shared-abstract-detail-edit.routable"; +import {ComplianceLevelEnum} from "@shared/enums/business/compliance-level.enum"; +import {DataFileStatusEnum} from "@shared/enums/business/data-file-status.enum"; +import {DataTableComponentEnum} from "@shared/enums/data-table-component.enum"; +import {FieldTypeEnum} from "@shared/enums/field-type.enum"; +import {LocalStateEnum} from "@shared/enums/local-state.enum"; +import {AppRoutesEnum} from "@shared/enums/routes.enum"; +import {DataTableActions} from "@shared/models/data-table-actions.model"; +import {DataTableColumns} from "@shared/models/data-table-columns.model"; +import {StatusHistory} from "@shared/models/status-history.model"; +import {Observable} from "rxjs"; +import { + CompositionState, + isNullOrUndefined, + MemoizedUtil, + OrderEnum, + QueryParameters, + TRANSLATE, +} from "solidify-frontend"; + +@Component({ + selector: "dlcm-dip-file-routable", + templateUrl: "./dip-file.routable.html", + styleUrls: ["./dip-file.routable.scss"], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class DipFileRoutable extends SharedAbstractDetailEditRoutable<Dip, DipStateModel> implements OnInit { + isLoadingDataFileObs: Observable<boolean> = MemoizedUtil.isLoading(this._store, DipDataFileState); + listDataFileObs: Observable<DipDataFile[]> = CompositionState.list(this._store, DipDataFileState); + queryParametersObs: Observable<QueryParameters> = CompositionState.queryParameters(this._store, DipDataFileState); + + isLoadingHistoryObs: Observable<boolean> = MemoizedUtil.isLoading(this._store, DipDataFileStatusHistoryState); + dataFileHistoryObs: Observable<StatusHistory[]> = MemoizedUtil.select(this._store, DipDataFileStatusHistoryState, state => state.history); + queryParametersHistoryObs: Observable<QueryParameters> = MemoizedUtil.select(this._store, DipDataFileStatusHistoryState, state => state.queryParameters); + + @ViewChild("formPresentational", {static: false}) + readonly formPresentational: DipFormPresentational; + + columns: DataTableColumns<DipDataFile>[]; + actions: DataTableActions<DipDataFile>[]; + + readonly KEY_PARAM_NAME: keyof Dip & string = undefined; + + constructor(protected readonly _store: Store, + protected readonly _route: ActivatedRoute, + protected readonly _actions$: Actions, + protected readonly _changeDetector: ChangeDetectorRef, + protected readonly _dialog: MatDialog) { + super(_store, _route, _actions$, _changeDetector, _dialog, LocalStateEnum.preservation_dip, preservationDipActionNameSpace, LocalStateEnum.preservation); + } + + ngOnInit(): void { + super.ngOnInit(); + this.getCurrentModelOnParent(); + + this.columns = [ + { + field: "fileName", + header: TRANSLATE("deposit.datafile.table.fileName"), + type: FieldTypeEnum.string, + order: OrderEnum.none, + isSortable: true, + isFilterable: false, + }, + { + field: "creation.when" as any, + header: TRANSLATE("deposit.datafile.table.creation.when"), + type: FieldTypeEnum.datetime, + order: OrderEnum.none, + isFilterable: false, + isSortable: true, + }, + { + field: "status", + header: TRANSLATE("deposit.datafile.table.status"), + type: FieldTypeEnum.singleSelect, + order: OrderEnum.none, + isSortable: false, + isFilterable: false, + translate: true, + filterEnum: [ + { + key: DataFileStatusEnum.IN_ERROR, + value: TRANSLATE("IN_ERROR"), + }, + { + key: DataFileStatusEnum.READY, + value: TRANSLATE("READY"), + }, + { + key: DataFileStatusEnum.RECEIVED, + value: TRANSLATE("RECEIVED"), + }, + { + key: DataFileStatusEnum.CHANGE_RELATIVE_LOCATION, + value: TRANSLATE("CHANGE_RELATIVE_LOCATION"), + }, + { + key: DataFileStatusEnum.TO_PROCESS, + value: TRANSLATE("TO_PROCESS"), + }, + { + key: DataFileStatusEnum.PROCESSED, + value: TRANSLATE("PROCESSED"), + }, + { + key: DataFileStatusEnum.FILE_FORMAT_IDENTIFIED, + value: TRANSLATE("FILE_FORMAT_IDENTIFIED"), + }, + { + key: DataFileStatusEnum.FILE_FORMAT_UNKNOWN, + value: TRANSLATE("FILE_FORMAT_UNKNOWN"), + }, + { + key: DataFileStatusEnum.VIRUS_CHECKED, + value: TRANSLATE("VIRUS_CHECKED"), + }, + { + key: DataFileStatusEnum.CLEANING, + value: TRANSLATE("CLEANING"), + }, + { + key: DataFileStatusEnum.CLEANED, + value: TRANSLATE("CLEANED"), + }, + ], + }, + { + field: "complianceLevel", + header: TRANSLATE("deposit.datafile.table.complianceLevel"), + type: FieldTypeEnum.singleSelect, + order: OrderEnum.none, + isSortable: false, + isFilterable: false, + translate: true, + component: DataTableComponentEnum.conformityLevelStar, + filterEnum: [ + { + key: ComplianceLevelEnum.FULL_COMPLIANCE, + value: TRANSLATE("FULL_COMPLIANCE"), + }, + { + key: ComplianceLevelEnum.AVERAGE_COMPLIANCE, + value: TRANSLATE("AVERAGE_COMPLIANCE"), + }, + { + key: ComplianceLevelEnum.WEAK_COMPLIANCE, + value: TRANSLATE("WEAK_COMPLIANCE"), + }, + { + key: ComplianceLevelEnum.NO_COMPLIANCE, + value: TRANSLATE("NO_COMPLIANCE"), + }, + { + key: ComplianceLevelEnum.NOT_ASSESSED, + value: TRANSLATE("NOT_ASSESSED"), + }, + ], + }, + ]; + + this.actions = [ + { + logo: "warning", + callback: (dipDataFile: DipDataFile) => this.resumeDataFile(this._resId, dipDataFile), + placeholder: TRANSLATE("crud.list.action.resume"), + displayOnCondition: current => isNullOrUndefined(current) || current.status === DataFileStatusEnum.IN_ERROR, + }, + { + logo: "file_download", + callback: (dipDataFile: DipDataFile) => this.downloadDataFile(this._resId, dipDataFile), + placeholder: TRANSLATE("crud.list.action.download"), + displayOnCondition: (dipDataFile: DipDataFile) => true, + }, + ]; + } + + private getCurrentModelOnParent(): void { + this._resId = this._route.snapshot.parent.paramMap.get(AppRoutesEnum.paramIdWithoutPrefixParam); + this.getDepositById(this._resId); + } + + private getDepositById(id: string): void { + // this._store.dispatch(ResourceActionHelper.getById(preservationDipActionNameSpace, id, true)); + this.getSubResourceWithParentId(id); + } + + getSubResourceWithParentId(id: string): void { + // const queryParameter = new QueryParameters(); + // MappingObjectUtil.set(queryParameter.search.searchItems, "relativeLocation", "/"); + this._store.dispatch(new PreservationDipDataFileAction.GetAll(id, null, true)); + // this._store.dispatch(new PreservationDipDataFileAction.GetListFolder(id)); + } + + onQueryParametersEvent(queryParameters: QueryParameters): void { + this._store.dispatch(new PreservationDipDataFileAction.ChangeQueryParameters(this._resId, queryParameters, true)); + // this._changeDetector.detectChanges(); // Allow to display spinner the first time + } + + refresh(): void { + this._store.dispatch(new PreservationDipDataFileAction.Refresh(this._resId)); + } + + download($event: DipDataFile): void { + this._store.dispatch(new PreservationDipDataFileAction.Download(this._resId, $event)); + } + + showDetail(dipDataFile: DipDataFile): void { + const dialogRef = this._dialog.open(SharedFileDetailDialog, { + data: { + dataFile: dipDataFile, + parentId: this._resId, + isReadonly: true, + isLoading: this.isLoadingHistoryObs, + statusHistory: this.dataFileHistoryObs, + queryParametersObs: this.queryParametersHistoryObs, + state: PreservationDipDataFileStatusHistoryAction, + name: LocalStateEnum.preservation_dip, + buttons: [ + { + labelToTranslate: TRANSLATE("deposit.file.button.download"), + color: "accent", + icon: "file_download", + callback: (current) => this.downloadDataFile(this._resId, dipDataFile), + order: 2, + }, + { + labelToTranslate: TRANSLATE("deposit.file.button.resume"), + color: "accent", + icon: "warning", + callback: (current) => this.resumeDataFile(this._resId, dipDataFile), + displayCondition: current => current.status === DataFileStatusEnum.IN_ERROR, + order: 3, + }, + ], + } as SharedFileDetailDialogData<DipDataFile>, + width: environment.modalWidth, + }); + } + + private downloadDataFile(parentId: string, dataFile: DipDataFile): void { + this._store.dispatch(new PreservationDipDataFileAction.Download(parentId, dataFile)); + } + + private resumeDataFile(parentId: string, dataFile: DipDataFile): void { + this._store.dispatch(new PreservationDipDataFileAction.Resume(parentId, dataFile)); + + } +} diff --git a/src/app/features/preservation/dip/components/routables/dip-list/dip-list.routable.ts b/src/app/features/preservation/dip/components/routables/dip-list/dip-list.routable.ts new file mode 100644 index 000000000..061131baf --- /dev/null +++ b/src/app/features/preservation/dip/components/routables/dip-list/dip-list.routable.ts @@ -0,0 +1,137 @@ +import { + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + OnInit, +} from "@angular/core"; +import {MatDialog} from "@angular/material/dialog"; +import {ActivatedRoute} from "@angular/router"; +import {DipStateModel} from "@app/features/preservation/dip/stores/dip.state"; +import { + Dip, + PreservationJob, +} from "@app/generated-api"; +import {SharedAbstractListRoutable} from "@app/shared/components/routables/shared-abstract-list/shared-abstract-list.routable"; +import {FieldTypeEnum} from "@app/shared/enums/field-type.enum"; +import {LocalStateEnum} from "@app/shared/enums/local-state.enum"; +import {appAuthorizedOrganizationalUnitNameSpace} from "@app/stores/authorized-organizational-unit/app-authorized-organizational-unit.action"; +import {AppAuthorizedOrganizationalUnitState} from "@app/stores/authorized-organizational-unit/app-authorized-organizational-unit.state"; +import { + Actions, + Store, +} from "@ngxs/store"; +import { + PreservationDipAction, + preservationDipActionNameSpace, +} from "@preservation/dip/stores/dip.action"; +import {PackageStatusEnumHelper} from "@shared/enums/business/package-status.enum"; +import {DataTableActions} from "@shared/models/data-table-actions.model"; +import { + OrderEnum, + ResourceNameSpace, + TRANSLATE, +} from "solidify-frontend"; + +@Component({ + selector: "dlcm-dip-list-routable", + templateUrl: "../../../../../../shared/components/routables/shared-abstract-list/shared-abstract-list.routable.html", + styleUrls: ["../../../../../../shared/components/routables/shared-abstract-list/shared-abstract-list.routable.scss"], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class DipListRoutable extends SharedAbstractListRoutable<Dip, DipStateModel> implements OnInit { + readonly KEY_CREATE_BUTTON: string = undefined; + readonly KEY_REFRESH_BUTTON: string = TRANSLATE("preservation.dip.refresh"); + readonly KEY_BACK_BUTTON: string = TRANSLATE("preservation.dip.back"); + readonly KEY_PARAM_NAME: keyof Dip & string = undefined; + + appAuthorizedOrganizationalUnitNameSpace: ResourceNameSpace = appAuthorizedOrganizationalUnitNameSpace; + appAuthorizedOrganizationalUnitState: typeof AppAuthorizedOrganizationalUnitState = AppAuthorizedOrganizationalUnitState; + + constructor(protected readonly _store: Store, + protected readonly _changeDetector: ChangeDetectorRef, + protected readonly _route: ActivatedRoute, + protected readonly _actions$: Actions, + protected readonly _dialog: MatDialog) { + super(_store, _changeDetector, _route, _actions$, _dialog, LocalStateEnum.preservation_dip, preservationDipActionNameSpace, { + canCreate: false, + }, LocalStateEnum.preservation); + } + + conditionDisplayEditButton(model: Dip | undefined): boolean { + return false; + } + + conditionDisplayDeleteButton(model: Dip | undefined): boolean { + return false; + } + + ngOnInit(): void { + super.ngOnInit(); + } + + defineColumns(): void { + this.columns = [ + { + field: "info.name" as any, + header: TRANSLATE("preservation.dip.table.header.title"), + type: FieldTypeEnum.string, + order: OrderEnum.none, + isFilterable: true, + isSortable: true, + }, + // { + // field: "organizationalUnit.name" as any, + // header: TRANSLATE("preservation.dip.table.header.organizationalUnit"), + // type: FieldTypeEnum.searchableSingleSelect, + // order: OrderEnum.none, + // isFilterable: false, + // // isSortable: true, + // resourceNameSpace: this.appAuthorizedOrganizationalUnitNameSpace, + // resourceState: this.appAuthorizedOrganizationalUnitState as any, + // filterableField: "info.organizationalUnitId" as any, + // }, + { + field: "creation.when" as any, + header: TRANSLATE("preservation.dip.table.header.creation.when"), + type: FieldTypeEnum.datetime, + order: OrderEnum.none, + isFilterable: true, + isSortable: true, + }, + { + field: "lastUpdate.when" as any, + header: TRANSLATE("preservation.dip.table.header.lastUpdate.when"), + type: FieldTypeEnum.datetime, + order: OrderEnum.descending, + isFilterable: true, + isSortable: true, + }, + { + field: "info.status" as any, + header: TRANSLATE("preservation.dip.table.header.status"), + type: FieldTypeEnum.singleSelect, + order: OrderEnum.none, + isFilterable: true, + isSortable: true, + translate: true, + filterEnum: PackageStatusEnumHelper.getListKeyValue(), + }, + ]; + } + + // @Override + protected defineActions(): DataTableActions<PreservationJob>[] { + return [ + { + logo: "file_download", + callback: (model: Dip) => this.download(model), + placeholder: TRANSLATE("crud.list.action.download"), + displayOnCondition: (model: Dip) => true, + }, + ]; + } + + private download(dip: Dip): void { + this._store.dispatch(new PreservationDipAction.Download(dip.resId)); + } +} diff --git a/src/app/features/preservation/dip/components/routables/dip-metadata/dip-metadata.routable.html b/src/app/features/preservation/dip/components/routables/dip-metadata/dip-metadata.routable.html new file mode 100644 index 000000000..086a86f89 --- /dev/null +++ b/src/app/features/preservation/dip/components/routables/dip-metadata/dip-metadata.routable.html @@ -0,0 +1,12 @@ +<div class="wrapper" + [dlcmSpinner]="(isLoadingObs | async)" +> + <dlcm-dip-form #formPresentational + *ngIf="isReadyToBeDisplayedObs | async" + [model]="currentObs| async" + [readonly]="!isEdit" + (submitChange)="update($event)" + (dirtyChange)="updateCanDeactivate($event)" + > + </dlcm-dip-form> +</div> diff --git a/src/app/features/preservation/dip/components/routables/dip-metadata/dip-metadata.routable.scss b/src/app/features/preservation/dip/components/routables/dip-metadata/dip-metadata.routable.scss new file mode 100644 index 000000000..d288fce4d --- /dev/null +++ b/src/app/features/preservation/dip/components/routables/dip-metadata/dip-metadata.routable.scss @@ -0,0 +1,8 @@ +@import "../sass/abstracts/variables"; +@import "../sass/abstracts/mixins"; + +:host { + .wrapper { + min-height: 300px; + } +} diff --git a/src/app/features/preservation/dip/components/routables/dip-metadata/dip-metadata.routable.ts b/src/app/features/preservation/dip/components/routables/dip-metadata/dip-metadata.routable.ts new file mode 100644 index 000000000..e466d13bd --- /dev/null +++ b/src/app/features/preservation/dip/components/routables/dip-metadata/dip-metadata.routable.ts @@ -0,0 +1,61 @@ +import { + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + OnDestroy, + OnInit, + ViewChild, +} from "@angular/core"; +import {MatDialog} from "@angular/material"; +import {ActivatedRoute} from "@angular/router"; +import { + DipState, + DipStateModel, +} from "@app/features/preservation/dip/stores/dip.state"; +import {Dip} from "@app/generated-api"; +import {DepositFormPresentational} from "@deposit/components/presentationals/deposit-form/deposit-form.presentational"; +import { + Actions, + Select, + Store, +} from "@ngxs/store"; +import {preservationDipActionNameSpace} from "@preservation/dip/stores/dip.action"; +import {SharedAbstractDetailEditRoutable} from "@shared/components/routables/shared-abstract-detail-edit/shared-abstract-detail-edit.routable"; +import {LocalStateEnum} from "@shared/enums/local-state.enum"; +import {Observable} from "rxjs"; + +@Component({ + selector: "dlcm-dip-metadata-routable", + templateUrl: "./dip-metadata.routable.html", + styleUrls: ["./dip-metadata.routable.scss"], + changeDetection: ChangeDetectionStrategy.OnPush, +}) + +export class DipMetadataRoutable extends SharedAbstractDetailEditRoutable<Dip, DipStateModel> implements OnInit, OnDestroy { + @Select(DipState.isLoadingWithDependency) isLoadingWithDependencyObs: Observable<boolean>; + @Select(DipState.isReadyToBeDisplayed) isReadyToBeDisplayedObs: Observable<boolean>; + + @ViewChild("formPresentational", {static: false}) + readonly formPresentational: DepositFormPresentational; + + readonly KEY_PARAM_NAME: keyof Dip & string = undefined; + + constructor(protected readonly _store: Store, + protected readonly _route: ActivatedRoute, + protected readonly _actions$: Actions, + protected readonly _changeDetector: ChangeDetectorRef, + protected readonly _dialog: MatDialog) { + super(_store, _route, _actions$, _changeDetector, _dialog, LocalStateEnum.preservation_dip, preservationDipActionNameSpace, LocalStateEnum.preservation); + } + + ngOnInit(): void { + super.ngOnInit(); + } + + ngOnDestroy(): void { + super.ngOnDestroy(); + } + + protected getSubResourceWithParentId(id: string): void { + } +} diff --git a/src/app/features/preservation/dip/dip-routing.module.ts b/src/app/features/preservation/dip/dip-routing.module.ts new file mode 100644 index 000000000..3221da4fd --- /dev/null +++ b/src/app/features/preservation/dip/dip-routing.module.ts @@ -0,0 +1,77 @@ +import {NgModule} from "@angular/core"; +import {RouterModule} from "@angular/router"; +import { + AppRoutesEnum, + PreservationPlanningRoutesEnum, +} from "@app/shared/enums/routes.enum"; +import {DlcmRoutes} from "@app/shared/models/dlcm-route.model"; +import {DipDetailEditRoutable} from "@preservation/dip/components/routables/dip-detail-edit/dip-detail-edit.routable"; +import {DipFileRoutable} from "@preservation/dip/components/routables/dip-file/dip-file.routable"; +import {DipListRoutable} from "@preservation/dip/components/routables/dip-list/dip-list.routable"; +import {DipMetadataRoutable} from "@preservation/dip/components/routables/dip-metadata/dip-metadata.routable"; +import {DipState} from "@preservation/dip/stores/dip.state"; +import {CanDeactivateGuard} from "@shared/services/can-deactivate-guard.service"; +import {TRANSLATE} from "solidify-frontend"; + +const routes: DlcmRoutes = [ + { + path: AppRoutesEnum.root, + component: DipListRoutable, + data: {}, + }, + { + path: PreservationPlanningRoutesEnum.dipDetail + AppRoutesEnum.separator + AppRoutesEnum.paramId, + redirectTo: PreservationPlanningRoutesEnum.dipDetail + AppRoutesEnum.separator + AppRoutesEnum.paramId + AppRoutesEnum.separator + PreservationPlanningRoutesEnum.dipMetadata, + pathMatch: "full", + }, + { + path: PreservationPlanningRoutesEnum.dipDetail + AppRoutesEnum.separator + AppRoutesEnum.paramId, + component: DipDetailEditRoutable, + data: { + breadcrumbMemoizedSelector: DipState.currentTitle, + }, + children: [ + { + path: PreservationPlanningRoutesEnum.dipMetadata, + component: DipMetadataRoutable, + data: { + breadcrumb: TRANSLATE("breadcrumb.preservation.dip.metadata"), + noBreadcrumbLink: true, + }, + children: [ + { + path: PreservationPlanningRoutesEnum.dipEdit, + data: { + breadcrumb: TRANSLATE("breadcrumb.preservation.dip.edit"), + }, + canDeactivate: [CanDeactivateGuard], + }, + ], + }, + { + path: PreservationPlanningRoutesEnum.dipFiles, + component: DipFileRoutable, + data: { + breadcrumb: TRANSLATE("breadcrumb.preservation.dip.file"), + noBreadcrumbLink: true, + }, + children: [ + { + path: PreservationPlanningRoutesEnum.dipEdit, + data: { + breadcrumb: TRANSLATE("breadcrumb.preservation.dip.edit"), + }, + canDeactivate: [CanDeactivateGuard], + }, + ], + }, + ], + }, +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule], +}) +export class DipRoutingModule { +} diff --git a/src/app/features/preservation/dip/dip.module.ts b/src/app/features/preservation/dip/dip.module.ts new file mode 100644 index 000000000..5459c00e0 --- /dev/null +++ b/src/app/features/preservation/dip/dip.module.ts @@ -0,0 +1,55 @@ +import {NgModule} from "@angular/core"; +import {DipFormPresentational} from "@app/features/preservation/dip/components/presentationals/dip-form/dip-form.presentational"; +import {DipFileRoutable} from "@app/features/preservation/dip/components/routables/dip-file/dip-file.routable"; +import {DipListRoutable} from "@app/features/preservation/dip/components/routables/dip-list/dip-list.routable"; +import {DipMetadataRoutable} from "@app/features/preservation/dip/components/routables/dip-metadata/dip-metadata.routable"; +import {DipDataFileState} from "@app/features/preservation/dip/stores/data-file/dip-data-file.state"; +import {DipDataFileStatusHistoryState} from "@app/features/preservation/dip/stores/data-file/status-history/dip-data-file-status-history.state"; +import {DipState} from "@app/features/preservation/dip/stores/dip.state"; +import {DipStatusHistoryState} from "@app/features/preservation/dip/stores/status-history/dip-status-history.state"; +import {SharedModule} from "@app/shared/shared.module"; +import {TranslateModule} from "@ngx-translate/core"; +import {NgxsModule} from "@ngxs/store"; +import {DipRoutingModule} from "@preservation/dip/dip-routing.module"; +import {DipDetailEditRoutable} from "./components/routables/dip-detail-edit/dip-detail-edit.routable"; + +const routables = [ + DipListRoutable, + DipDetailEditRoutable, + DipFileRoutable, + DipMetadataRoutable, +]; +const containers = []; +const dialogs = []; +const presentationals = [ + DipFormPresentational, +]; + +@NgModule({ + declarations: [ + ...routables, + ...containers, + ...dialogs, + ...presentationals, + ], + imports: [ + SharedModule, + DipRoutingModule, + TranslateModule.forChild({}), + NgxsModule.forFeature([ + DipState, + DipDataFileState, + DipStatusHistoryState, + DipDataFileStatusHistoryState, + ]), + ], + entryComponents: [ + ...dialogs, + ], + exports: [ + ...routables, + ], + providers: [], +}) +export class DipModule { +} diff --git a/src/app/features/preservation/dip/enums/dip-tab.enum.ts b/src/app/features/preservation/dip/enums/dip-tab.enum.ts new file mode 100644 index 000000000..928996421 --- /dev/null +++ b/src/app/features/preservation/dip/enums/dip-tab.enum.ts @@ -0,0 +1,4 @@ +export enum DipTabEnum { + metadata = 0, + file = 1, +} diff --git a/src/app/features/preservation/dip/helpers/dip.helper.ts b/src/app/features/preservation/dip/helpers/dip.helper.ts new file mode 100644 index 000000000..9442b3c38 --- /dev/null +++ b/src/app/features/preservation/dip/helpers/dip.helper.ts @@ -0,0 +1,34 @@ +import {ActivatedRoute} from "@angular/router"; +import {DipTabEnum} from "@preservation/dip/enums/dip-tab.enum"; +import {PreservationPlanningRoutesEnum} from "@shared/enums/routes.enum"; + +export class DipHelper { + static getTabRouteSelected(route: ActivatedRoute): PreservationPlanningRoutesEnum | undefined { + const children = route.snapshot.children; + if (children.length > 0 && children[0].url.length > 0) { + const mode = children[0].url[0].path; + return mode as PreservationPlanningRoutesEnum; + } + return undefined; + } + + static getTabSelectedIndexWithRoute(tabRouteSelected: PreservationPlanningRoutesEnum): DipTabEnum | undefined { + if (tabRouteSelected === PreservationPlanningRoutesEnum.dipMetadata) { + return DipTabEnum.metadata; + } + if (tabRouteSelected === PreservationPlanningRoutesEnum.dipFiles) { + return DipTabEnum.file; + } + return undefined; + } + + static getTabRouteSelectedWithTabIndex(tabIndexSelected: DipTabEnum): PreservationPlanningRoutesEnum | undefined { + if (tabIndexSelected === DipTabEnum.metadata) { + return PreservationPlanningRoutesEnum.dipMetadata; + } + if (tabIndexSelected === DipTabEnum.file) { + return PreservationPlanningRoutesEnum.dipFiles; + } + return undefined; + } +} diff --git a/src/app/features/preservation/dip/models/dip-data-file.model.ts b/src/app/features/preservation/dip/models/dip-data-file.model.ts new file mode 100644 index 000000000..85250dbc3 --- /dev/null +++ b/src/app/features/preservation/dip/models/dip-data-file.model.ts @@ -0,0 +1,4 @@ +import {DataFile} from "@shared/models/business/data-file.model"; + +export interface DipDataFile extends DataFile { +} diff --git a/src/app/features/preservation/dip/models/dip-extended.model.ts b/src/app/features/preservation/dip/models/dip-extended.model.ts new file mode 100644 index 000000000..baa0c2eaa --- /dev/null +++ b/src/app/features/preservation/dip/models/dip-extended.model.ts @@ -0,0 +1,4 @@ +import {Dip} from "@app/generated-api"; + +export interface DipExtended extends Dip { +} diff --git a/src/app/features/preservation/dip/stores/data-file/dip-data-file.action.ts b/src/app/features/preservation/dip/stores/data-file/dip-data-file.action.ts new file mode 100644 index 000000000..bd41229f4 --- /dev/null +++ b/src/app/features/preservation/dip/stores/data-file/dip-data-file.action.ts @@ -0,0 +1,104 @@ +import {DipDataFile} from "@preservation/dip/models/dip-data-file.model"; +import {LocalStateEnum} from "@shared/enums/local-state.enum"; +import { + BaseAction, + CompositionAction, + CompositionNameSpace, + QueryParameters, + TypeDefaultAction, +} from "solidify-frontend"; + +const state = LocalStateEnum.preservation_dip_dataFile; + +export namespace PreservationDipDataFileAction { + @TypeDefaultAction(state) + export class GetAll extends CompositionAction.GetAll { + } + + @TypeDefaultAction(state) + export class GetAllSuccess extends CompositionAction.GetAllSuccess<DipDataFile> { + } + + @TypeDefaultAction(state) + export class GetAllFail extends CompositionAction.GetAllFail { + } + + @TypeDefaultAction(state) + export class GetById extends CompositionAction.GetById { + } + + @TypeDefaultAction(state) + export class GetByIdSuccess extends CompositionAction.GetByIdSuccess<DipDataFile> { + } + + @TypeDefaultAction(state) + export class GetByIdFail extends CompositionAction.GetByIdFail { + } + + @TypeDefaultAction(state) + export class Update extends CompositionAction.Update<DipDataFile> { + } + + @TypeDefaultAction(state) + export class UpdateSuccess extends CompositionAction.UpdateSuccess<DipDataFile> { + } + + @TypeDefaultAction(state) + export class UpdateFail extends CompositionAction.UpdateFail<DipDataFile> { + } + + @TypeDefaultAction(state) + export class Create extends CompositionAction.Create<DipDataFile> { + } + + @TypeDefaultAction(state) + export class CreateSuccess extends CompositionAction.CreateSuccess<DipDataFile> { + } + + @TypeDefaultAction(state) + export class CreateFail extends CompositionAction.CreateFail<DipDataFile> { + } + + @TypeDefaultAction(state) + export class Delete extends CompositionAction.Delete<DipDataFile> { + } + + @TypeDefaultAction(state) + export class DeleteSuccess extends CompositionAction.DeleteSuccess<DipDataFile> { + } + + @TypeDefaultAction(state) + export class DeleteFail extends CompositionAction.DeleteFail<DipDataFile> { + } + + export class ChangeQueryParameters extends BaseAction { + static readonly type: string = `[${state}] Change Query Parameters`; + + constructor(public parentId: string, public queryParameters: QueryParameters, public keepCurrentContext: boolean = false) { + super(); + } + } + + export class Refresh { + static readonly type: string = `[${state}] Refresh`; + + constructor(public parentId: string) { + } + } + + export class Download { + static readonly type: string = `[${state}] Download`; + + constructor(public parentId: string, public dataFile: DipDataFile) { + } + } + + export class Resume { + static readonly type: string = `[${state}] Resume`; + + constructor(public parentId: string, public dataFile: DipDataFile) { + } + } +} + +export const preservationDipDataFileActionNameSpace: CompositionNameSpace = PreservationDipDataFileAction; diff --git a/src/app/features/preservation/dip/stores/data-file/dip-data-file.state.ts b/src/app/features/preservation/dip/stores/data-file/dip-data-file.state.ts new file mode 100644 index 000000000..3b44107b1 --- /dev/null +++ b/src/app/features/preservation/dip/stores/data-file/dip-data-file.state.ts @@ -0,0 +1,116 @@ +import {Inject} from "@angular/core"; +import {WINDOW} from "@app/app.module"; +import { + PreservationDipDataFileAction, + preservationDipDataFileActionNameSpace, +} from "@app/features/preservation/dip/stores/data-file/dip-data-file.action"; +import {ApiActionEnum} from "@app/shared/enums/api-action.enum"; +import {ApiResourceNameEnum} from "@app/shared/enums/api-resource-name.enum"; +import {AccessResourceApiEnum} from "@app/shared/enums/api.enum"; +import {LocalStateEnum} from "@app/shared/enums/local-state.enum"; +import { + Action, + Actions, + Selector, + State, + StateContext, + Store, +} from "@ngxs/store"; +import {DipDataFile} from "@preservation/dip/models/dip-data-file.model"; +import { + DipDataFileStatusHistoryState, + DipDataFileStatusHistoryStateModel, +} from "@preservation/dip/stores/data-file/status-history/dip-data-file-status-history.state"; +import {DownloadService} from "@shared/services/download.service"; +import {defaultStatusHistoryInitValue} from "@shared/stores/status-history/status-history.state"; +import {Observable} from "rxjs"; +import {tap} from "rxjs/internal/operators/tap"; +import { + ApiService, + CompositionState, + CompositionStateModel, + defaultCompositionStateInitValue, + NotificationService, + TRANSLATE, +} from "solidify-frontend"; + +export const defaultDipDataFileValue: () => DipDataFileStateModel = () => + ({ + ...defaultCompositionStateInitValue(), + preservation_dip_dataFile_statusHistory: defaultStatusHistoryInitValue(), + listFolders: [], + numberFilesInErrors: undefined, + numberFiles: undefined, + }); + +export interface DipDataFileStateModel extends CompositionStateModel<DipDataFile> { + preservation_dip_dataFile_statusHistory: DipDataFileStatusHistoryStateModel; + listFolders: string[]; + numberFilesInErrors: number | undefined; + numberFiles: number | undefined; +} + +@State<DipDataFileStateModel>({ + name: LocalStateEnum.preservation_dip_dataFile, + defaults: { + ...defaultDipDataFileValue(), + }, + children: [ + DipDataFileStatusHistoryState, + ], +}) +export class DipDataFileState extends CompositionState<DipDataFileStateModel, DipDataFile> { + constructor(protected apiService: ApiService, + protected store: Store, + protected notificationService: NotificationService, + protected actions$: Actions, + @Inject(WINDOW) private _window: Window, + private downloadService: DownloadService) { + super(apiService, store, notificationService, actions$, { + nameSpace: preservationDipDataFileActionNameSpace, + resourceName: ApiResourceNameEnum.DATAFILE, + }); + } + + protected get _urlResource(): string { + return AccessResourceApiEnum.dip; + } + + @Selector() + static listFolders(state: DipDataFileStateModel): string[] { + return state.listFolders; + } + + @Action(PreservationDipDataFileAction.Refresh) + refresh(ctx: StateContext<DipDataFileStateModel>, action: PreservationDipDataFileAction.Refresh): void { + ctx.dispatch(new PreservationDipDataFileAction.GetAll(action.parentId, undefined, true)); + } + + @Action(PreservationDipDataFileAction.Download) + download(ctx: StateContext<DipDataFileStateModel>, action: PreservationDipDataFileAction.Download): void { + const url = `${this._urlResource}/${action.parentId}/${this._resourceName}/${action.dataFile.resId}/${ApiActionEnum.DL}`; + this.notificationService.showInformation(TRANSLATE("notification.deposit.file.download"), true); + this.downloadService.download(url, action.dataFile.fileName, action.dataFile.fileSize).subscribe(() => { + this.notificationService.showSuccess(TRANSLATE("notification.deposit.file.downloadWithSuccess"), true); + }); + } + + @Action(PreservationDipDataFileAction.ChangeQueryParameters) + changeQueryParameters(ctx: StateContext<DipDataFileStateModel>, action: PreservationDipDataFileAction.ChangeQueryParameters): void { + ctx.patchState({ + queryParameters: action.queryParameters, + }); + ctx.dispatch(new PreservationDipDataFileAction.GetAll(action.parentId, undefined, action.keepCurrentContext)); + } + + @Action(PreservationDipDataFileAction.Resume) + resume(ctx: StateContext<DipDataFileStateModel>, action: PreservationDipDataFileAction.Resume): Observable<void> { + return this.apiService.post<void>(`${this._urlResource}/${action.parentId}/${this._resourceName}/${action.dataFile.resId}/${ApiActionEnum.RESUME}`) + .pipe( + tap(() => { + this.notificationService.showInformation(TRANSLATE("notification.deposit.file.resumed"), true); + ctx.dispatch(new PreservationDipDataFileAction.GetAll(action.parentId, undefined, true)); + }), + ); + } +} diff --git a/src/app/features/preservation/dip/stores/data-file/status-history/dip-data-file-status-history.action.ts b/src/app/features/preservation/dip/stores/data-file/status-history/dip-data-file-status-history.action.ts new file mode 100644 index 000000000..d3a3bd276 --- /dev/null +++ b/src/app/features/preservation/dip/stores/data-file/status-history/dip-data-file-status-history.action.ts @@ -0,0 +1,27 @@ +import {LocalStateEnum} from "@app/shared/enums/local-state.enum"; +import {StatusHistoryNamespace} from "@shared/stores/status-history/status-history-namespace.model"; +import {StatusHistoryAction} from "@shared/stores/status-history/status-history.action"; +import {TypeDefaultAction} from "solidify-frontend"; + +const state = LocalStateEnum.preservation_dip_dataFile_statusHistory; + +export namespace PreservationDipDataFileStatusHistoryAction { + + @TypeDefaultAction(state) + export class History extends StatusHistoryAction.History { + } + + @TypeDefaultAction(state) + export class HistorySuccess extends StatusHistoryAction.HistorySuccess { + } + + @TypeDefaultAction(state) + export class HistoryFail extends StatusHistoryAction.HistoryFail { + } + + @TypeDefaultAction(state) + export class ChangeQueryParameters extends StatusHistoryAction.ChangeQueryParameters { + } +} + +export const preservationDipDataFileStatusHistoryNamespace: StatusHistoryNamespace = PreservationDipDataFileStatusHistoryAction; diff --git a/src/app/features/preservation/dip/stores/data-file/status-history/dip-data-file-status-history.state.ts b/src/app/features/preservation/dip/stores/data-file/status-history/dip-data-file-status-history.state.ts new file mode 100644 index 000000000..0d7471020 --- /dev/null +++ b/src/app/features/preservation/dip/stores/data-file/status-history/dip-data-file-status-history.state.ts @@ -0,0 +1,44 @@ +import {preservationDipDataFileStatusHistoryNamespace} from "@app/features/preservation/dip/stores/data-file/status-history/dip-data-file-status-history.action"; +import {AccessResourceApiEnum} from "@app/shared/enums/api.enum"; +import {LocalStateEnum} from "@app/shared/enums/local-state.enum"; + +import { + Actions, + State, + Store, +} from "@ngxs/store"; +import {DipDataFile} from "@preservation/dip/models/dip-data-file.model"; +import { + defaultStatusHistoryInitValue, + StatusHistoryState, + StatusHistoryStateModel, +} from "@shared/stores/status-history/status-history.state"; +import { + ApiService, + NotificationService, +} from "solidify-frontend"; + +export interface DipDataFileStatusHistoryStateModel extends StatusHistoryStateModel<DipDataFile> { +} + +@State<DipDataFileStatusHistoryStateModel>({ + name: LocalStateEnum.preservation_dip_dataFile_statusHistory, + defaults: { + ...defaultStatusHistoryInitValue(), + }, +}) +export class DipDataFileStatusHistoryState extends StatusHistoryState<DipDataFileStatusHistoryStateModel, DipDataFile> { + + constructor(protected apiService: ApiService, + protected store: Store, + protected notificationService: NotificationService, + protected actions$: Actions) { + super(apiService, store, notificationService, actions$, { + nameSpace: preservationDipDataFileStatusHistoryNamespace, + }); + } + + protected get _urlResource(): string { + return AccessResourceApiEnum.dipDataFile; + } +} diff --git a/src/app/features/preservation/dip/stores/dip.action.ts b/src/app/features/preservation/dip/stores/dip.action.ts new file mode 100644 index 000000000..208686e16 --- /dev/null +++ b/src/app/features/preservation/dip/stores/dip.action.ts @@ -0,0 +1,130 @@ +import {Dip} from "@app/generated-api"; +import {LocalStateEnum} from "@app/shared/enums/local-state.enum"; +import { + BaseAction, + BaseSubAction, + ResourceAction, + ResourceNameSpace, + TypeDefaultAction, +} from "solidify-frontend"; + +const state = LocalStateEnum.preservation_dip; + +export namespace PreservationDipAction { + @TypeDefaultAction(state) + export class LoadResource extends ResourceAction.LoadResource { + } + + @TypeDefaultAction(state) + export class LoadResourceSuccess extends ResourceAction.LoadResourceSuccess { + } + + @TypeDefaultAction(state) + export class LoadResourceFail extends ResourceAction.LoadResourceFail { + } + + @TypeDefaultAction(state) + export class ChangeQueryParameters extends ResourceAction.ChangeQueryParameters { + } + + @TypeDefaultAction(state) + export class GetAll extends ResourceAction.GetAll { + } + + @TypeDefaultAction(state) + export class GetAllSuccess extends ResourceAction.GetAllSuccess<Dip> { + } + + @TypeDefaultAction(state) + export class GetAllFail extends ResourceAction.GetAllFail<Dip> { + } + + @TypeDefaultAction(state) + export class GetByListId extends ResourceAction.GetByListId { + } + + @TypeDefaultAction(state) + export class GetByListIdSuccess extends ResourceAction.GetByListIdSuccess { + } + + @TypeDefaultAction(state) + export class GetByListIdFail extends ResourceAction.GetByListIdFail { + } + + @TypeDefaultAction(state) + export class GetById extends ResourceAction.GetById { + } + + @TypeDefaultAction(state) + export class GetByIdSuccess extends ResourceAction.GetByIdSuccess<Dip> { + } + + @TypeDefaultAction(state) + export class GetByIdFail extends ResourceAction.GetByIdFail<Dip> { + } + + @TypeDefaultAction(state) + export class Create extends ResourceAction.Create<Dip> { + } + + @TypeDefaultAction(state) + export class CreateSuccess extends ResourceAction.CreateSuccess<Dip> { + } + + @TypeDefaultAction(state) + export class CreateFail extends ResourceAction.CreateFail<Dip> { + } + + @TypeDefaultAction(state) + export class Update extends ResourceAction.Update<Dip> { + } + + @TypeDefaultAction(state) + export class UpdateSuccess extends ResourceAction.UpdateSuccess<Dip> { + } + + @TypeDefaultAction(state) + export class UpdateFail extends ResourceAction.UpdateFail<Dip> { + } + + @TypeDefaultAction(state) + export class Delete extends ResourceAction.Delete { + } + + @TypeDefaultAction(state) + export class DeleteSuccess extends ResourceAction.DeleteSuccess { + } + + @TypeDefaultAction(state) + export class DeleteFail extends ResourceAction.DeleteFail { + } + + @TypeDefaultAction(state) + export class Clean extends ResourceAction.Clean { + } + + export class Download { + static readonly type: string = `[${state}] Download`; + + constructor(public id: string) { + } + } + + export class Resume extends BaseAction { + static readonly type: string = `[${state}] Resume`; + + constructor(public id: string) { + super(); + } + } + + export class ResumeSuccess extends BaseSubAction<Resume> { + static readonly type: string = `[${state}] Resume Success`; + } + + export class ResumeFail extends BaseSubAction<Resume> { + static readonly type: string = `[${state}] Resume Fail`; + } +} + +export const preservationDipActionNameSpace: ResourceNameSpace = PreservationDipAction; diff --git a/src/app/features/preservation/dip/stores/dip.state.ts b/src/app/features/preservation/dip/stores/dip.state.ts new file mode 100644 index 000000000..dee209939 --- /dev/null +++ b/src/app/features/preservation/dip/stores/dip.state.ts @@ -0,0 +1,167 @@ +import {HttpClient} from "@angular/common/http"; +import { + defaultDipDataFileValue, + DipDataFileState, + DipDataFileStateModel, +} from "@app/features/preservation/dip/stores/data-file/dip-data-file.state"; +import { + PreservationDipAction, + preservationDipActionNameSpace, +} from "@app/features/preservation/dip/stores/dip.action"; +import { + DipStatusHistoryState, + DipStatusHistoryStateModel, +} from "@app/features/preservation/dip/stores/status-history/dip-status-history.state"; +import {Dip} from "@app/generated-api"; +import {AccessResourceApiEnum} from "@app/shared/enums/api.enum"; +import {LocalStateEnum} from "@app/shared/enums/local-state.enum"; +import {Navigate} from "@ngxs/router-plugin"; +import { + Action, + Actions, + Selector, + State, + StateContext, + Store, +} from "@ngxs/store"; +import {ApiActionEnum} from "@shared/enums/api-action.enum"; +import { + DepositRoutesEnum, + RoutesEnum, +} from "@shared/enums/routes.enum"; +import {DownloadService} from "@shared/services/download.service"; +import {defaultStatusHistoryInitValue} from "@shared/stores/status-history/status-history.state"; +import {Observable} from "rxjs"; +import { + catchError, + tap, +} from "rxjs/operators"; +import { + ApiService, + defaultResourceStateInitValue, + isNullOrUndefined, + NotificationService, + OverrideDefaultAction, + ResourceState, + ResourceStateModel, + StoreUtil, + TRANSLATE, +} from "solidify-frontend"; + +export interface DipStateModel extends ResourceStateModel<Dip> { + preservation_dip_dataFile: DipDataFileStateModel; + isLoadingDataFile: boolean; + preservation_dip_statusHistory: DipStatusHistoryStateModel; +} + +@State<DipStateModel>({ + name: LocalStateEnum.preservation_dip, + defaults: { + ...defaultResourceStateInitValue(), + preservation_dip_dataFile: defaultDipDataFileValue(), + isLoadingDataFile: false, + preservation_dip_statusHistory: {...defaultStatusHistoryInitValue()}, + }, + children: [ + DipDataFileState, + DipStatusHistoryState, + ], +}) +export class DipState extends ResourceState<DipStateModel, Dip> { + constructor(protected apiService: ApiService, + protected store: Store, + protected notificationService: NotificationService, + protected actions$: Actions, + protected httpClient: HttpClient, + private downloadService: DownloadService) { + super(apiService, store, notificationService, actions$, { + nameSpace: preservationDipActionNameSpace, + notificationResourceUpdateFailTextToTranslate: TRANSLATE("dip.notification.resource.update.fail"), + }); + } + + protected get _urlResource(): string { + return AccessResourceApiEnum.dip; + } + + @Selector() + static isLoading(state: DipStateModel): boolean { + return StoreUtil.isLoadingState(state); + } + + @Selector() + static currentTitle(state: DipStateModel): string | undefined { + if (isNullOrUndefined(state.current)) { + return undefined; + } + return state.current.info.name; + } + + @Selector() + static isLoadingWithDependency(state: DipStateModel): boolean { + return this.isLoading(state); + } + + @Selector() + static isReadyToBeDisplayed(state: DipStateModel): boolean { + return this.isReadyToBeDisplayedInCreateMode + && !isNullOrUndefined(state.current); + } + + @Selector() + static isReadyToBeDisplayedInCreateMode(state: DipStateModel): boolean { + return true; + } + + @OverrideDefaultAction() + @Action(PreservationDipAction.UpdateSuccess) + updateSuccess(ctx: StateContext<DipStateModel>, action: PreservationDipAction.UpdateSuccess): void { + super.updateSuccess(ctx, action); + + ctx.dispatch([ + new Navigate([RoutesEnum.preservationDipDetail, action.model.resId, DepositRoutesEnum.files]), + ]); + } + + @Action(PreservationDipAction.Download) + download(ctx: StateContext<DipStateModel>, action: PreservationDipAction.Download): void { + const fileName = "dip_" + action.id + ".zip"; + this.notificationService.showInformation(TRANSLATE("notification.deposit.file.download"), true); + this.downloadService.download(`${this._urlResource}/${action.id}/${ApiActionEnum.DL}`, fileName).subscribe(() => { + this.notificationService.showInformation(TRANSLATE("notification.deposit.file.downloadWithSuccess"), true); + }); + } + + @Action(PreservationDipAction.Resume) + resume(ctx: StateContext<DipStateModel>, action: PreservationDipAction.Resume): Observable<string> { + ctx.patchState({ + isLoadingCounter: ctx.getState().isLoadingCounter + 1, + }); + return this.httpClient.post<string>(`${this._urlResource}/${action.id}/${ApiActionEnum.RESUME}`, null) + .pipe( + tap(result => { + ctx.dispatch(new PreservationDipAction.ResumeSuccess(action)); + }), + catchError(error => { + ctx.dispatch(new PreservationDipAction.ResumeFail(action)); + throw error; + }), + ); + } + + @Action(PreservationDipAction.ResumeSuccess) + resumeSuccess(ctx: StateContext<DipStateModel>, action: PreservationDipAction.ResumeSuccess): void { + ctx.patchState({ + isLoadingCounter: ctx.getState().isLoadingCounter - 1, + }); + this.notificationService.showInformation(TRANSLATE("notification.dip.action.resume.success"), true); + } + + @Action(PreservationDipAction.ResumeFail) + resumeFail(ctx: StateContext<DipStateModel>, action: PreservationDipAction.ResumeFail): void { + ctx.patchState({ + isLoadingCounter: ctx.getState().isLoadingCounter - 1, + }); + this.notificationService.showError(TRANSLATE("notification.dip.action.resume.fail"), true); + } +} diff --git a/src/app/features/preservation/dip/stores/status-history/dip-status-history.action.ts b/src/app/features/preservation/dip/stores/status-history/dip-status-history.action.ts new file mode 100644 index 000000000..49ed8e7ed --- /dev/null +++ b/src/app/features/preservation/dip/stores/status-history/dip-status-history.action.ts @@ -0,0 +1,27 @@ +import {LocalStateEnum} from "@app/shared/enums/local-state.enum"; +import {StatusHistoryNamespace} from "@shared/stores/status-history/status-history-namespace.model"; +import {StatusHistoryAction} from "@shared/stores/status-history/status-history.action"; +import {TypeDefaultAction} from "solidify-frontend"; + +const state = LocalStateEnum.preservation_dip_statusHistory; + +export namespace PreservationDipStatusHistoryAction { + + @TypeDefaultAction(state) + export class History extends StatusHistoryAction.History { + } + + @TypeDefaultAction(state) + export class HistorySuccess extends StatusHistoryAction.HistorySuccess { + } + + @TypeDefaultAction(state) + export class HistoryFail extends StatusHistoryAction.HistoryFail { + } + + @TypeDefaultAction(state) + export class ChangeQueryParameters extends StatusHistoryAction.ChangeQueryParameters { + } +} + +export const preservationDipStatusHistoryNamespace: StatusHistoryNamespace = PreservationDipStatusHistoryAction; diff --git a/src/app/features/preservation/dip/stores/status-history/dip-status-history.state.ts b/src/app/features/preservation/dip/stores/status-history/dip-status-history.state.ts new file mode 100644 index 000000000..154ac38ca --- /dev/null +++ b/src/app/features/preservation/dip/stores/status-history/dip-status-history.state.ts @@ -0,0 +1,44 @@ +import {preservationDipStatusHistoryNamespace} from "@app/features/preservation/dip/stores/status-history/dip-status-history.action"; +import {Dip} from "@app/generated-api"; +import {AccessResourceApiEnum} from "@app/shared/enums/api.enum"; +import {LocalStateEnum} from "@app/shared/enums/local-state.enum"; + +import { + Actions, + State, + Store, +} from "@ngxs/store"; +import { + defaultStatusHistoryInitValue, + StatusHistoryState, + StatusHistoryStateModel, +} from "@shared/stores/status-history/status-history.state"; +import { + ApiService, + NotificationService, +} from "solidify-frontend"; + +export interface DipStatusHistoryStateModel extends StatusHistoryStateModel<Dip> { +} + +@State<DipStatusHistoryStateModel>({ + name: LocalStateEnum.preservation_dip_statusHistory, + defaults: { + ...defaultStatusHistoryInitValue(), + }, +}) +export class DipStatusHistoryState extends StatusHistoryState<DipStatusHistoryStateModel, Dip> { + + constructor(protected apiService: ApiService, + protected store: Store, + protected notificationService: NotificationService, + protected actions$: Actions) { + super(apiService, store, notificationService, actions$, { + nameSpace: preservationDipStatusHistoryNamespace, + }); + } + + protected get _urlResource(): string { + return AccessResourceApiEnum.dip; + } +} diff --git a/src/app/features/preservation/preservation-routing.module.ts b/src/app/features/preservation/preservation-routing.module.ts index a86b9ee60..7015a56c4 100644 --- a/src/app/features/preservation/preservation-routing.module.ts +++ b/src/app/features/preservation/preservation-routing.module.ts @@ -80,6 +80,15 @@ const routes: Routes = [ }, canActivate: [ApplicationRoleGuardService], }, + { + path: PreservationPlanningRoutesEnum.dip, + // @ts-ignore Dynamic import + loadChildren: () => import("./dip/dip.module").then(m => m.DipModule), + data: { + breadcrumb: TRANSLATE("breadcrumb.preservation.dip.root"), + }, + canActivate: [ApplicationRoleGuardService], + }, ]; @NgModule({ diff --git a/src/app/features/preservation/preservation.module.ts b/src/app/features/preservation/preservation.module.ts index 0be704b2a..09ec7c831 100644 --- a/src/app/features/preservation/preservation.module.ts +++ b/src/app/features/preservation/preservation.module.ts @@ -14,6 +14,10 @@ import {NgxsModule} from "@ngxs/store"; import {PreservationAipAipState} from "@preservation/aip/stores/aip-aip/aip-aip.state"; import {PreservationAipAipStatusHistoryState} from "@preservation/aip/stores/aip-aip/status-history/aip-aip-status-history.state"; import {PreservationAipStatusHistoryState} from "@preservation/aip/stores/status-history/aip-status-history.state"; +import {DipDataFileState} from "@preservation/dip/stores/data-file/dip-data-file.state"; +import {DipDataFileStatusHistoryState} from "@preservation/dip/stores/data-file/status-history/dip-data-file-status-history.state"; +import {DipState} from "@preservation/dip/stores/dip.state"; +import {DipStatusHistoryState} from "@preservation/dip/stores/status-history/dip-status-history.state"; import {SipDataFileState} from "@preservation/sip/stores/data-file/sip-data-file.state"; import {SipDataFileStatusHistoryState} from "@preservation/sip/stores/data-file/status-history/sip-data-file-status-history.state"; import {SipState} from "@preservation/sip/stores/sip.state"; @@ -47,11 +51,15 @@ const presentationals = []; SipDataFileState, SipStatusHistoryState, SipDataFileStatusHistoryState, + DipState, + DipDataFileState, + DipStatusHistoryState, + DipDataFileStatusHistoryState, PreservationAipState, PreservationAipOrganizationalUnitState, PreservationAipAipState, PreservationAipStatusHistoryState, - PreservationAipAipStatusHistoryState + PreservationAipAipStatusHistoryState, ]), ], entryComponents: [ diff --git a/src/app/features/preservation/stores/preservation.state.ts b/src/app/features/preservation/stores/preservation.state.ts index 361f1a69f..c1fba9111 100644 --- a/src/app/features/preservation/stores/preservation.state.ts +++ b/src/app/features/preservation/stores/preservation.state.ts @@ -16,6 +16,10 @@ import { State, Store, } from "@ngxs/store"; +import { + DipState, + DipStateModel, +} from "@preservation/dip/stores/dip.state"; import { SipState, SipStateModel, @@ -27,6 +31,7 @@ export interface PreservationStateModel extends BaseStateModel { preservation_job: PreservationJobStateModel | undefined; preservation_aipStatus: PreservationAipStatusStateModel | undefined; preservation_sip: SipStateModel | undefined; + preservation_dip: DipStateModel | undefined; } @State<PreservationStateModel>({ @@ -37,13 +42,15 @@ export interface PreservationStateModel extends BaseStateModel { preservation_job: undefined, preservation_aipStatus: undefined, preservation_sip: undefined, + preservation_dip: undefined, }, children: [ PreservationMonitoringState, PreservationJobState, PreservationAipStatusState, SipState, - PreservationAipState + DipState, + PreservationAipState, ], }) export class PreservationState { diff --git a/src/app/shared/enums/api.enum.ts b/src/app/shared/enums/api.enum.ts index 92dba977d..c0b311d59 100644 --- a/src/app/shared/enums/api.enum.ts +++ b/src/app/shared/enums/api.enum.ts @@ -73,6 +73,10 @@ export class AccessResourceApiEnum implements ResourceApiEnum { return BaseResourceApiEnum.access + SEPARATOR + ApiResourceNameEnum.DIP; } + static get dipDataFile(): string { + return BaseResourceApiEnum.ingest + SEPARATOR + ApiResourceNameEnum.DIP + parentIdInUrl + ApiResourceNameEnum.DATAFILE; + } + static get allMetadata(): string { return BaseResourceApiEnum.access + SEPARATOR + ApiResourceNameEnum.ALL_METADATA; } @@ -226,7 +230,7 @@ export class ArchivalStorageResourceApiEnum implements ResourceApiEnum { archivalStorageList.forEach(url => { newList.push({ index: url.index, - url: url.url + SEPARATOR + ApiResourceNameEnum.AIP + parentIdInUrl + ApiResourceNameEnum.DATAFILE + url: url.url + SEPARATOR + ApiResourceNameEnum.AIP + parentIdInUrl + ApiResourceNameEnum.DATAFILE, }); }); return newList; diff --git a/src/app/shared/enums/local-state.enum.ts b/src/app/shared/enums/local-state.enum.ts index 6633bd7ca..2053bf25b 100644 --- a/src/app/shared/enums/local-state.enum.ts +++ b/src/app/shared/enums/local-state.enum.ts @@ -80,4 +80,9 @@ export enum LocalStateEnum { preservation_sip_statusHistory = "preservation_sip_statusHistory", preservation_sip_dataFile = "preservation_sip_dataFile", preservation_sip_dataFile_statusHistory = "preservation_sip_dataFile_statusHistory", + + preservation_dip = "preservation_dip", + preservation_dip_statusHistory = "preservation_dip_statusHistory", + preservation_dip_dataFile = "preservation_dip_dataFile", + preservation_dip_dataFile_statusHistory = "preservation_dip_dataFile_statusHistory", } diff --git a/src/app/shared/enums/routes.enum.ts b/src/app/shared/enums/routes.enum.ts index cffc9efac..cae162e27 100644 --- a/src/app/shared/enums/routes.enum.ts +++ b/src/app/shared/enums/routes.enum.ts @@ -131,9 +131,10 @@ export enum PreservationPlanningRoutesEnum { storagionNumberWithoutPrefixParam = "storagion", dip = "dip", - dipCreate = "create", dipDetail = "detail", dipEdit = "edit", + dipMetadata = "metadata", + dipFiles = "files", sip = "sip", sipDetail = "detail", @@ -215,9 +216,7 @@ export class RoutesEnum implements RoutesEnum { static preservationSipDetail: string = AppRoutesEnum.preservation + urlSeparator + PreservationPlanningRoutesEnum.sip + urlSeparator + PreservationPlanningRoutesEnum.sipDetail; static preservationDip: string = AppRoutesEnum.preservation + urlSeparator + PreservationPlanningRoutesEnum.dip; - static preservationDipCreate: string = AppRoutesEnum.preservation + urlSeparator + PreservationPlanningRoutesEnum.dip + urlSeparator + PreservationPlanningRoutesEnum.dipCreate; static preservationDipDetail: string = AppRoutesEnum.preservation + urlSeparator + PreservationPlanningRoutesEnum.dip + urlSeparator + PreservationPlanningRoutesEnum.dipDetail; - static preservationDipEdit: string = AppRoutesEnum.preservation + urlSeparator + PreservationPlanningRoutesEnum.dip + urlSeparator + PreservationPlanningRoutesEnum.dipEdit; static preservationPreservationPlanning: string = AppRoutesEnum.preservation + urlSeparator + PreservationPlanningRoutesEnum.preservationPlanning; static preservationPreservationPlanningCreate: string = AppRoutesEnum.preservation + urlSeparator + PreservationPlanningRoutesEnum.preservationPlanning + urlSeparator + PreservationPlanningRoutesEnum.preservationPlanningCreate; diff --git a/src/app/shared/utils/store-route-local.util.ts b/src/app/shared/utils/store-route-local.util.ts index 08b7be1bc..31f4937db 100644 --- a/src/app/shared/utils/store-route-local.util.ts +++ b/src/app/shared/utils/store-route-local.util.ts @@ -60,6 +60,9 @@ export class StoreRouteLocalUtil { if (state === LocalStateEnum.preservation_sip) { return RoutesEnum.preservationSipDetail; } + if (state === LocalStateEnum.preservation_dip) { + return RoutesEnum.preservationDipDetail; + } throw Error(StringUtil.format(StoreRouteLocalUtil.messageNotFound, "Detail", state)); } @@ -158,6 +161,9 @@ export class StoreRouteLocalUtil { if (state === LocalStateEnum.preservation_sip) { return RoutesEnum.preservationSip; } + if (state === LocalStateEnum.preservation_dip) { + return RoutesEnum.preservationDip; + } throw Error(StringUtil.format(StoreRouteLocalUtil.messageNotFound, "Root", state)); } diff --git a/src/assets/i18n/de.json b/src/assets/i18n/de.json index b6f9b03ae..97e9e0825 100644 --- a/src/assets/i18n/de.json +++ b/src/assets/i18n/de.json @@ -62,8 +62,8 @@ "delete": { "cancel": "Cancel", "confirm": "Yes", - "message": "Are you sure you want to delete the dissemination policy '{{name}}' ?", - "title": "Confirm deletion" + "message": "Are you sure you want to delete the following dissemination policy: '{{name}}' ?", + "title": "Confirm delete action" } }, "form": { @@ -74,14 +74,14 @@ "type": "Type" }, "home": { - "subtitle": "Describe dissemination policy", + "subtitle": "Describes dissemination policies", "title": "Dissemination policy" }, "notification": { "resource": { - "create": "Dissemination policy successfully created", - "delete": "Dissemination policy successfully deleted", - "update": "Dissemination policy successfully updated" + "create": "Dissemination policy created", + "delete": "Dissemination policy deleted", + "update": "Dissemination policy updated" } }, "table": { @@ -91,7 +91,7 @@ }, "destinationServer": "Destination server", "lastUpdate": { - "when": "Modified" + "when": "Updated" }, "name": "Name", "subFolder": "Subfolder", @@ -108,8 +108,8 @@ "delete": { "cancel": "Cancel", "confirm": "Yes", - "message": "Are you sure you want to delete the funding agency '{{name}}'?", - "title": "Confirm deletion" + "message": "Are you sure you want to delete the following funding agency: '{{name}}' ?", + "title": "Confirm delete action" } }, "form": { @@ -121,14 +121,14 @@ "url": "Url" }, "home": { - "subtitle": "Describe funding agency", + "subtitle": "Describes funding agencies", "title": "Funding agency" }, "notification": { "resource": { - "create": "Funding agency created with success", - "delete": "Funding agency deleted with success", - "update": "Role updated with success" + "create": "Funding agency created", + "delete": "Funding agency deleted", + "update": "Funding agency updated" } }, "table": { @@ -150,8 +150,8 @@ "delete": { "cancel": "Cancel", "confirm": "Confirm", - "message": "Are you sure you want to delete this institution?", - "title": "Confirm deletion" + "message": "Are you sure you want to delete the following institution: '{{name}}' ?", + "title": "Confirm delete action" } }, "form": { @@ -161,14 +161,14 @@ "url": "url" }, "home": { - "subtitle": "Describe institution", + "subtitle": "Describes institutions", "title": "Institution" }, "notification": { "resource": { - "create": "Institution created with success", - "delete": "Institution deleted with success", - "update": "Institution updated with success" + "create": "Institution created", + "delete": "Institution deleted", + "update": "Institution updated" } }, "table": { @@ -177,7 +177,7 @@ "when": "Creation date" }, "lastUpdate": { - "when": "Last update" + "when": "Updated" }, "name": "name" } @@ -192,8 +192,8 @@ "delete": { "cancel": "Cancel", "confirm": "Confirm", - "message": "Are you sure you want to delete this license?", - "title": "Confirm Deletion" + "message": "Are you sure you want to delete the following license: '{{name}}' ?", + "title": "Confirm delete action" } }, "form": { @@ -212,14 +212,14 @@ "url": "URL" }, "home": { - "subtitle": "Describe license for a deposit", + "subtitle": "Describes licenses for a deposit", "title": "License" }, "notification": { "resource": { - "create": "License created with success", - "delete": "License deleted with success", - "update": "License updated with success" + "create": "License created", + "delete": "License deleted", + "update": "License updated" } }, "table": { @@ -245,8 +245,8 @@ "delete": { "cancel": "Cancel", "confirm": "Yes", - "message": "Are you sure you want to delete this OAI set?", - "title": "Confirm deletion" + "message": "Are you sure you want to delete the following OAI set: '{{name}}' ?", + "title": "Confirm delete action" } }, "form": { @@ -258,15 +258,15 @@ "submit": "Save" }, "home": { - "subtitle": "Describe OAI sets", + "subtitle": "Describes OAI sets", "title": "OAI Sets" }, "notification": { - "bulkCreateSuccess": "OAI sets created successfully", + "bulkCreateSuccess": "OAI sets created", "resource": { - "create": "OAI set created successfully", - "delete": "OAI set deleted successfully", - "update": "OAI set updated successfully" + "create": "OAI set created", + "delete": "OAI set deleted", + "update": "OAI set updated" } }, "table": { @@ -291,8 +291,8 @@ "delete": { "cancel": "Cancel", "confirm": "Yes", - "message": "Are you sure you want to delete the Client '{{name}}'?", - "title": "Confirm deletion" + "message": "Are you sure you want to delete the following client: '{{name}}' ?", + "title": "Confirm delete action" } }, "form": { @@ -311,14 +311,14 @@ }, "notification": { "resource": { - "create": "OAuth2 Client created with success", - "delete": "OAuth2 Client deleted with success", - "update": "OAuth2 Client updated with success" + "create": "OAuth2 Client created", + "delete": "OAuth2 Client deleted", + "update": "OAuth2 Client updated" } }, "table": { "header": { - "clientId": "ID Client", + "clientId": "Client ID", "name": "Name", "redirectUri": "URL Redirection" } @@ -329,8 +329,8 @@ "delete": { "cancel": "Cancel", "confirm": "Yes", - "message": "Are you sure you want to delete the organizational unit '{{name}}'?", - "title": "Confirm deletion" + "message": "Are you sure you want to delete the following organizational unit: '{{name}}' ?", + "title": "Confirm delete action" } }, "form": { @@ -351,15 +351,15 @@ "url": "Url" }, "home": { - "subtitle": "Describe organizational unit", + "subtitle": "Describes organizational units", "title": "OrganizationalUnit" }, "new": "Create new organizational unit", "notification": { "resource": { - "create": "Organizational Unit created with success", - "delete": "Organizational Unit deleted with success", - "update": "Organizational Unit updated with success" + "create": "Organizational Unit created", + "delete": "Organizational Unit deleted", + "update": "Organizational Unit updated" } }, "refresh": "Refresh", @@ -382,14 +382,14 @@ "delete": { "cancel": "Cancel", "confirm": "Yes", - "message": "Are you sure you want to delete this person ?", - "title": "Delete confirmation" + "message": "Are you sure you want to delete the following person: '{{name}}' ?", + "title": "Confirm delete action" } }, "form": { - "firstName": "FirstName", + "firstName": "First Name", "institution": "Institution", - "lastName": "LastName", + "lastName": "Last Name", "orcid": "ORCID", "submit": "Save" }, @@ -399,15 +399,15 @@ }, "notification": { "resource": { - "create": "Person created with success", - "delete": "Person deleted with success", - "update": "Person updated with success" + "create": "Person created", + "delete": "Person deleted", + "update": "Person updated" } }, "table": { "header": { - "firstName": "FirstName", - "lastName": "LastName", + "firstName": "First Name", + "lastName": "Last Name", "orcid": "ORCID" } } @@ -417,12 +417,12 @@ "notification": { "deleteCache": { "fail": "Unable to clear cache", - "success": "Cache cleared with success" + "success": "Cache cleared" } }, "table": { "header": { - "creationDate": "Creation date", + "creationDate": "Created", "name": "Name", "resId": "Id", "summary": "Status" @@ -439,8 +439,8 @@ "delete": { "cancel": "Cancel", "confirm": "Yes", - "message": "Are you sure you want to delete the preservation policy '{{name}}'?", - "title": "Confirm deletion" + "message": "Are you sure you want to delete the following preservation policy: '{{name}}' ?", + "title": "Confirm delete action" } }, "form": { @@ -450,14 +450,14 @@ "submit": "Save" }, "home": { - "subtitle": "Describe the preservation policy for a deposit", + "subtitle": "Describes the preservation policies for a deposit", "title": "Preservation Policy" }, "notification": { "resource": { - "create": "Preservation Policy created with success", - "delete": "Preservation Policy deleted with success", - "update": "Preservation Policy updated with success" + "create": "Preservation Policy created", + "delete": "Preservation Policy deleted", + "update": "Preservation Policy updated" } }, "table": { @@ -467,7 +467,7 @@ }, "dispositionApproval": "Disposition Approval", "lastUpdate": { - "when": "Last updated" + "when": "Updated" }, "name": "Name", "retention": "Retention" @@ -488,9 +488,9 @@ }, "notification": { "resource": { - "create": "Role created with success", - "delete": "Role deleted with success", - "update": "Role updated with success" + "create": "Role created", + "delete": "Role deleted", + "update": "Role updated" } }, "table": { @@ -508,8 +508,8 @@ "delete": { "cancel": "Cancel", "confirm": "Yes", - "message": "Are you sure you want to delete the submission policy '{{name}}'?", - "title": "Confirm deletion" + "message": "Are you sure you want to delete the following submission policy: '{{name}}' ?", + "title": "Confirm delete action" } }, "form": { @@ -519,14 +519,14 @@ "timeToKeep": "Time To Keep" }, "home": { - "subtitle": "Describe the submission policy for a deposit", + "subtitle": "Describes the submission policies for a deposit", "title": "Submission Policy" }, "notification": { "resource": { - "create": "Submission Policy created with success", - "delete": "Submission Policy deleted with success", - "update": "Submission Policy updated with success" + "create": "Submission Policy created", + "delete": "Submission Policy deleted", + "update": "Submission Policy updated" } }, "table": { @@ -535,7 +535,7 @@ "when": "Created" }, "lastUpdate": { - "when": "Last updated" + "when": "Updated" }, "name": "Name", "submissionApproval": "Submission Approval", @@ -552,23 +552,23 @@ "delete": { "cancel": "Cancel", "confirm": "Yes", - "message": "Are you sure you want to delete the User '{{name}}'?", - "title": "Confirm deletion" + "message": "Are you sure you want to delete the following user: '{{name}}' ?", + "title": "Confirm delete action" }, "editOwnRoles": { "cancel": "Cancel", "confirm": "Yes, I'm sure!", "looseAdminRole": "You will lose access to the administration features.", - "noMoreRight": "You have removed all your rights which will prevent you from accessing the application!", + "noMoreRight": "You have removed all of your permissions. This will prevent you from accessing the application!", "summary": { "addedRoles": "Added roles :", "removedRoles": "Removed roles :" }, - "title": "Be careful, you will modify your roles!", - "warning": "This will result in a change of rights within the application.\nYou're going to be dislodged." + "title": "Be careful, you are modifying your own roles!", + "warning": "This action will result in a change of permissions within the application.\nYou will be logged out." } }, - "editCurrentUser": "Warning, you will edit your own informations !", + "editCurrentUser": "Warning, you may be editing your own information!", "form": { "accessToken": "Access Token", "email": "Email", @@ -583,14 +583,14 @@ "targetedUid": "targeted Uid" }, "home": { - "subtitle": "Describe the User", + "subtitle": "Describes Users", "title": "User" }, "notification": { "resource": { - "create": "User created with success", - "delete": "User deleted with success", - "update": "User updated with success" + "create": "User created", + "delete": "User deleted", + "update": "User updated" } }, "table": { @@ -620,10 +620,10 @@ }, "licence": "Licence", "name": "Name", - "organizationUnit": "Organization unit", + "organizationUnit": "Organizational unit", "resId": "ResId", "size": "Size", - "status": "Statut", + "status": "Status", "yearOfcreation": "Year" }, "table": { @@ -632,7 +632,7 @@ "when": "Created" }, "lastUpdate": { - "when": "Last updated" + "when": "Updated" }, "name": "Name", "organizationalUnit": "Organizational unit", @@ -681,14 +681,14 @@ "button": { "backToHome": "Back to home page" }, - "title": "The requested resource does not exist or no longer exist..." + "title": "The requested resource does not exist or no longer exists..." }, "notification": { "tokenCopiedToClipboardFail": "Unable to copy the token to the clipboard", "tokenCopiedToClipboardSuccess": "Token copied to the clipboard" }, "status": { - "backToOnline": "You are back to online", + "backToOnline": "You are back online", "newVersionAvailable": "A new version of the application is available", "offline": "Your are currently offline" }, @@ -718,14 +718,14 @@ "updateAfter": "Later", "updateNow": "Yes" }, - "title": "New version of the available application" + "title": "A new version of the application is available" }, "user": { "administrative": { "form": { "email": "Email", "firstName": "First name", - "homeOrganization": "Organization Unit", + "homeOrganization": "Organizational Unit", "lastName": "Last name", "roles": "Roles" }, @@ -754,7 +754,7 @@ "disseminationPolicy": { "create": "Create", "edit": "Edit", - "root": "Dissemination policy" + "root": "Dissemination policies" }, "funding-agencies": { "create": "Create", @@ -829,12 +829,18 @@ }, "preservation": { "aip-statuses": { - "root": "AIPs Statuses" + "root": "AIP Status" }, "aip": { - "root": "Storagion", + "file": "Files", + "metadata": "Metadata", + "root": "Storagion" + }, + "dip": { + "edit": "Edit", + "file": "Data files", "metadata": "Metadata", - "file": "Files" + "root": "DIP" }, "job": { "create": "Create", @@ -857,10 +863,10 @@ "action": { "delete": "Delete", "download": "Download", + "goToAip": "Go to Aip", "goToEdit": "Edit", "resume": "Resume", - "start": "Run", - "goToAip": "Go to Aip" + "start": "Run" } } }, @@ -872,7 +878,7 @@ "completed": "The submission of the deposit has been completed", "error": "The deposit is in error. Please submit it later", "processed": "Ongoing filing process", - "rejected": "The submission of the deposit was rejected" + "rejected": "This deposit has been rejected" } }, "approve": "Approve", @@ -887,7 +893,7 @@ "table": { "complianceLevel": "Compliance level", "creation": { - "when": "Sending date" + "when": "Uploaded" }, "fileName": "File name", "status": "Status" @@ -899,14 +905,14 @@ "delete": { "cancel": "Cancel", "confirm": "Yes", - "message": "Are you sure you want to delete the deposit '{{name}}'?", - "title": "Confirm deletion" + "message": "Are you sure you want to delete the following deposit: '{{name}}'?", + "title": "Confirm delete action" } }, "doi": "DOI", "edit": "Edit", "error": { - "fileInError": "The data file has an Error", + "fileInError": "One or more data files contain errors", "noFile": "You have to upload a file to submit the deposit" }, "file": { @@ -993,9 +999,9 @@ "notification": { "error": "There was a problem with the upload of the file", "info": { - "canceled": "The file upload has been cancelled with success" + "canceled": "The file upload has been cancelled" }, - "success": "File uploaded with success" + "success": "File uploaded" }, "retry": "Retry" } @@ -1039,12 +1045,12 @@ }, "reserveDOI": { "fail": "There was an error when reserving DOI of the deposit", - "success": "DOI reserved with success" + "success": "DOI reserved" }, "resource": { - "create": "Deposit created with success", - "delete": "Deposit deleted with success", - "update": "Deposit updated with success" + "create": "Deposit created", + "delete": "Deposit deleted", + "update": "Deposit updated" }, "submit": { "fail": "There was an error when submitting the deposit", @@ -1081,7 +1087,7 @@ "reject": "Reject", "reserveDOI": "Reserve DOI", "submissionPolicy": "Submission Policy", - "submit": "Save", + "submit": "Submit", "tab": { "datafiles": "Files", "details": "Metadata" @@ -1092,7 +1098,7 @@ "when": "Created" }, "lastUpdate": { - "when": "Last updated" + "when": "Updated" }, "publicationDate": "Publication date", "status": "Status", @@ -1124,21 +1130,30 @@ "submissionPolicy": "Shows the options that were defined during your preservation space's (i.e. organizational unit) creation. \n\nSee the documentation for more information" } }, + "dip": { + "notification": { + "resource": { + "update": { + "fail": "Unable to update the DIP" + } + } + } + }, "doi": { "copyLongDoi": "Copy long DOI", "copyShortDoi": "Copy short DOI", "copyUrlLongDoi": "Copy url with long DOI", "copyUrlShortLongDoi": "Copy url with short DOI", "notification": { - "longDoiCopyToClipboard": "Long DOI copy to clipboard", + "longDoiCopyToClipboard": "Long DOI copied to clipboard", "shortDoiCopyToClipboard": { "fail": "Unable to retrieve the short DOI", - "success": "Short DOI copy to clipboard" + "success": "Short DOI copied to clipboard" }, - "urlLongDoiCopyToClipboard": "Url with long DOI copy to clipboard", + "urlLongDoiCopyToClipboard": "Url with long DOI copied to clipboard", "urlShortDoiCopyToClipboard": { "fail": "Unable to retrieve the short DOI", - "success": "Url with short DOI copy to clipboard" + "success": "Url with short DOI copied to clipboard" } } }, @@ -1194,7 +1209,7 @@ "notification": { "error": "An error occurs during the preparation of package", "inPreparation": "Package in preparation. The download will start soon", - "success": "Package downloaded successfully" + "success": "Package downloaded" } } }, @@ -1247,11 +1262,11 @@ "action": { "deepChecksum": { "fail": "Impossible to do a deep checksum", - "success": "AIP's deep checksums will be checked" + "success": "The AIP's deep checksums will be checked" }, "reindex": { "fail": "Impossible to reindex the AIP", - "success": "TThe AIP will be reindexed" + "success": "The AIP will be reindexed" }, "resume": { "fail": "Impossible to restart AIP processing", @@ -1259,19 +1274,27 @@ }, "simpleChecksum": { "fail": "Impossible to do a simple checkum", - "success": "AIP's simple checksums will be checked" + "success": "The AIP's simple checksums will be checked" } }, "file": { "download": "Download in progress", - "downloadWithSuccess": "notification.aip.file.downloadWithSuccess" + "downloadWithSuccess": "AIP downloaded" } }, "deposit": { "file": { "download": "Download in progress", - "downloadWithSuccess": "notification.deposit.file.downloadWithSuccess", - "resumed": "Resource resumed successfully" + "downloadWithSuccess": "Deposit downloaded", + "resumed": "Resource resumed" + } + }, + "dip": { + "action": { + "resume": { + "fail": "Impossible to restart DIP processing", + "success": "DIP processing has been restarted" + } } }, "sip": { @@ -1299,16 +1322,16 @@ "new": "Create new organizational unit", "notification": { "resource": { - "create": "Organizational Unit created with success", - "delete": "Organizational Unit deleted with success", - "update": "Organizational Unit updated with success" + "create": "Organizational Unit created", + "delete": "Organizational Unit deleted", + "update": "Organizational Unit updated" } }, "openingDate": "Opening Date", "refresh": "Refresh", "security": { "notification": { - "noRightForThisAction": "You do not have the right to carry out this action" + "noRightForThisAction": "You do not have permission to carry out this action" } }, "submit": "Save", @@ -1324,6 +1347,12 @@ }, "preservation": { "aip": { + "aip": { + "aipOfAip": "Aips", + "table": { + "aipName": "Aip Name" + } + }, "back": "Back", "button": { "back": "Back", @@ -1336,12 +1365,6 @@ "tab": { "aip": "AIP", "details": "Metadata" - }, - "aip": { - "aipOfAip": "Aips", - "table": { - "aipName": "Aip Name" - } } }, "aipStats": { @@ -1364,9 +1387,42 @@ } }, "dip": { + "back": "Back", + "button": { + "download": "Download", + "resume": "Resume" + }, + "form": { + "access": "Access", + "aip": "AIP", + "complianceLevel": "Compliance level", + "description": "Description", + "name": "Name", + "organizationalUnitName": "Organizational unit", + "resId": "Identification", + "status": "Status", + "submit": "Save" + }, "home": { "subtitle": "Dissemination Information Package", "title": "DIP" + }, + "refresh": "Refresh", + "tab": { + "datafiles": "Files", + "details": "Metadata" + }, + "table": { + "header": { + "creation": { + "when": "Created" + }, + "lastUpdate": { + "when": "Updated" + }, + "status": "Status", + "title": "Title" + } } }, "home": { @@ -1383,8 +1439,8 @@ "delete": { "cancel": "Cancel", "confirm": "Yes", - "message": "Are you sure you want to delete this job?", - "title": "Confirm deletion" + "message": "Are you sure you want to delete the following job: '{{name}}' ?", + "title": "Confirm delete action" } }, "form": { @@ -1415,21 +1471,21 @@ }, "notification": { "init": { - "fail": "Unable to init jobs", - "success": "Jobs init with success" + "fail": "Unable to initiate jobs", + "success": "Jobs initiated" }, "resource": { - "create": "Job created successfully", - "delete": "Job deleted successfully", - "update": "Job updated successfully" + "create": "Job created", + "delete": "Job deleted", + "update": "Job updated" }, "resume": { "fail": "Unable to resume the job", - "success": "Job resumed successfully" + "success": "Job resumed" }, "start": { "fail": "Unable to start the job", - "success": "Job started successfully" + "success": "Job started" } }, "state": { @@ -1442,7 +1498,7 @@ "jobRecurrence": "Recurrence", "jobType": "Type", "name": "Name", - "when": "Last modification" + "when": "Updated" } } }, @@ -1463,12 +1519,12 @@ }, "notification": { "resume": { - "fail": "Unable to resume the job execution", - "success": "Job execution resumed with success" + "fail": "Unable to resume the job", + "success": "Job resumed" }, "start": { - "fail": "Unable to start the job execution", - "success": "Job execution started with success" + "fail": "Unable to start the job", + "success": "Job started" } }, "table": { @@ -1488,11 +1544,11 @@ "start": "Start" }, "ignoredItems": "Ignored items", - "inErrorItems": "In error items", + "inErrorItems": "Items in error", "processedItems": "Processed items", "runNumber": "Run number", "status": "Status", - "title": "Detail job execution", + "title": "Job execution detail", "totalItems": "Total items" }, "jobs": { @@ -1503,7 +1559,7 @@ }, "monitoring": { "home": { - "subtitle": "Allows to check the status of the solution", + "subtitle": "Shows the state of the platform's services", "title": "Monitoring" } }, @@ -1539,7 +1595,7 @@ "when": "Created" }, "lastUpdate": { - "when": "Last updated" + "when": "Updated" }, "organizationalUnit": "Organizational unit", "status": "Status", @@ -1557,9 +1613,9 @@ "person": { "notification": { "resource": { - "create": "Person created with success", - "delete": "Person deleted with success", - "update": "Person updated with success" + "create": "Person created", + "delete": "Person deleted", + "update": "Person updated" } } }, @@ -1613,7 +1669,7 @@ "welcome": { "rss": { "notification": { - "error": "There is an error while retrieving the RSS feed" + "error": "There was an error while retrieving the RSS feed" } } } diff --git a/src/assets/i18n/en.json b/src/assets/i18n/en.json index c763fef2a..97e9e0825 100644 --- a/src/assets/i18n/en.json +++ b/src/assets/i18n/en.json @@ -832,9 +832,15 @@ "root": "AIP Status" }, "aip": { - "root": "Storagion", + "file": "Files", "metadata": "Metadata", - "file": "Files" + "root": "Storagion" + }, + "dip": { + "edit": "Edit", + "file": "Data files", + "metadata": "Metadata", + "root": "DIP" }, "job": { "create": "Create", @@ -857,10 +863,10 @@ "action": { "delete": "Delete", "download": "Download", + "goToAip": "Go to Aip", "goToEdit": "Edit", "resume": "Resume", - "start": "Run", - "goToAip": "Go to Aip" + "start": "Run" } } }, @@ -1124,6 +1130,15 @@ "submissionPolicy": "Shows the options that were defined during your preservation space's (i.e. organizational unit) creation. \n\nSee the documentation for more information" } }, + "dip": { + "notification": { + "resource": { + "update": { + "fail": "Unable to update the DIP" + } + } + } + }, "doi": { "copyLongDoi": "Copy long DOI", "copyShortDoi": "Copy short DOI", @@ -1274,6 +1289,14 @@ "resumed": "Resource resumed" } }, + "dip": { + "action": { + "resume": { + "fail": "Impossible to restart DIP processing", + "success": "DIP processing has been restarted" + } + } + }, "sip": { "action": { "resume": { @@ -1324,6 +1347,12 @@ }, "preservation": { "aip": { + "aip": { + "aipOfAip": "Aips", + "table": { + "aipName": "Aip Name" + } + }, "back": "Back", "button": { "back": "Back", @@ -1336,12 +1365,6 @@ "tab": { "aip": "AIP", "details": "Metadata" - }, - "aip": { - "aipOfAip": "Aips", - "table": { - "aipName": "Aip Name" - } } }, "aipStats": { @@ -1364,9 +1387,42 @@ } }, "dip": { + "back": "Back", + "button": { + "download": "Download", + "resume": "Resume" + }, + "form": { + "access": "Access", + "aip": "AIP", + "complianceLevel": "Compliance level", + "description": "Description", + "name": "Name", + "organizationalUnitName": "Organizational unit", + "resId": "Identification", + "status": "Status", + "submit": "Save" + }, "home": { "subtitle": "Dissemination Information Package", "title": "DIP" + }, + "refresh": "Refresh", + "tab": { + "datafiles": "Files", + "details": "Metadata" + }, + "table": { + "header": { + "creation": { + "when": "Created" + }, + "lastUpdate": { + "when": "Updated" + }, + "status": "Status", + "title": "Title" + } } }, "home": { diff --git a/src/assets/i18n/fr.json b/src/assets/i18n/fr.json index ac0e5ffc1..367bbe125 100644 --- a/src/assets/i18n/fr.json +++ b/src/assets/i18n/fr.json @@ -832,9 +832,15 @@ "root": "Statuts des AIPs" }, "aip": { - "root": "Storagion", "file": "Fichiers", - "metadata": "Métadonnées" + "metadata": "Métadonnées", + "root": "Storagion" + }, + "dip": { + "edit": "Modifier", + "file": "Fichiers", + "metadata": "Métadonnées", + "root": "DIP" }, "job": { "create": "Créer", @@ -857,10 +863,10 @@ "action": { "delete": "Supprimer", "download": "Télécharger", + "goToAip": "Aller à Aip", "goToEdit": "Modifier", "resume": "Relancer", - "start": "Lancer", - "goToAip": "Aller à Aip" + "start": "Lancer" } } }, @@ -1124,6 +1130,15 @@ "submissionPolicy": "Montre les options qui ont été choisies lors de la création de votre espace de préservation (ou unité organisationnelle).\n\nVoir la documentation pour plus d'informations" } }, + "dip": { + "notification": { + "resource": { + "update": { + "fail": "Impossible de mettre à jour le DIP" + } + } + } + }, "doi": { "copyLongDoi": "Copier le DOI long", "copyShortDoi": "Copier le DOI court", @@ -1274,6 +1289,14 @@ "resumed": "Processus d'ingestion relancé avec succès" } }, + "dip": { + "action": { + "resume": { + "fail": "Impossible de relancer le traitement du DIP", + "success": "Le traitement du DIP a été relancé" + } + } + }, "sip": { "action": { "resume": { @@ -1324,6 +1347,12 @@ }, "preservation": { "aip": { + "aip": { + "aipOfAip": "Aips", + "table": { + "aipName": "Nom du Aip" + } + }, "back": "Retour", "button": { "back": "Retour", @@ -1336,12 +1365,6 @@ "tab": { "aip": "AIP", "details": "Métadonnées" - }, - "aip": { - "aipOfAip": "Aips", - "table": { - "aipName": "Nom du Aip" - } } }, "aipStats": { @@ -1364,9 +1387,42 @@ } }, "dip": { + "back": "Retour", + "button": { + "download": "Télécharger", + "resume": "Relancer" + }, + "form": { + "access": "Accès", + "aip": "AIP", + "complianceLevel": "Niveau de conformité des données", + "description": "Description", + "name": "Nom", + "organizationalUnitName": "Unité organisationnelle", + "resId": "Identifiant", + "status": "Statut", + "submit": "Enregistrer" + }, "home": { - "subtitle": "Paquet d'information diffusé", + "subtitle": "Paquet d'information à disséminer", "title": "DIP" + }, + "refresh": "Rafraichir", + "tab": { + "datafiles": "Fichiers", + "details": "Métadonnées" + }, + "table": { + "header": { + "creation": { + "when": "Date de création" + }, + "lastUpdate": { + "when": "Dernière mise à jour" + }, + "status": "Statut", + "title": "Titre" + } } }, "home": { -- GitLab