Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow partial export #522

Open
wants to merge 15 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 45 additions & 2 deletions backend/src/database/state-migrations/migrations.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
import type { EntityManager } from 'typeorm';
import type { PatientCategory } from 'digital-fuesim-manv-shared/dist/models/patient-category';
import type {
UUID,
StateExport,
Mutable,
MapImageTemplate,
VehicleTemplate,
ExerciseAction,
} from 'digital-fuesim-manv-shared';
import {
StateExport,
cloneDeepMutable,
PartialExport,
ExerciseState,
applyAllActions,
} from 'digital-fuesim-manv-shared';
import type { EntityManager } from 'typeorm';
import { RestoreError } from '../../utils/restore-error';
import { ActionWrapperEntity } from '../entities/action-wrapper.entity';
import { ExerciseWrapperEntity } from '../entities/exercise-wrapper.entity';
Expand Down Expand Up @@ -186,6 +190,45 @@ export function migrateStateExport(
return stateExport;
}

export function migratePartialExport(
partialExportToMigrate: PartialExport
): Mutable<PartialExport> {
// Encapsulate the partial export in a state export and migrate it
const mutablePartialExport = cloneDeepMutable(partialExportToMigrate);
const stateExport = cloneDeepMutable(
new StateExport({
...cloneDeepMutable(ExerciseState.create('123456')),
mapImageTemplates: partialExportToMigrate.mapImageTemplates ?? [],
patientCategories: mutablePartialExport.patientCategories ?? [],
vehicleTemplates: mutablePartialExport.vehicleTemplates ?? [],
})
);
stateExport.fileVersion = mutablePartialExport.fileVersion;
stateExport.dataVersion = mutablePartialExport.dataVersion;
const migratedStateExport = migrateStateExport(stateExport as StateExport);
const mapImageTemplates =
mutablePartialExport.mapImageTemplates !== undefined
? (migratedStateExport.currentState
.mapImageTemplates as MapImageTemplate[])
: undefined;
const patientCategories =
mutablePartialExport.patientCategories !== undefined
? (migratedStateExport.currentState
.patientCategories as PatientCategory[])
: undefined;
const vehicleTemplates =
mutablePartialExport.vehicleTemplates !== undefined
? (migratedStateExport.currentState
.vehicleTemplates as VehicleTemplate[])
: undefined;
const migratedPartialExport = new PartialExport(
patientCategories,
vehicleTemplates,
mapImageTemplates
);
return cloneDeepMutable(migratedPartialExport);
}

/**
* Migrates {@link propertiesToMigrate} to the newest version ({@link ExerciseState.currentStateVersion})
* by mutating them.
Expand Down
4 changes: 4 additions & 0 deletions frontend/src/app/pages/exercises/exercise/exercise.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import { ExerciseSettingsModalComponent } from './shared/exercise-settings/exerc
import { ExerciseStateBadgeComponent } from './shared/exercise-state-badge/exercise-state-badge.component';
import { ExerciseStatisticsModule } from './shared/exercise-statistics/exercise-statistics.module';
import { HospitalEditorModule } from './shared/hospital-editor/hospital-editor.module';
import { PartialExportSelectionComponent } from './shared/partial-export-selection/partial-export-selection-modal/partial-export-selection.component';
import { PartialImportOverwriteComponent } from './shared/partial-import-overwrite/partial-import-overwrite-modal/partial-import-overwrite.component';
import { TimeTravelComponent } from './shared/time-travel/time-travel.component';
import { TrainerMapEditorComponent } from './shared/trainer-map-editor/trainer-map-editor.component';
import { TrainerToolbarComponent } from './shared/trainer-toolbar/trainer-toolbar.component';
Expand All @@ -32,6 +34,8 @@ import { TransferOverviewModule } from './shared/transfer-overview/transfer-over
CreateImageTemplateModalComponent,
EditImageTemplateModalComponent,
ImageTemplateFormComponent,
PartialExportSelectionComponent,
PartialImportOverwriteComponent,
],
imports: [
CommonModule,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,10 @@ <h2>
um {{ timeConstraints.current | formatDuration }}
</span>
</button>
<button ngbDropdownItem (click)="partialExport()">
<i class="bi-card-list me-2"> </i>
Partieller Export
ClFeSc marked this conversation as resolved.
Show resolved Hide resolved
</button>
</div>
</div>
</h2>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import type { OnDestroy } from '@angular/core';
import { Component } from '@angular/core';
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
import { Store } from '@ngrx/store';
import {
cloneDeepMutable,
StateExport,
cloneDeepMutable,
StateHistoryCompound,
} from 'digital-fuesim-manv-shared';
import { Subject } from 'rxjs';
Expand All @@ -13,16 +14,17 @@ import { MessageService } from 'src/app/core/messages/message.service';
import { saveBlob } from 'src/app/shared/functions/save-blob';
import type { AppState } from 'src/app/state/app.state';
import {
selectExerciseId,
selectExerciseStateMode,
selectTimeConstraints,
selectExerciseId,
} from 'src/app/state/application/selectors/application.selectors';
import {
selectExerciseState,
selectParticipantId,
selectExerciseState,
} from 'src/app/state/application/selectors/exercise.selectors';
import { selectStateSnapshot } from 'src/app/state/get-state-snapshot';
import { selectOwnClient } from 'src/app/state/application/selectors/shared.selectors';
import { selectStateSnapshot } from 'src/app/state/get-state-snapshot';
import { openPartialExportSelectionModal } from '../shared/partial-export-selection/open-partial-export-selection-modal';

@Component({
selector: 'app-exercise',
Expand All @@ -43,7 +45,8 @@ export class ExerciseComponent implements OnDestroy {
private readonly store: Store<AppState>,
private readonly apiService: ApiService,
private readonly applicationService: ApplicationService,
private readonly messageService: MessageService
private readonly messageService: MessageService,
private readonly modalService: NgbModal
) {}

public shareExercise(type: 'participantId' | 'trainerId') {
Expand Down Expand Up @@ -103,6 +106,10 @@ export class ExerciseComponent implements OnDestroy {
saveBlob(blob, `exercise-state-${currentState.participantId}.json`);
}

public partialExport() {
openPartialExportSelectionModal(this.modalService);
}

public exportExerciseState() {
const currentState = selectStateSnapshot(
selectExerciseState,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import type { NgbModal } from '@ng-bootstrap/ng-bootstrap';
import { PartialExportSelectionComponent } from './partial-export-selection-modal/partial-export-selection.component';

export function openPartialExportSelectionModal(ngbModalService: NgbModal) {
ngbModalService.open(PartialExportSelectionComponent, {
size: 'm',
});
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<div class="modal-header">
<h4 class="modal-title">Partieller Export</h4>
<button type="button" class="btn-close" (click)="close()"></button>
</div>
<div class="modal-body">
<table class="table">
<tbody>
<tr>
<td>Patientenvorlagen</td>
<td>
<div class="form-switch">
<input
[(ngModel)]="configuration.patientCategories"
class="form-check-input"
type="checkbox"
/>
</div>
</td>
</tr>
<tr>
<td>Fahrzeugvorlagen</td>
<td>
<div class="form-switch">
<input
[(ngModel)]="configuration.vehicleTemplates"
class="form-check-input"
type="checkbox"
/>
</div>
</td>
</tr>
<tr>
<td>Bildvorlagen</td>
<td>
<div class="form-switch">
<input
[(ngModel)]="configuration.mapImageTemplates"
class="form-check-input"
type="checkbox"
/>
</div>
</td>
</tr>
</tbody>
</table>
ClFeSc marked this conversation as resolved.
Show resolved Hide resolved
<button
(click)="partialExport()"
[disabled]="
!(
configuration.mapImageTemplates ||
configuration.patientCategories ||
configuration.vehicleTemplates
)
"
class="btn btn-primary"
>
Exportieren
</button>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { Component } from '@angular/core';
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
import { Store } from '@ngrx/store';
import { cloneDeepMutable, PartialExport } from 'digital-fuesim-manv-shared';
import { saveBlob } from 'src/app/shared/functions/save-blob';
import type { AppState } from 'src/app/state/app.state';
import { selectExerciseState } from 'src/app/state/application/selectors/exercise.selectors';
import { selectStateSnapshot } from 'src/app/state/get-state-snapshot';

interface PartialExportConfiguration {
patientCategories: boolean;
vehicleTemplates: boolean;
mapImageTemplates: boolean;
}

@Component({
selector: 'app-partial-export-selection',
templateUrl: './partial-export-selection.component.html',
styleUrls: ['./partial-export-selection.component.scss'],
})
export class PartialExportSelectionComponent {
Dassderdie marked this conversation as resolved.
Show resolved Hide resolved
constructor(
private readonly store: Store<AppState>,
public activeModal: NgbActiveModal
ClFeSc marked this conversation as resolved.
Show resolved Hide resolved
) {}

public configuration: PartialExportConfiguration = {
mapImageTemplates: false,
patientCategories: false,
vehicleTemplates: false,
};

public close() {
this.activeModal.close();
}

public partialExport() {
const currentState = selectStateSnapshot(
selectExerciseState,
this.store
);
const patientCategories = this.configuration.patientCategories
? currentState.patientCategories
: undefined;
const vehicleTemplates = this.configuration.vehicleTemplates
? currentState.vehicleTemplates
: undefined;
const mapImageTemplates = this.configuration.mapImageTemplates
? currentState.mapImageTemplates
: undefined;
const blob = new Blob([
JSON.stringify(
new PartialExport(
cloneDeepMutable(patientCategories),
cloneDeepMutable(vehicleTemplates),
cloneDeepMutable(mapImageTemplates)
)
),
]);
saveBlob(blob, `exercise-partial-${currentState.participantId}.json`);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import type { NgbModal } from '@ng-bootstrap/ng-bootstrap';
import { PartialImportOverwriteComponent } from './partial-import-overwrite-modal/partial-import-overwrite.component';

export function openPartialImportOverwriteModal(
ngbModalService: NgbModal,
importString?: string
) {
const modelRef = ngbModalService.open(PartialImportOverwriteComponent, {
size: 'm',
});
const componentInstance =
modelRef.componentInstance as PartialImportOverwriteComponent;
componentInstance.importString = importString;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<div class="modal-header">
<h4 class="modal-title">Vorlagenimport</h4>
<button type="button" class="btn-close" (click)="close()"></button>
</div>
<div class="modal-body">
<div>
Wollen Sie die vorhandenen Vorlagen mit den importierten Vorlagen
ersetzen oder ergänzen?
</div>
</div>
<div class="modal-footer">
<label *ngIf="importingPartialExport" class="btn-outline-primary">
<span class="spinner-border spinner-border-sm"></span>
Import läuft
</label>
Dassderdie marked this conversation as resolved.
Show resolved Hide resolved
Dassderdie marked this conversation as resolved.
Show resolved Hide resolved
<button
type="button"
class="btn btn-danger"
(click)="partialImportOverwrite('overwrite')"
>
Überschreiben
</button>
<button
type="button"
class="btn btn-warning"
(click)="partialImportOverwrite('append')"
>
Ergänzen
</button>
<button
type="button"
class="btn btn-outline-secondary"
autofocus
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use [appAutofocus]="true" (or something like this). Your current solution doesn't work, as the HTML-attribute only focuses when the page is loaded.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, it still doesn't seem to work. No idea why. Either remove it completely or maybe add a comment about it not working...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Btw. you're using autofocus yourself in frontend/src/app/core/confirmation-modal/confirmation-modal.component.html. I think I might have copied this from there.

(click)="close()"
>
Abbrechen
</button>
</div>
ClFeSc marked this conversation as resolved.
Show resolved Hide resolved
Loading