From 31c169169f8ae626ff1b63ea7e6edb55cfcbb916 Mon Sep 17 00:00:00 2001 From: Lukas Radermacher <49586507+lukasrad02@users.noreply.github.com> Date: Thu, 19 Jan 2023 16:44:19 +0100 Subject: [PATCH 01/18] WIP --- .../create-image-template-modal.component.ts | 1 + .../send-alarm-group-interface.component.ts | 2 +- .../transfer-overview-table.component.html | 4 +- .../transfer-target-input.component.ts | 2 +- .../transfer-time-input.component.ts | 2 +- shared/src/models/alarm-group.ts | 4 ++ shared/src/models/client.ts | 5 +- shared/src/models/eoc-log-entry.ts | 4 ++ shared/src/models/exercise-configuration.ts | 4 ++ shared/src/models/hospital-patient.ts | 5 +- shared/src/models/hospital.ts | 5 +- shared/src/models/map-image-template.ts | 4 ++ shared/src/models/map-image.ts | 4 ++ shared/src/models/material-template.ts | 5 +- shared/src/models/material.ts | 5 +- shared/src/models/patient-category.ts | 4 ++ shared/src/models/patient-health-state.ts | 4 ++ shared/src/models/patient-template.ts | 5 +- shared/src/models/patient.ts | 10 +++- shared/src/models/personnel-template.ts | 5 +- shared/src/models/personnel.ts | 5 +- shared/src/models/simulated-region.ts | 4 ++ shared/src/models/transfer-point.ts | 9 +++- shared/src/models/vehicle-template.ts | 5 +- shared/src/models/vehicle.ts | 5 +- shared/src/models/viewport.ts | 4 ++ .../create-vehicle-parameters.ts | 1 + shared/src/state.ts | 4 +- .../src/store/action-reducers/alarm-group.ts | 10 ++-- shared/src/store/action-reducers/client.ts | 8 +-- shared/src/store/action-reducers/exercise.ts | 15 ++++-- shared/src/store/action-reducers/hospital.ts | 20 +++----- .../src/store/action-reducers/map-images.ts | 16 +++--- shared/src/store/action-reducers/material.ts | 2 +- shared/src/store/action-reducers/patient.ts | 10 ++-- .../store/action-reducers/simulated-region.ts | 8 +-- .../store/action-reducers/transfer-point.ts | 26 +++++----- shared/src/store/action-reducers/transfer.ts | 14 +++--- .../utils/calculate-treatments.ts | 49 ++++++------------- .../action-reducers/utils/get-element.ts | 21 +++----- .../action-reducers/utils/spatial-elements.ts | 18 +++++-- shared/src/store/action-reducers/vehicle.ts | 35 ++++++------- shared/src/store/action-reducers/viewport.ts | 8 +-- shared/src/utils/type-state-selector-map.ts | 15 ++++++ 44 files changed, 234 insertions(+), 162 deletions(-) create mode 100644 shared/src/utils/type-state-selector-map.ts diff --git a/frontend/src/app/pages/exercises/exercise/shared/editor-panel/create-image-template-modal/create-image-template-modal.component.ts b/frontend/src/app/pages/exercises/exercise/shared/editor-panel/create-image-template-modal/create-image-template-modal.component.ts index 11a6aead7..134360443 100644 --- a/frontend/src/app/pages/exercises/exercise/shared/editor-panel/create-image-template-modal/create-image-template-modal.component.ts +++ b/frontend/src/app/pages/exercises/exercise/shared/editor-panel/create-image-template-modal/create-image-template-modal.component.ts @@ -35,6 +35,7 @@ export class CreateImageTemplateModalComponent { type: '[MapImageTemplate] Add mapImageTemplate', mapImageTemplate: { id: uuid(), + type: 'mapImageTemplate', image: { url, height, diff --git a/frontend/src/app/pages/exercises/exercise/shared/emergency-operations-center/send-alarm-group-interface/send-alarm-group-interface.component.ts b/frontend/src/app/pages/exercises/exercise/shared/emergency-operations-center/send-alarm-group-interface/send-alarm-group-interface.component.ts index ef6929b26..61cb4b21b 100644 --- a/frontend/src/app/pages/exercises/exercise/shared/emergency-operations-center/send-alarm-group-interface/send-alarm-group-interface.component.ts +++ b/frontend/src/app/pages/exercises/exercise/shared/emergency-operations-center/send-alarm-group-interface/send-alarm-group-interface.component.ts @@ -106,7 +106,7 @@ export class SendAlarmGroupInterfaceComponent implements OnDestroy { }), this.exerciseService.proposeAction({ type: '[Transfer] Add to transfer', - elementType: 'vehicles', + elementType: 'vehicle', elementId: vehicleParameters.vehicle.id, startPoint: AlarmGroupStartPoint.create( alarmGroup.name, diff --git a/frontend/src/app/pages/exercises/exercise/shared/transfer-overview/transfer-overview-table/transfer-overview-table.component.html b/frontend/src/app/pages/exercises/exercise/shared/transfer-overview/transfer-overview-table/transfer-overview-table.component.html index b17308b0d..4d592c856 100644 --- a/frontend/src/app/pages/exercises/exercise/shared/transfer-overview/transfer-overview-table/transfer-overview-table.component.html +++ b/frontend/src/app/pages/exercises/exercise/shared/transfer-overview/transfer-overview-table/transfer-overview-table.component.html @@ -42,14 +42,14 @@ diff --git a/frontend/src/app/pages/exercises/exercise/shared/transfer-overview/transfer-target-input/transfer-target-input.component.ts b/frontend/src/app/pages/exercises/exercise/shared/transfer-overview/transfer-target-input/transfer-target-input.component.ts index 2bd0ab98f..dd84fa34e 100644 --- a/frontend/src/app/pages/exercises/exercise/shared/transfer-overview/transfer-target-input/transfer-target-input.component.ts +++ b/frontend/src/app/pages/exercises/exercise/shared/transfer-overview/transfer-target-input/transfer-target-input.component.ts @@ -11,7 +11,7 @@ import { selectTransferPoints } from 'src/app/state/application/selectors/exerci styleUrls: ['./transfer-target-input.component.scss'], }) export class TransferTargetInputComponent { - @Input() elementType!: 'personnel' | 'vehicles'; + @Input() elementType!: 'personnel' | 'vehicle'; @Input() elementId!: UUID; @Input() transfer!: Transfer; diff --git a/frontend/src/app/pages/exercises/exercise/shared/transfer-overview/transfer-time-input/transfer-time-input.component.ts b/frontend/src/app/pages/exercises/exercise/shared/transfer-overview/transfer-time-input/transfer-time-input.component.ts index 011a25963..f01016989 100644 --- a/frontend/src/app/pages/exercises/exercise/shared/transfer-overview/transfer-time-input/transfer-time-input.component.ts +++ b/frontend/src/app/pages/exercises/exercise/shared/transfer-overview/transfer-time-input/transfer-time-input.component.ts @@ -11,7 +11,7 @@ import { selectCurrentTime } from 'src/app/state/application/selectors/exercise. styleUrls: ['./transfer-time-input.component.scss'], }) export class TransferTimeInputComponent { - @Input() elementType!: 'personnel' | 'vehicles'; + @Input() elementType!: 'personnel' | 'vehicle'; @Input() elementId!: UUID; diff --git a/shared/src/models/alarm-group.ts b/shared/src/models/alarm-group.ts index 3d4d58d72..2d6ed8dc1 100644 --- a/shared/src/models/alarm-group.ts +++ b/shared/src/models/alarm-group.ts @@ -1,5 +1,6 @@ import { IsString, IsUUID } from 'class-validator'; import { UUID, uuid, uuidValidationOptions } from '../utils'; +import { IsValue } from '../utils/validators'; import { IsIdMap } from '../utils/validators/is-id-map'; import { getCreate } from './utils'; import { AlarmGroupVehicle } from './utils/alarm-group-vehicle'; @@ -8,6 +9,9 @@ export class AlarmGroup { @IsUUID(4, uuidValidationOptions) public readonly id: UUID = uuid(); + @IsValue('alarmGroup' as const) + public readonly type = 'alarmGroup'; + @IsString() public readonly name: string; diff --git a/shared/src/models/client.ts b/shared/src/models/client.ts index aac8ec2eb..ae42a3546 100644 --- a/shared/src/models/client.ts +++ b/shared/src/models/client.ts @@ -6,7 +6,7 @@ import { MaxLength, } from 'class-validator'; import { UUID, uuid, uuidValidationOptions } from '../utils'; -import { IsLiteralUnion } from '../utils/validators'; +import { IsLiteralUnion, IsValue } from '../utils/validators'; import { getCreate, Role } from './utils'; import { roleAllowedValues } from './utils/role'; @@ -14,6 +14,9 @@ export class Client { @IsUUID(4, uuidValidationOptions) public readonly id: UUID = uuid(); + @IsValue('client' as const) + public readonly type = 'client'; + @IsString() // Required by database @MaxLength(255) diff --git a/shared/src/models/eoc-log-entry.ts b/shared/src/models/eoc-log-entry.ts index f6b7c0113..ed83508e9 100644 --- a/shared/src/models/eoc-log-entry.ts +++ b/shared/src/models/eoc-log-entry.ts @@ -1,11 +1,15 @@ import { IsInt, IsString, IsUUID, MaxLength } from 'class-validator'; import { UUID, uuid, uuidValidationOptions } from '../utils'; +import { IsValue } from '../utils/validators'; import { getCreate } from './utils'; export class EocLogEntry { @IsUUID(4, uuidValidationOptions) public readonly id: UUID = uuid(); + @IsValue('eocLogEntry' as const) + public readonly type = 'eocLogEntry'; + /** * The time in the exercise */ diff --git a/shared/src/models/exercise-configuration.ts b/shared/src/models/exercise-configuration.ts index 9899cdc3d..dce59e0a9 100644 --- a/shared/src/models/exercise-configuration.ts +++ b/shared/src/models/exercise-configuration.ts @@ -1,9 +1,13 @@ import { Type } from 'class-transformer'; import { IsBoolean, ValidateNested } from 'class-validator'; import { defaultTileMapProperties } from '../data'; +import { IsValue } from '../utils/validators'; import { getCreate, TileMapProperties } from './utils'; export class ExerciseConfiguration { + @IsValue('exerciseConfiguration' as const) + public readonly type = 'exerciseConfiguration'; + @IsBoolean() public readonly pretriageEnabled: boolean = true; @IsBoolean() diff --git a/shared/src/models/hospital-patient.ts b/shared/src/models/hospital-patient.ts index b9aa4655f..17a2588ac 100644 --- a/shared/src/models/hospital-patient.ts +++ b/shared/src/models/hospital-patient.ts @@ -8,7 +8,7 @@ import { } from 'class-validator'; import type { Mutable } from '../utils'; import { cloneDeepMutable, UUID, uuidValidationOptions } from '../utils'; -import { IsIdMap, IsLiteralUnion } from '../utils/validators'; +import { IsIdMap, IsLiteralUnion, IsValue } from '../utils/validators'; import { getCreate, HealthPoints, @@ -29,6 +29,9 @@ export class HospitalPatient { @IsUUID(4, uuidValidationOptions) public readonly patientId: UUID; + @IsValue('hospitalPatient' as const) + public readonly type = 'hospitalPatient'; + /** * the vehicle that a patient was transported with */ diff --git a/shared/src/models/hospital.ts b/shared/src/models/hospital.ts index 80797693a..528e35c83 100644 --- a/shared/src/models/hospital.ts +++ b/shared/src/models/hospital.ts @@ -1,12 +1,15 @@ import { IsNumber, IsString, IsUUID, Min } from 'class-validator'; import { uuid, uuidValidationOptions, UUID, UUIDSet } from '../utils'; -import { IsUUIDSet } from '../utils/validators'; +import { IsUUIDSet, IsValue } from '../utils/validators'; import { getCreate } from './utils'; export class Hospital { @IsUUID(4, uuidValidationOptions) public readonly id: UUID = uuid(); + @IsValue('hospital' as const) + public readonly type = 'hospital'; + @IsString() public readonly name: string; diff --git a/shared/src/models/map-image-template.ts b/shared/src/models/map-image-template.ts index c701df501..9a2f4ab90 100644 --- a/shared/src/models/map-image-template.ts +++ b/shared/src/models/map-image-template.ts @@ -1,12 +1,16 @@ import { Type } from 'class-transformer'; import { IsString, IsUUID, ValidateNested } from 'class-validator'; import { UUID, uuid, uuidValidationOptions } from '../utils'; +import { IsValue } from '../utils/validators'; import { getCreate, ImageProperties } from './utils'; export class MapImageTemplate { @IsUUID(4, uuidValidationOptions) public readonly id: UUID = uuid(); + @IsValue('mapImageTemplate' as const) + public readonly type = 'mapImageTemplate'; + @IsString() public readonly name: string; diff --git a/shared/src/models/map-image.ts b/shared/src/models/map-image.ts index 6dc0c3e46..ad15022a7 100644 --- a/shared/src/models/map-image.ts +++ b/shared/src/models/map-image.ts @@ -1,12 +1,16 @@ import { Type } from 'class-transformer'; import { IsBoolean, IsInt, IsUUID, ValidateNested } from 'class-validator'; import { uuid, UUID, uuidValidationOptions } from '../utils'; +import { IsValue } from '../utils/validators'; import { Position, getCreate, ImageProperties } from './utils'; export class MapImage { @IsUUID(4, uuidValidationOptions) public readonly id: UUID = uuid(); + @IsValue('mapImage' as const) + public readonly type = 'mapImage'; + @ValidateNested() @Type(() => Position) public readonly position: Position; diff --git a/shared/src/models/material-template.ts b/shared/src/models/material-template.ts index 95f6ba6ec..39b45da3e 100644 --- a/shared/src/models/material-template.ts +++ b/shared/src/models/material-template.ts @@ -1,11 +1,14 @@ import { Type } from 'class-transformer'; import { IsNumber, Max, Min, ValidateNested } from 'class-validator'; import { maxTreatmentRange } from '../state-helpers/max-treatment-range'; -import { IsLiteralUnion } from '../utils/validators'; +import { IsLiteralUnion, IsValue } from '../utils/validators'; import { CanCaterFor, getCreate, ImageProperties } from './utils'; import { MaterialType, materialTypeAllowedValues } from './utils/material-type'; export class MaterialTemplate { + @IsValue('materialTemplate' as const) + public readonly type = 'materialTemplate'; + @IsLiteralUnion(materialTypeAllowedValues) public readonly materialType: MaterialType; diff --git a/shared/src/models/material.ts b/shared/src/models/material.ts index e82ab1ed0..5059705b3 100644 --- a/shared/src/models/material.ts +++ b/shared/src/models/material.ts @@ -10,7 +10,7 @@ import { } from 'class-validator'; import { maxTreatmentRange } from '../state-helpers/max-treatment-range'; import { uuidValidationOptions, UUID, uuid, UUIDSet } from '../utils'; -import { IsUUIDSet } from '../utils/validators'; +import { IsUUIDSet, IsValue } from '../utils/validators'; import type { MaterialTemplate } from './material-template'; import { CanCaterFor, Position, ImageProperties, getCreate } from './utils'; @@ -18,6 +18,9 @@ export class Material { @IsUUID(4, uuidValidationOptions) public readonly id: UUID = uuid(); + @IsValue('material' as const) + public readonly type = 'material'; + @IsUUID(4, uuidValidationOptions) public readonly vehicleId: UUID; diff --git a/shared/src/models/patient-category.ts b/shared/src/models/patient-category.ts index 6b9d8b683..d6161453c 100644 --- a/shared/src/models/patient-category.ts +++ b/shared/src/models/patient-category.ts @@ -1,10 +1,14 @@ import { Type } from 'class-transformer'; import { ArrayNotEmpty, IsArray, ValidateNested } from 'class-validator'; +import { IsValue } from '../utils/validators'; import { PatientTemplate } from './patient-template'; import { getCreate, ImageProperties } from './utils'; import { PatientStatusCode } from './utils/patient-status-code'; export class PatientCategory { + @IsValue('patientCategory' as const) + public readonly type = 'patientCategory'; + @ValidateNested() @Type(() => PatientStatusCode) public readonly name: PatientStatusCode; diff --git a/shared/src/models/patient-health-state.ts b/shared/src/models/patient-health-state.ts index 60810920e..9e04c85ea 100644 --- a/shared/src/models/patient-health-state.ts +++ b/shared/src/models/patient-health-state.ts @@ -7,6 +7,7 @@ import { ValidateNested, } from 'class-validator'; import { uuid, UUID, uuidValidationOptions } from '../utils'; +import { IsValue } from '../utils/validators'; import { getCreate, HealthPoints, IsValidHealthPoint } from './utils'; /** @@ -109,6 +110,9 @@ export class PatientHealthState { @IsUUID(4, uuidValidationOptions) public readonly id: UUID = uuid(); + @IsValue('patientHealthState' as const) + public readonly type = 'patientHealthState'; + @Type(() => FunctionParameters) @ValidateNested() public readonly functionParameters: FunctionParameters; diff --git a/shared/src/models/patient-template.ts b/shared/src/models/patient-template.ts index 65e319f09..63a785757 100644 --- a/shared/src/models/patient-template.ts +++ b/shared/src/models/patient-template.ts @@ -1,7 +1,7 @@ import { Type } from 'class-transformer'; import { IsUUID, ValidateNested } from 'class-validator'; import { cloneDeepMutable, UUID, uuid, uuidValidationOptions } from '../utils'; -import { IsIdMap } from '../utils/validators'; +import { IsIdMap, IsValue } from '../utils/validators'; import type { PatientStatusCode } from './utils'; import { BiometricInformation, @@ -21,6 +21,9 @@ export class PatientTemplate { @IsUUID(4, uuidValidationOptions) public readonly id: UUID = uuid(); + @IsValue('patientTemplate' as const) + public readonly type = 'patientTemplate'; + @ValidateNested() @Type(() => BiometricInformation) public readonly biometricInformation: BiometricInformation; diff --git a/shared/src/models/patient.ts b/shared/src/models/patient.ts index 19423c02d..0e33f4850 100644 --- a/shared/src/models/patient.ts +++ b/shared/src/models/patient.ts @@ -12,7 +12,12 @@ import { isEmpty, } from 'class-validator'; import { uuidValidationOptions, UUID, uuid, UUIDSet } from '../utils'; -import { IsLiteralUnion, IsIdMap, IsUUIDSet } from '../utils/validators'; +import { + IsLiteralUnion, + IsIdMap, + IsUUIDSet, + IsValue, +} from '../utils/validators'; import { PatientHealthState } from './patient-health-state'; import { BiometricInformation, @@ -32,6 +37,9 @@ export class Patient { @IsUUID(4, uuidValidationOptions) public readonly id: UUID = uuid(); + @IsValue('patient') + public readonly type = 'patient'; + @ValidateNested() @Type(() => PersonalInformation) public readonly personalInformation: PersonalInformation; diff --git a/shared/src/models/personnel-template.ts b/shared/src/models/personnel-template.ts index ccc06782a..fc3e3c101 100644 --- a/shared/src/models/personnel-template.ts +++ b/shared/src/models/personnel-template.ts @@ -1,7 +1,7 @@ import { Type } from 'class-transformer'; import { IsNumber, Max, Min, ValidateNested } from 'class-validator'; import { maxTreatmentRange } from '../state-helpers/max-treatment-range'; -import { IsLiteralUnion } from '../utils/validators'; +import { IsLiteralUnion, IsValue } from '../utils/validators'; import { PersonnelType, CanCaterFor, @@ -12,6 +12,9 @@ import { personnelTypeAllowedValues } from './utils/personnel-type'; // TODO: These are not (yet) saved in the state -> Decide whether they should and if not move this file from the models folder away export class PersonnelTemplate { + @IsValue('personnelTemplate' as const) + public readonly type = 'personnelTemplate'; + @IsLiteralUnion(personnelTypeAllowedValues) public readonly personnelType: PersonnelType; diff --git a/shared/src/models/personnel.ts b/shared/src/models/personnel.ts index afe24d0e4..b5dbd2430 100644 --- a/shared/src/models/personnel.ts +++ b/shared/src/models/personnel.ts @@ -10,7 +10,7 @@ import { } from 'class-validator'; import { maxTreatmentRange } from '../state-helpers/max-treatment-range'; import { uuidValidationOptions, UUID, uuid, UUIDSet } from '../utils'; -import { IsLiteralUnion, IsUUIDSet } from '../utils/validators'; +import { IsLiteralUnion, IsUUIDSet, IsValue } from '../utils/validators'; import type { PersonnelTemplate } from './personnel-template'; import { PersonnelType, @@ -26,6 +26,9 @@ export class Personnel { @IsUUID(4, uuidValidationOptions) public readonly id: UUID = uuid(); + @IsValue('personnel' as const) + public readonly type = 'personnel'; + @IsUUID(4, uuidValidationOptions) public readonly vehicleId: UUID; diff --git a/shared/src/models/simulated-region.ts b/shared/src/models/simulated-region.ts index 6e3db79a4..8d4f47cc4 100644 --- a/shared/src/models/simulated-region.ts +++ b/shared/src/models/simulated-region.ts @@ -1,6 +1,7 @@ import { Type } from 'class-transformer'; import { IsString, IsUUID, ValidateNested } from 'class-validator'; import { UUID, uuid, uuidValidationOptions } from '../utils'; +import { IsValue } from '../utils/validators'; import { getCreate, Position, Size } from './utils'; import type { ImageProperties } from './utils'; @@ -8,6 +9,9 @@ export class SimulatedRegion { @IsUUID(4, uuidValidationOptions) public readonly id: UUID = uuid(); + @IsValue('simulatedRegion' as const) + public readonly type = 'simulatedRegion'; + /** * top-left position */ diff --git a/shared/src/models/transfer-point.ts b/shared/src/models/transfer-point.ts index b825b36be..5aa895ca5 100644 --- a/shared/src/models/transfer-point.ts +++ b/shared/src/models/transfer-point.ts @@ -1,7 +1,11 @@ import { Type } from 'class-transformer'; import { IsString, IsUUID, ValidateNested } from 'class-validator'; import { UUID, uuid, UUIDSet, uuidValidationOptions } from '../utils'; -import { IsReachableTransferPoints, IsUUIDSet } from '../utils/validators'; +import { + IsReachableTransferPoints, + IsUUIDSet, + IsValue, +} from '../utils/validators'; import type { ImageProperties } from './utils'; import { getCreate, Position } from './utils'; @@ -9,6 +13,9 @@ export class TransferPoint { @IsUUID(4, uuidValidationOptions) public readonly id: UUID = uuid(); + @IsValue('transferPoint' as const) + public readonly type = 'transferPoint'; + @ValidateNested() @Type(() => Position) public readonly position: Position; diff --git a/shared/src/models/vehicle-template.ts b/shared/src/models/vehicle-template.ts index f0dd3157b..c2956dd40 100644 --- a/shared/src/models/vehicle-template.ts +++ b/shared/src/models/vehicle-template.ts @@ -7,7 +7,7 @@ import { IsArray, } from 'class-validator'; import { uuidValidationOptions, UUID, uuid } from '../utils'; -import { IsLiteralUnion } from '../utils/validators'; +import { IsLiteralUnion, IsValue } from '../utils/validators'; import type { PersonnelType } from './utils'; import { ImageProperties, @@ -21,6 +21,9 @@ export class VehicleTemplate { @IsUUID(4, uuidValidationOptions) public readonly id: UUID = uuid(); + @IsValue('vehicleTemplate' as const) + public readonly type = 'vehicleTemplate'; + @IsString() public readonly vehicleType: string; diff --git a/shared/src/models/vehicle.ts b/shared/src/models/vehicle.ts index 95141b20b..abad33a37 100644 --- a/shared/src/models/vehicle.ts +++ b/shared/src/models/vehicle.ts @@ -7,7 +7,7 @@ import { ValidateNested, } from 'class-validator'; import { uuid, uuidValidationOptions, UUID, UUIDSet } from '../utils'; -import { IsUUIDSet } from '../utils/validators'; +import { IsUUIDSet, IsValue } from '../utils/validators'; import { getCreate, Position, Transfer } from './utils'; import { ImageProperties } from './utils/image-properties'; @@ -15,6 +15,9 @@ export class Vehicle { @IsUUID(4, uuidValidationOptions) public readonly id: UUID = uuid(); + @IsValue('vehicle' as const) + public readonly type = 'vehicle'; + @IsString() public readonly vehicleType: string; diff --git a/shared/src/models/viewport.ts b/shared/src/models/viewport.ts index fc31482b7..4a147e472 100644 --- a/shared/src/models/viewport.ts +++ b/shared/src/models/viewport.ts @@ -1,6 +1,7 @@ import { Type } from 'class-transformer'; import { IsString, IsUUID, ValidateNested } from 'class-validator'; import { UUID, uuid, uuidValidationOptions } from '../utils'; +import { IsValue } from '../utils/validators'; import { getCreate, Position, Size } from './utils'; import type { ImageProperties } from './utils'; @@ -8,6 +9,9 @@ export class Viewport { @IsUUID(4, uuidValidationOptions) public readonly id: UUID = uuid(); + @IsValue('viewport' as const) + public readonly type = 'viewport'; + /** * top-left position */ diff --git a/shared/src/state-helpers/create-vehicle-parameters.ts b/shared/src/state-helpers/create-vehicle-parameters.ts index d4d9951ad..cfda5766d 100644 --- a/shared/src/state-helpers/create-vehicle-parameters.ts +++ b/shared/src/state-helpers/create-vehicle-parameters.ts @@ -44,6 +44,7 @@ export function createVehicleParameters( const vehicle: Vehicle = { id: vehicleId, + type: 'vehicle', materialIds: arrayToUUIDSet(materials.map((m) => m.id)), vehicleType: vehicleTemplate.vehicleType, name: vehicleTemplate.name, diff --git a/shared/src/state.ts b/shared/src/state.ts index ab27ab0db..9071494cd 100644 --- a/shared/src/state.ts +++ b/shared/src/state.ts @@ -44,7 +44,7 @@ import { SpatialTree, } from './models/utils'; import type { MaterialType } from './models/utils/material-type'; -import type { SpatialElementType } from './store/action-reducers/utils/spatial-elements'; +import type { SpatialElementSelector } from './store/action-reducers/utils/spatial-elements'; import type { UUID } from './utils'; import { uuid, uuidValidationOptions } from './utils'; import { IsIdMap, IsLiteralUnion } from './utils/validators'; @@ -123,7 +123,7 @@ export class ExerciseState { // Mutable` could still have immutable objects in spatialTree @IsObject() public readonly spatialTrees: { - [type in SpatialElementType]: SpatialTree; + [type in SpatialElementSelector]: SpatialTree; } = { materials: SpatialTree.create(), patients: SpatialTree.create(), diff --git a/shared/src/store/action-reducers/alarm-group.ts b/shared/src/store/action-reducers/alarm-group.ts index 598c30c63..cf4d3cead 100644 --- a/shared/src/store/action-reducers/alarm-group.ts +++ b/shared/src/store/action-reducers/alarm-group.ts @@ -96,7 +96,7 @@ export namespace AlarmGroupActionReducers { reducer: (draftState, { alarmGroupId, name }) => { const alarmGroup = getElement( draftState, - 'alarmGroups', + 'alarmGroup', alarmGroupId ); alarmGroup.name = name; @@ -108,7 +108,7 @@ export namespace AlarmGroupActionReducers { export const removeAlarmGroup: ActionReducer = { action: RemoveAlarmGroupAction, reducer: (draftState, { alarmGroupId }) => { - getElement(draftState, 'alarmGroups', alarmGroupId); + getElement(draftState, 'alarmGroup', alarmGroupId); delete draftState.alarmGroups[alarmGroupId]; return draftState; }, @@ -121,7 +121,7 @@ export namespace AlarmGroupActionReducers { reducer: (draftState, { alarmGroupId, alarmGroupVehicle }) => { const alarmGroup = getElement( draftState, - 'alarmGroups', + 'alarmGroup', alarmGroupId ); alarmGroup.alarmGroupVehicles[alarmGroupVehicle.id] = @@ -140,7 +140,7 @@ export namespace AlarmGroupActionReducers { ) => { const alarmGroup = getElement( draftState, - 'alarmGroups', + 'alarmGroup', alarmGroupId ); const alarmGroupVehicle = getAlarmGroupVehicle( @@ -160,7 +160,7 @@ export namespace AlarmGroupActionReducers { reducer: (draftState, { alarmGroupId, alarmGroupVehicleId }) => { const alarmGroup = getElement( draftState, - 'alarmGroups', + 'alarmGroup', alarmGroupId ); getAlarmGroupVehicle(alarmGroup, alarmGroupVehicleId); diff --git a/shared/src/store/action-reducers/client.ts b/shared/src/store/action-reducers/client.ts index 5c1438f22..55208470e 100644 --- a/shared/src/store/action-reducers/client.ts +++ b/shared/src/store/action-reducers/client.ts @@ -53,7 +53,7 @@ export namespace ClientActionReducers { export const removeClient: ActionReducer = { action: RemoveClientAction, reducer: (draftState, { clientId }) => { - getElement(draftState, 'clients', clientId); + getElement(draftState, 'client', clientId); delete draftState.clients[clientId]; return draftState; }, @@ -64,12 +64,12 @@ export namespace ClientActionReducers { { action: RestrictViewToViewportAction, reducer: (draftState, { clientId, viewportId }) => { - const client = getElement(draftState, 'clients', clientId); + const client = getElement(draftState, 'client', clientId); if (viewportId === undefined) { client.viewRestrictedToViewportId = viewportId; return draftState; } - getElement(draftState, 'viewports', viewportId); + getElement(draftState, 'viewport', viewportId); client.viewRestrictedToViewportId = viewportId; return draftState; }, @@ -79,7 +79,7 @@ export namespace ClientActionReducers { export const setWaitingRoom: ActionReducer = { action: SetWaitingRoomAction, reducer: (draftState, { clientId, shouldBeInWaitingRoom }) => { - const client = getElement(draftState, 'clients', clientId); + const client = getElement(draftState, 'client', clientId); client.isInWaitingRoom = shouldBeInWaitingRoom; return draftState; }, diff --git a/shared/src/store/action-reducers/exercise.ts b/shared/src/store/action-reducers/exercise.ts index 55a1fbba3..b2a7c0918 100644 --- a/shared/src/store/action-reducers/exercise.ts +++ b/shared/src/store/action-reducers/exercise.ts @@ -11,6 +11,7 @@ import { Patient } from '../../models'; import { getStatus } from '../../models/utils'; import type { ExerciseState } from '../../state'; import type { Mutable } from '../../utils'; +import { typeSelectorMap } from '../../utils/type-state-selector-map'; import { IsValue } from '../../utils/validators'; import type { Action, ActionReducer } from '../action-reducer'; import { ReducerError } from '../reducer-error'; @@ -122,7 +123,7 @@ export namespace ExerciseActionReducers { }); // Refresh transfers - refreshTransfer(draftState, 'vehicles', tickInterval); + refreshTransfer(draftState, 'vehicle', tickInterval); refreshTransfer(draftState, 'personnel', tickInterval); return draftState; }, @@ -130,12 +131,18 @@ export namespace ExerciseActionReducers { }; } +const transferTypeSelectorMap = { + personnel: typeSelectorMap.personnel, + vehicle: typeSelectorMap.vehicle, +} as const; +type TransferTypeSelectorMap = typeof transferTypeSelectorMap; + function refreshTransfer( draftState: Mutable, - key: 'personnel' | 'vehicles', + type: keyof TransferTypeSelectorMap, tickInterval: number ): void { - const elements = draftState[key]; + const elements = draftState[transferTypeSelectorMap[type]]; Object.values(elements).forEach((element: Mutable) => { if (!element.transfer) { return; @@ -148,6 +155,6 @@ function refreshTransfer( if (element.transfer.endTimeStamp > draftState.currentTime) { return; } - letElementArrive(draftState, key, element.id); + letElementArrive(draftState, type, element.id); }); } diff --git a/shared/src/store/action-reducers/hospital.ts b/shared/src/store/action-reducers/hospital.ts index 0f2214691..b5bb8e559 100644 --- a/shared/src/store/action-reducers/hospital.ts +++ b/shared/src/store/action-reducers/hospital.ts @@ -74,11 +74,7 @@ export namespace HospitalActionReducers { { action: EditTransportDurationToHospitalAction, reducer: (draftState, { hospitalId, transportDuration }) => { - const hospital = getElement( - draftState, - 'hospitals', - hospitalId - ); + const hospital = getElement(draftState, 'hospital', hospitalId); hospital.transportDuration = transportDuration; return draftState; }, @@ -88,7 +84,7 @@ export namespace HospitalActionReducers { export const renameHospital: ActionReducer = { action: RenameHospitalAction, reducer: (draftState, { hospitalId, name }) => { - const hospital = getElement(draftState, 'hospitals', hospitalId); + const hospital = getElement(draftState, 'hospital', hospitalId); hospital.name = name; return draftState; }, @@ -98,7 +94,7 @@ export namespace HospitalActionReducers { export const removeHospital: ActionReducer = { action: RemoveHospitalAction, reducer: (draftState, { hospitalId }) => { - const hospital = getElement(draftState, 'hospitals', hospitalId); + const hospital = getElement(draftState, 'hospital', hospitalId); // TODO: maybe make a hospital undeletable (if at least one patient is in it) for (const patientId of Object.keys(hospital.patientIds)) { delete draftState.hospitalPatients[patientId]; @@ -118,17 +114,13 @@ export namespace HospitalActionReducers { { action: TransportPatientToHospitalAction, reducer: (draftState, { hospitalId, vehicleId }) => { - const hospital = getElement( - draftState, - 'hospitals', - hospitalId - ); - const vehicle = getElement(draftState, 'vehicles', vehicleId); + const hospital = getElement(draftState, 'hospital', hospitalId); + const vehicle = getElement(draftState, 'vehicle', vehicleId); // TODO: Block vehicles whose material and personnel are unloaded for (const patientId of Object.keys(vehicle.patientIds)) { const patient = getElement( draftState, - 'patients', + 'patient', patientId ); draftState.hospitalPatients[patientId] = diff --git a/shared/src/store/action-reducers/map-images.ts b/shared/src/store/action-reducers/map-images.ts index 26bce6329..7384033ff 100644 --- a/shared/src/store/action-reducers/map-images.ts +++ b/shared/src/store/action-reducers/map-images.ts @@ -133,7 +133,7 @@ export namespace MapImagesActionReducers { export const moveMapImage: ActionReducer = { action: MoveMapImageAction, reducer: (draftState, { mapImageId, targetPosition }) => { - const mapImage = getElement(draftState, 'mapImages', mapImageId); + const mapImage = getElement(draftState, 'mapImage', mapImageId); mapImage.position = cloneDeepMutable(targetPosition); return draftState; }, @@ -143,7 +143,7 @@ export namespace MapImagesActionReducers { export const scaleMapImage: ActionReducer = { action: ScaleMapImageAction, reducer: (draftState, { mapImageId, newHeight, newAspectRatio }) => { - const mapImage = getElement(draftState, 'mapImages', mapImageId); + const mapImage = getElement(draftState, 'mapImage', mapImageId); if (newHeight) { mapImage.image.height = newHeight; } @@ -158,7 +158,7 @@ export namespace MapImagesActionReducers { export const removeMapImage: ActionReducer = { action: RemoveMapImageAction, reducer: (draftState, { mapImageId }) => { - getElement(draftState, 'mapImages', mapImageId); + getElement(draftState, 'mapImage', mapImageId); delete draftState.mapImages[mapImageId]; return draftState; }, @@ -169,11 +169,7 @@ export namespace MapImagesActionReducers { { action: ReconfigureMapImageUrlAction, reducer: (draftState, { mapImageId, newUrl }) => { - const mapImage = getElement( - draftState, - 'mapImages', - mapImageId - ); + const mapImage = getElement(draftState, 'mapImage', mapImageId); mapImage.image.url = newUrl; return draftState; }, @@ -183,7 +179,7 @@ export namespace MapImagesActionReducers { export const setLockedMapImage: ActionReducer = { action: SetIsLockedMapImageAction, reducer: (draftState, { mapImageId, newLocked }) => { - const mapImage = getElement(draftState, 'mapImages', mapImageId); + const mapImage = getElement(draftState, 'mapImage', mapImageId); mapImage.isLocked = newLocked; return draftState; }, @@ -193,7 +189,7 @@ export namespace MapImagesActionReducers { export const changeZIndex: ActionReducer = { action: ChangeZIndexMapImageAction, reducer: (draftState, { mapImageId, mode }) => { - const mapImage = getElement(draftState, 'mapImages', mapImageId); + const mapImage = getElement(draftState, 'mapImage', mapImageId); switch (mode) { case 'bringToFront': case 'bringToBack': { diff --git a/shared/src/store/action-reducers/material.ts b/shared/src/store/action-reducers/material.ts index a38b201ab..4d1847dc4 100644 --- a/shared/src/store/action-reducers/material.ts +++ b/shared/src/store/action-reducers/material.ts @@ -24,7 +24,7 @@ export namespace MaterialActionReducers { reducer: (draftState, { materialId, targetPosition }) => { updateElementPosition( draftState, - 'materials', + 'material', materialId, targetPosition ); diff --git a/shared/src/store/action-reducers/patient.ts b/shared/src/store/action-reducers/patient.ts index 277ab6e50..613aa7a2d 100644 --- a/shared/src/store/action-reducers/patient.ts +++ b/shared/src/store/action-reducers/patient.ts @@ -29,7 +29,7 @@ export function deletePatient( draftState: Mutable, patientId: UUID ) { - removeElementPosition(draftState, 'patients', patientId); + removeElementPosition(draftState, 'patient', patientId); delete draftState.patients[patientId]; } @@ -120,7 +120,7 @@ export namespace PatientActionReducers { } const mutablePatient = cloneDeepMutable(patient); draftState.patients[mutablePatient.id] = mutablePatient; - addElementPosition(draftState, 'patients', mutablePatient.id); + addElementPosition(draftState, 'patient', mutablePatient.id); return draftState; }, rights: 'trainer', @@ -131,7 +131,7 @@ export namespace PatientActionReducers { reducer: (draftState, { patientId, targetPosition }) => { updateElementPosition( draftState, - 'patients', + 'patient', patientId, targetPosition ); @@ -152,7 +152,7 @@ export namespace PatientActionReducers { export const setVisibleStatus: ActionReducer = { action: SetVisibleStatusAction, reducer: (draftState, { patientId, patientStatus }) => { - const patient = getElement(draftState, 'patients', patientId); + const patient = getElement(draftState, 'patient', patientId); patient.pretriageStatus = patientStatus; if (patient.position !== undefined) { @@ -167,7 +167,7 @@ export namespace PatientActionReducers { export const setUserTextAction: ActionReducer = { action: SetUserTextAction, reducer: (draftState, { patientId, remarks }) => { - const patient = getElement(draftState, 'patients', patientId); + const patient = getElement(draftState, 'patient', patientId); patient.remarks = remarks; return draftState; }, diff --git a/shared/src/store/action-reducers/simulated-region.ts b/shared/src/store/action-reducers/simulated-region.ts index 390e0456e..4de91f2ec 100644 --- a/shared/src/store/action-reducers/simulated-region.ts +++ b/shared/src/store/action-reducers/simulated-region.ts @@ -71,7 +71,7 @@ export namespace SimulatedRegionActionReducers { { action: RemoveSimulatedRegionAction, reducer: (draftState, { simulatedRegionId }) => { - getElement(draftState, 'simulatedRegions', simulatedRegionId); + getElement(draftState, 'simulatedRegion', simulatedRegionId); delete draftState.simulatedRegions[simulatedRegionId]; return draftState; }, @@ -84,7 +84,7 @@ export namespace SimulatedRegionActionReducers { reducer: (draftState, { simulatedRegionId, targetPosition }) => { const simulatedRegion = getElement( draftState, - 'simulatedRegions', + 'simulatedRegion', simulatedRegionId ); simulatedRegion.position = cloneDeepMutable(targetPosition); @@ -102,7 +102,7 @@ export namespace SimulatedRegionActionReducers { ) => { const simulatedRegion = getElement( draftState, - 'simulatedRegions', + 'simulatedRegion', simulatedRegionId ); simulatedRegion.position = cloneDeepMutable(targetPosition); @@ -118,7 +118,7 @@ export namespace SimulatedRegionActionReducers { reducer: (draftState, { simulatedRegionId, newName }) => { const simulatedRegion = getElement( draftState, - 'simulatedRegions', + 'simulatedRegion', simulatedRegionId ); simulatedRegion.name = newName; diff --git a/shared/src/store/action-reducers/transfer-point.ts b/shared/src/store/action-reducers/transfer-point.ts index dd55cbae0..caad44b2c 100644 --- a/shared/src/store/action-reducers/transfer-point.ts +++ b/shared/src/store/action-reducers/transfer-point.ts @@ -124,7 +124,7 @@ export namespace TransferPointActionReducers { reducer: (draftState, { transferPointId, targetPosition }) => { const transferPoint = getElement( draftState, - 'transferPoints', + 'transferPoint', transferPointId ); transferPoint.position = cloneDeepMutable(targetPosition); @@ -142,7 +142,7 @@ export namespace TransferPointActionReducers { ) => { const transferPoint = getElement( draftState, - 'transferPoints', + 'transferPoint', transferPointId ); // Empty strings are ignored @@ -173,12 +173,12 @@ export namespace TransferPointActionReducers { } const transferPoint1 = getElement( draftState, - 'transferPoints', + 'transferPoint', transferPointId1 ); const transferPoint2 = getElement( draftState, - 'transferPoints', + 'transferPoint', transferPointId2 ); const _duration = @@ -210,12 +210,12 @@ export namespace TransferPointActionReducers { } const transferPoint1 = getElement( draftState, - 'transferPoints', + 'transferPoint', transferPointId1 ); const transferPoint2 = getElement( draftState, - 'transferPoints', + 'transferPoint', transferPointId2 ); delete transferPoint1.reachableTransferPoints[transferPointId2]; @@ -230,20 +230,20 @@ export namespace TransferPointActionReducers { action: RemoveTransferPointAction, reducer: (draftState, { transferPointId }) => { // check if transferPoint exists - getElement(draftState, 'transferPoints', transferPointId); + getElement(draftState, 'transferPoint', transferPointId); // TODO: make it dynamic (if at any time something else is able to transfer this part needs to be changed accordingly) // Let all vehicles and personnel arrive that are on transfer to this transferPoint before deleting it for (const vehicleId of Object.keys(draftState.vehicles)) { const vehicle = getElement( draftState, - 'vehicles', + 'vehicle', vehicleId ); if ( vehicle.transfer?.targetTransferPointId === transferPointId ) { - letElementArrive(draftState, 'vehicles', vehicleId); + letElementArrive(draftState, 'vehicle', vehicleId); } } for (const personnelId of Object.keys(draftState.personnel)) { @@ -286,10 +286,10 @@ export namespace TransferPointActionReducers { action: ConnectHospitalAction, reducer: (draftState, { transferPointId, hospitalId }) => { // Check if hospital with this Id exists - getElement(draftState, 'hospitals', hospitalId); + getElement(draftState, 'hospital', hospitalId); const transferPoint = getElement( draftState, - 'transferPoints', + 'transferPoint', transferPointId ); transferPoint.reachableHospitals[hospitalId] = true; @@ -302,10 +302,10 @@ export namespace TransferPointActionReducers { action: DisconnectHospitalAction, reducer: (draftState, { hospitalId, transferPointId }) => { // Check if hospital with this Id exists - getElement(draftState, 'hospitals', hospitalId); + getElement(draftState, 'hospital', hospitalId); const transferPoint = getElement( draftState, - 'transferPoints', + 'transferPoint', transferPointId ); delete transferPoint.reachableHospitals[hospitalId]; diff --git a/shared/src/store/action-reducers/transfer.ts b/shared/src/store/action-reducers/transfer.ts index cd9f88fa9..1414d478c 100644 --- a/shared/src/store/action-reducers/transfer.ts +++ b/shared/src/store/action-reducers/transfer.ts @@ -17,9 +17,9 @@ import { updateElementPosition, } from './utils/spatial-elements'; -type TransferableElementType = 'personnel' | 'vehicles'; +type TransferableElementType = 'personnel' | 'vehicle'; const transferableElementTypeAllowedValues: AllowedValues = - { personnel: true, vehicles: true }; + { personnel: true, vehicle: true }; /** * Personnel/Vehicle in transfer will arrive immediately at new targetTransferPoint @@ -38,7 +38,7 @@ export function letElementArrive( } const targetTransferPoint = getElement( draftState, - 'transferPoints', + 'transferPoint', element.transfer.targetTransferPointId ); const newPosition: Mutable = { @@ -131,7 +131,7 @@ export namespace TransferActionReducers { { elementType, elementId, startPoint, targetTransferPointId } ) => { // check if transferPoint exists - getElement(draftState, 'transferPoints', targetTransferPointId); + getElement(draftState, 'transferPoint', targetTransferPointId); const element = getElement(draftState, elementType, elementId); if (element.transfer) { throw new ReducerError( @@ -144,7 +144,7 @@ export namespace TransferActionReducers { if (startPoint.type === 'transferPoint') { const transferStartPoint = getElement( draftState, - 'transferPoints', + 'transferPoint', startPoint.transferPointId ); const connection = @@ -193,7 +193,7 @@ export namespace TransferActionReducers { } if (targetTransferPointId) { // check if transferPoint exists - getElement(draftState, 'transferPoints', targetTransferPointId); + getElement(draftState, 'transferPoint', targetTransferPointId); element.transfer.targetTransferPointId = targetTransferPointId; } if (timeToAdd) { @@ -215,7 +215,7 @@ export namespace TransferActionReducers { { elementType, elementId, targetTransferPointId } ) => { // check if transferPoint exists - getElement(draftState, 'transferPoints', targetTransferPointId); + getElement(draftState, 'transferPoint', targetTransferPointId); const element = getElement(draftState, elementType, elementId); if (!element.transfer) { throw getNotInTransferError(element.id); diff --git a/shared/src/store/action-reducers/utils/calculate-treatments.ts b/shared/src/store/action-reducers/utils/calculate-treatments.ts index fbc9fd9d7..23ebff3b2 100644 --- a/shared/src/store/action-reducers/utils/calculate-treatments.ts +++ b/shared/src/store/action-reducers/utils/calculate-treatments.ts @@ -6,6 +6,7 @@ import { SpatialTree } from '../../../models/utils/spatial-tree'; import type { ExerciseState } from '../../../state'; import { maxTreatmentRange } from '../../../state-helpers/max-treatment-range'; import type { Mutable, UUID } from '../../../utils'; +import { typeSelectorMap } from '../../../utils/type-state-selector-map'; import { getElement } from './get-element'; // TODO: `caterFor` and `treat` are currently used as synonyms without a clear distinction. @@ -82,7 +83,7 @@ function tryToCaterFor( cateringElement.assignedPatientIds[patient.id] = true; - if (isPersonnel(cateringElement)) { + if (cateringElement.type === 'personnel') { patient.assignedPersonnelIds[cateringElement.id] = true; } else { patient.assignedMaterialIds[cateringElement.id] = true; @@ -92,26 +93,6 @@ function tryToCaterFor( return true; } -// TODO: Instead, give each Element a "type" property -> discriminated union -function isPatient( - element: Mutable -): element is Mutable { - return (element as Patient).personalInformation !== undefined; -} - -function isPersonnel( - element: Mutable -): element is Mutable { - return (element as Personnel).personnelType !== undefined; -} - -function isMaterial( - element: Mutable -): element is Mutable { - // as Material does not include any distinguishable properties, we will check if it is not of type Personnel or Patient - return !isPersonnel(element) && !isPatient(element); -} - /** * @param position of the patient where all elements of {@link elementType} should be recalculated * @param elementIdsToBeSkipped the elements whose treatment should not be updated @@ -119,11 +100,11 @@ function isMaterial( function updateCateringAroundPatient( state: Mutable, position: Position, - elementType: 'materials' | 'personnel', + elementType: 'material' | 'personnel', elementIdsToBeSkipped: Set ) { const elementsInTreatmentRange = SpatialTree.findAllElementsInCircle( - state.spatialTrees[elementType], + state.spatialTrees[typeSelectorMap[elementType]], position, maxTreatmentRange ).filter((elementId) => !elementIdsToBeSkipped.has(elementId)); @@ -140,7 +121,7 @@ function removeTreatmentsOfElement( // TODO: when elements have their own type saved don't use const patient = getElement(state, 'patients', element.id); // instead use const patient = element; // same for personnel and material in the other if statements - if (isPatient(element)) { + if (element.type === 'patient') { const patient = element; // Make all personnel stop treating this patient for (const personnelId of Object.keys(patient.assignedPersonnelIds)) { @@ -150,23 +131,23 @@ function removeTreatmentsOfElement( patient.assignedPersonnelIds = {}; // Make all material stop treating this patient for (const materialId of Object.keys(patient.assignedMaterialIds)) { - const material = getElement(state, 'materials', materialId); + const material = getElement(state, 'material', materialId); delete material.assignedPatientIds[patient.id]; } patient.assignedMaterialIds = {}; - } else if (isPersonnel(element)) { + } else if (element.type === 'personnel') { const personnel = element; // This personnel doesn't treat any patients anymore for (const patientId of Object.keys(personnel.assignedPatientIds)) { - const patient = getElement(state, 'patients', patientId); + const patient = getElement(state, 'patient', patientId); delete patient.assignedPersonnelIds[personnel.id]; } personnel.assignedPatientIds = {}; - } else if (isMaterial(element)) { + } else if (element.type === 'material') { const material = element; // This material doesn't treat any patients anymore for (const patientId of Object.keys(material.assignedPatientIds)) { - const patient = getElement(state, 'patients', patientId); + const patient = getElement(state, 'patient', patientId); delete patient.assignedMaterialIds[material.id]; } material.assignedPatientIds = {}; @@ -194,7 +175,7 @@ export function updateTreatments( return; } - if (isPersonnel(element) || isMaterial(element)) { + if (element.type === 'personnel' || element.type === 'material') { updateCatering(state, element); return; } @@ -208,7 +189,7 @@ export function updateTreatments( alreadyUpdatedElementIds.add(personnelId); } for (const materialId of Object.keys(element.assignedMaterialIds)) { - updateCatering(state, getElement(state, 'materials', materialId)); + updateCatering(state, getElement(state, 'material', materialId)); // Saving materialIds of material that already got calculated - makes small movements of patients more efficient alreadyUpdatedElementIds.add(materialId); } @@ -222,7 +203,7 @@ export function updateTreatments( updateCateringAroundPatient( state, element.position, - 'materials', + 'material', alreadyUpdatedElementIds ); // The treatment of the patient has just been updated -> hence the visible status hasn't been changed since the last update @@ -270,7 +251,7 @@ function updateCatering( tryToCaterFor( cateringElement, catersFor, - getElement(state, 'patients', patientId), + getElement(state, 'patient', patientId), state.configuration.pretriageEnabled, state.configuration.bluePatientsEnabled ); @@ -296,7 +277,7 @@ function updateCatering( ) // Filter out every patient in the overrideTreatmentRange .filter((patientId) => !cateredForPatients.has(patientId)) - .map((patientId) => getElement(state, 'patients', patientId)); + .map((patientId) => getElement(state, 'patient', patientId)); const patientsPerStatus = groupBy(patientsInTreatmentRange, (patient) => getCateringStatus( diff --git a/shared/src/store/action-reducers/utils/get-element.ts b/shared/src/store/action-reducers/utils/get-element.ts index 721faa5dd..4ed737ed1 100644 --- a/shared/src/store/action-reducers/utils/get-element.ts +++ b/shared/src/store/action-reducers/utils/get-element.ts @@ -1,5 +1,7 @@ import type { ExerciseState } from '../../../state'; import type { Mutable, UUID } from '../../../utils'; +import type { TypeSelectorMap } from '../../../utils/type-state-selector-map'; +import { typeSelectorMap } from '../../../utils/type-state-selector-map'; import { ReducerError } from '../../reducer-error'; /** @@ -7,25 +9,16 @@ import { ReducerError } from '../../reducer-error'; * @throws ReducerError if the element does not exist */ export function getElement< - ElementType extends - | 'alarmGroups' - | 'clients' - | 'hospitals' - | 'mapImages' - | 'materials' - | 'patients' - | 'personnel' - | 'simulatedRegions' - | 'transferPoints' - | 'vehicles' - | 'viewports', + ElementType extends keyof TypeSelectorMap, State extends ExerciseState | Mutable >( state: State, elementType: ElementType, elementId: UUID -): State[ElementType][UUID] { - const element = state[elementType][elementId] as State[ElementType][UUID]; +): State[TypeSelectorMap[ElementType]][UUID] { + const element = state[typeSelectorMap[elementType]][ + elementId + ] as State[TypeSelectorMap[ElementType]][UUID]; if (!element) { throw new ReducerError( `Element of type ${elementType} with id ${elementId} does not exist` diff --git a/shared/src/store/action-reducers/utils/spatial-elements.ts b/shared/src/store/action-reducers/utils/spatial-elements.ts index 49e84c6da..f840adb2c 100644 --- a/shared/src/store/action-reducers/utils/spatial-elements.ts +++ b/shared/src/store/action-reducers/utils/spatial-elements.ts @@ -3,6 +3,7 @@ import { SpatialTree } from '../../../models/utils/spatial-tree'; import type { ExerciseState } from '../../../state'; import type { Mutable, UUID } from '../../../utils'; import { cloneDeepMutable } from '../../../utils'; +import { typeSelectorMap } from '../../../utils/type-state-selector-map'; import { updateTreatments } from './calculate-treatments'; import { getElement } from './get-element'; @@ -11,7 +12,14 @@ import { getElement } from './get-element'; * The position of the element must be changed via one of the function in this file. * In addition, the respective functions must be called when an element gets added or removed. */ -export type SpatialElementType = 'materials' | 'patients' | 'personnel'; +const spatialTypeSelectorMap = { + material: typeSelectorMap.material, + patient: typeSelectorMap.patient, + personnel: typeSelectorMap.personnel, +} as const; +type SpatialTypeSelectorMap = typeof spatialTypeSelectorMap; +type SpatialElementType = keyof SpatialTypeSelectorMap; +export type SpatialElementSelector = SpatialTypeSelectorMap[SpatialElementType]; /** * Adds an element with a position and executes side effects to guarantee the consistency of the state. @@ -27,7 +35,7 @@ export function addElementPosition( return; } SpatialTree.addElement( - state.spatialTrees[elementType], + state.spatialTrees[spatialTypeSelectorMap[elementType]], element.id, element.position ); @@ -47,14 +55,14 @@ export function updateElementPosition( const startPosition = element.position; if (startPosition !== undefined) { SpatialTree.moveElement( - state.spatialTrees[elementType], + state.spatialTrees[spatialTypeSelectorMap[elementType]], element.id, startPosition, targetPosition ); } else { SpatialTree.addElement( - state.spatialTrees[elementType], + state.spatialTrees[spatialTypeSelectorMap[elementType]], element.id, targetPosition ); @@ -77,7 +85,7 @@ export function removeElementPosition( return; } SpatialTree.removeElement( - state.spatialTrees[elementType], + state.spatialTrees[spatialTypeSelectorMap[elementType]], element.id, element.position ); diff --git a/shared/src/store/action-reducers/vehicle.ts b/shared/src/store/action-reducers/vehicle.ts index 6db4d99bb..5d9778d05 100644 --- a/shared/src/store/action-reducers/vehicle.ts +++ b/shared/src/store/action-reducers/vehicle.ts @@ -26,10 +26,10 @@ export function deleteVehicle( draftState: Mutable, vehicleId: UUID ) { - const vehicle = getElement(draftState, 'vehicles', vehicleId); + const vehicle = getElement(draftState, 'vehicle', vehicleId); // Delete related material and personnel Object.keys(vehicle.materialIds).forEach((materialId) => { - removeElementPosition(draftState, 'materials', materialId); + removeElementPosition(draftState, 'material', materialId); delete draftState.materials[materialId]; }); Object.keys(vehicle.personnelIds).forEach((personnelId) => { @@ -151,7 +151,7 @@ export namespace VehicleActionReducers { draftState.vehicles[vehicle.id] = cloneDeepMutable(vehicle); for (const material of cloneDeepMutable(materials)) { draftState.materials[material.id] = material; - addElementPosition(draftState, 'materials', material.id); + addElementPosition(draftState, 'material', material.id); } for (const person of cloneDeepMutable(personnel)) { draftState.personnel[person.id] = person; @@ -165,7 +165,7 @@ export namespace VehicleActionReducers { export const moveVehicle: ActionReducer = { action: MoveVehicleAction, reducer: (draftState, { vehicleId, targetPosition }) => { - const vehicle = getElement(draftState, 'vehicles', vehicleId); + const vehicle = getElement(draftState, 'vehicle', vehicleId); vehicle.position = cloneDeepMutable(targetPosition); return draftState; }, @@ -175,7 +175,7 @@ export namespace VehicleActionReducers { export const renameVehicle: ActionReducer = { action: RenameVehicleAction, reducer: (draftState, { vehicleId, name }) => { - const vehicle = getElement(draftState, 'vehicles', vehicleId); + const vehicle = getElement(draftState, 'vehicle', vehicleId); vehicle.name = name; for (const personnelId of Object.keys(vehicle.personnelIds)) { draftState.personnel[personnelId]!.vehicleName = name; @@ -200,7 +200,7 @@ export namespace VehicleActionReducers { export const unloadVehicle: ActionReducer = { action: UnloadVehicleAction, reducer: (draftState, { vehicleId }) => { - const vehicle = getElement(draftState, 'vehicles', vehicleId); + const vehicle = getElement(draftState, 'vehicle', vehicleId); const unloadPosition = vehicle.position; if (!unloadPosition) { throw new ReducerError( @@ -226,7 +226,7 @@ export namespace VehicleActionReducers { for (const patientId of patientIds) { x += space; - updateElementPosition(draftState, 'patients', patientId, { + updateElementPosition(draftState, 'patient', patientId, { x, y: unloadPosition.y, }); @@ -255,13 +255,9 @@ export namespace VehicleActionReducers { for (const materialId of materialIds) { x += space; - const material = getElement( - draftState, - 'materials', - materialId - ); + const material = getElement(draftState, 'material', materialId); if (Material.isInVehicle(material)) { - updateElementPosition(draftState, 'materials', materialId, { + updateElementPosition(draftState, 'material', materialId, { x, y: unloadPosition.y, }); @@ -279,12 +275,12 @@ export namespace VehicleActionReducers { draftState, { vehicleId, elementToBeLoadedId, elementToBeLoadedType } ) => { - const vehicle = getElement(draftState, 'vehicles', vehicleId); + const vehicle = getElement(draftState, 'vehicle', vehicleId); switch (elementToBeLoadedType) { case 'materials': { const material = getElement( draftState, - 'materials', + 'material', elementToBeLoadedId ); if (!vehicle.materialIds[elementToBeLoadedId]) { @@ -292,7 +288,7 @@ export namespace VehicleActionReducers { `Material with id ${material.id} is not assignable to the vehicle with id ${vehicle.id}` ); } - removeElementPosition(draftState, 'materials', material.id); + removeElementPosition(draftState, 'material', material.id); break; } case 'personnel': { @@ -321,7 +317,7 @@ export namespace VehicleActionReducers { case 'patients': { const patient = getElement( draftState, - 'patients', + 'patient', elementToBeLoadedId ); if ( @@ -334,13 +330,14 @@ export namespace VehicleActionReducers { } vehicle.patientIds[elementToBeLoadedId] = true; - removeElementPosition(draftState, 'patients', patient.id); + // removeElementPosition(draftState, 'patient', patient.id); + removeElementPosition(draftState, patient.type, patient.id); // Load in all materials Object.keys(vehicle.materialIds).forEach((materialId) => { removeElementPosition( draftState, - 'materials', + 'material', materialId ); }); diff --git a/shared/src/store/action-reducers/viewport.ts b/shared/src/store/action-reducers/viewport.ts index 13c3b1919..f81ee56a3 100644 --- a/shared/src/store/action-reducers/viewport.ts +++ b/shared/src/store/action-reducers/viewport.ts @@ -69,7 +69,7 @@ export namespace ViewportActionReducers { export const removeViewport: ActionReducer = { action: RemoveViewportAction, reducer: (draftState, { viewportId }) => { - getElement(draftState, 'viewports', viewportId); + getElement(draftState, 'viewport', viewportId); delete draftState.viewports[viewportId]; return draftState; }, @@ -79,7 +79,7 @@ export namespace ViewportActionReducers { export const moveViewport: ActionReducer = { action: MoveViewportAction, reducer: (draftState, { viewportId, targetPosition }) => { - const viewport = getElement(draftState, 'viewports', viewportId); + const viewport = getElement(draftState, 'viewport', viewportId); viewport.position = cloneDeepMutable(targetPosition); return draftState; }, @@ -89,7 +89,7 @@ export namespace ViewportActionReducers { export const resizeViewport: ActionReducer = { action: ResizeViewportAction, reducer: (draftState, { viewportId, targetPosition, newSize }) => { - const viewport = getElement(draftState, 'viewports', viewportId); + const viewport = getElement(draftState, 'viewport', viewportId); viewport.position = cloneDeepMutable(targetPosition); viewport.size = cloneDeepMutable(newSize); return draftState; @@ -100,7 +100,7 @@ export namespace ViewportActionReducers { export const renameViewport: ActionReducer = { action: RenameViewportAction, reducer: (draftState, { viewportId, newName }) => { - const viewport = getElement(draftState, 'viewports', viewportId); + const viewport = getElement(draftState, 'viewport', viewportId); viewport.name = newName; return draftState; }, diff --git a/shared/src/utils/type-state-selector-map.ts b/shared/src/utils/type-state-selector-map.ts new file mode 100644 index 000000000..09ab1ac85 --- /dev/null +++ b/shared/src/utils/type-state-selector-map.ts @@ -0,0 +1,15 @@ +export const typeSelectorMap = { + alarmGroup: 'alarmGroups', + client: 'clients', + hospital: 'hospitals', + mapImage: 'mapImages', + material: 'materials', + patient: 'patients', + personnel: 'personnel', + simulatedRegion: 'simulatedRegions', + transferPoint: 'transferPoints', + vehicle: 'vehicles', + viewport: 'viewports', +} as const; + +export type TypeSelectorMap = typeof typeSelectorMap; From b5a13c860bb9722183b2d3c3731bf1a42c4ebb23 Mon Sep 17 00:00:00 2001 From: Lukas Radermacher <49586507+lukasrad02@users.noreply.github.com> Date: Tue, 24 Jan 2023 12:22:54 +0100 Subject: [PATCH 02/18] Pick specific selectors from TypeSelectorMap --- shared/src/store/action-reducers/exercise.ts | 13 ++++++------- .../action-reducers/utils/spatial-elements.ts | 18 +++++++----------- 2 files changed, 13 insertions(+), 18 deletions(-) diff --git a/shared/src/store/action-reducers/exercise.ts b/shared/src/store/action-reducers/exercise.ts index b2a7c0918..a2733c828 100644 --- a/shared/src/store/action-reducers/exercise.ts +++ b/shared/src/store/action-reducers/exercise.ts @@ -11,7 +11,10 @@ import { Patient } from '../../models'; import { getStatus } from '../../models/utils'; import type { ExerciseState } from '../../state'; import type { Mutable } from '../../utils'; -import { typeSelectorMap } from '../../utils/type-state-selector-map'; +import { + TypeSelectorMap, + typeSelectorMap, +} from '../../utils/type-state-selector-map'; import { IsValue } from '../../utils/validators'; import type { Action, ActionReducer } from '../action-reducer'; import { ReducerError } from '../reducer-error'; @@ -131,18 +134,14 @@ export namespace ExerciseActionReducers { }; } -const transferTypeSelectorMap = { - personnel: typeSelectorMap.personnel, - vehicle: typeSelectorMap.vehicle, -} as const; -type TransferTypeSelectorMap = typeof transferTypeSelectorMap; +type TransferTypeSelectorMap = Pick; function refreshTransfer( draftState: Mutable, type: keyof TransferTypeSelectorMap, tickInterval: number ): void { - const elements = draftState[transferTypeSelectorMap[type]]; + const elements = draftState[typeSelectorMap[type]]; Object.values(elements).forEach((element: Mutable) => { if (!element.transfer) { return; diff --git a/shared/src/store/action-reducers/utils/spatial-elements.ts b/shared/src/store/action-reducers/utils/spatial-elements.ts index f840adb2c..2458b0568 100644 --- a/shared/src/store/action-reducers/utils/spatial-elements.ts +++ b/shared/src/store/action-reducers/utils/spatial-elements.ts @@ -3,6 +3,7 @@ import { SpatialTree } from '../../../models/utils/spatial-tree'; import type { ExerciseState } from '../../../state'; import type { Mutable, UUID } from '../../../utils'; import { cloneDeepMutable } from '../../../utils'; +import type { TypeSelectorMap } from '../../../utils/type-state-selector-map'; import { typeSelectorMap } from '../../../utils/type-state-selector-map'; import { updateTreatments } from './calculate-treatments'; import { getElement } from './get-element'; @@ -12,13 +13,8 @@ import { getElement } from './get-element'; * The position of the element must be changed via one of the function in this file. * In addition, the respective functions must be called when an element gets added or removed. */ -const spatialTypeSelectorMap = { - material: typeSelectorMap.material, - patient: typeSelectorMap.patient, - personnel: typeSelectorMap.personnel, -} as const; -type SpatialTypeSelectorMap = typeof spatialTypeSelectorMap; -type SpatialElementType = keyof SpatialTypeSelectorMap; +type SpatialElementType = 'material' | 'patient' | 'personnel'; +type SpatialTypeSelectorMap = Pick; export type SpatialElementSelector = SpatialTypeSelectorMap[SpatialElementType]; /** @@ -35,7 +31,7 @@ export function addElementPosition( return; } SpatialTree.addElement( - state.spatialTrees[spatialTypeSelectorMap[elementType]], + state.spatialTrees[typeSelectorMap[elementType]], element.id, element.position ); @@ -55,14 +51,14 @@ export function updateElementPosition( const startPosition = element.position; if (startPosition !== undefined) { SpatialTree.moveElement( - state.spatialTrees[spatialTypeSelectorMap[elementType]], + state.spatialTrees[typeSelectorMap[elementType]], element.id, startPosition, targetPosition ); } else { SpatialTree.addElement( - state.spatialTrees[spatialTypeSelectorMap[elementType]], + state.spatialTrees[typeSelectorMap[elementType]], element.id, targetPosition ); @@ -85,7 +81,7 @@ export function removeElementPosition( return; } SpatialTree.removeElement( - state.spatialTrees[spatialTypeSelectorMap[elementType]], + state.spatialTrees[typeSelectorMap[elementType]], element.id, element.position ); From eff1ca7a750c561201a1532ae50f1b9abc62276a Mon Sep 17 00:00:00 2001 From: Lukas Radermacher <49586507+lukasrad02@users.noreply.github.com> Date: Tue, 24 Jan 2023 12:31:23 +0100 Subject: [PATCH 03/18] Fix linter --- shared/src/store/action-reducers/exercise.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/shared/src/store/action-reducers/exercise.ts b/shared/src/store/action-reducers/exercise.ts index a2733c828..123cd68b8 100644 --- a/shared/src/store/action-reducers/exercise.ts +++ b/shared/src/store/action-reducers/exercise.ts @@ -11,10 +11,8 @@ import { Patient } from '../../models'; import { getStatus } from '../../models/utils'; import type { ExerciseState } from '../../state'; import type { Mutable } from '../../utils'; -import { - TypeSelectorMap, - typeSelectorMap, -} from '../../utils/type-state-selector-map'; +import type { TypeSelectorMap } from '../../utils/type-state-selector-map'; +import { typeSelectorMap } from '../../utils/type-state-selector-map'; import { IsValue } from '../../utils/validators'; import type { Action, ActionReducer } from '../action-reducer'; import { ReducerError } from '../reducer-error'; From 4e67f172b63d22f51b7644c175119fbb57140687 Mon Sep 17 00:00:00 2001 From: Lukas Radermacher <49586507+lukasrad02@users.noreply.github.com> Date: Tue, 24 Jan 2023 14:52:51 +0100 Subject: [PATCH 04/18] Remove type property from feature --- .../catering-lines-feature-manager.ts | 1 - .../element-feature-manager.ts | 8 +----- .../feature-managers/element-manager.ts | 14 +--------- .../map-images-feature-manager.ts | 8 +++--- .../material-feature-manager.ts | 5 ++-- .../patient-feature-manager.ts | 9 +++---- .../personnel-feature-manager.ts | 5 ++-- .../simulated-region-feature-manager.ts | 7 +++-- .../transfer-lines-feature-manager.ts | 1 - .../transfer-point-feature-manager.ts | 15 ++++++----- .../vehicle-feature-manager.ts | 27 ++++++++----------- .../viewport-feature-manager.ts | 7 +++-- shared/src/store/action-reducers/vehicle.ts | 12 ++++----- 13 files changed, 45 insertions(+), 74 deletions(-) diff --git a/frontend/src/app/pages/exercises/exercise/shared/exercise-map/feature-managers/catering-lines-feature-manager.ts b/frontend/src/app/pages/exercises/exercise/shared/exercise-map/feature-managers/catering-lines-feature-manager.ts index 388e360ee..d18292808 100644 --- a/frontend/src/app/pages/exercises/exercise/shared/exercise-map/feature-managers/catering-lines-feature-manager.ts +++ b/frontend/src/app/pages/exercises/exercise/shared/exercise-map/feature-managers/catering-lines-feature-manager.ts @@ -19,7 +19,6 @@ export class CateringLinesFeatureManager > implements FeatureManager> { - readonly type = 'cateringLines'; readonly unsupportedChangeProperties = new Set(['id'] as const); private readonly lineStyleHelper = new LineStyleHelper( diff --git a/frontend/src/app/pages/exercises/exercise/shared/exercise-map/feature-managers/element-feature-manager.ts b/frontend/src/app/pages/exercises/exercise/shared/exercise-map/feature-managers/element-feature-manager.ts index 93c081916..3b3e58cdc 100644 --- a/frontend/src/app/pages/exercises/exercise/shared/exercise-map/feature-managers/element-feature-manager.ts +++ b/frontend/src/app/pages/exercises/exercise/shared/exercise-map/feature-managers/element-feature-manager.ts @@ -1,9 +1,4 @@ -import type { - ExerciseState, - Position, - Size, - UUID, -} from 'digital-fuesim-manv-shared'; +import type { Position, Size, UUID } from 'digital-fuesim-manv-shared'; import { isArray } from 'lodash-es'; import type { MapBrowserEvent } from 'ol'; import { Feature } from 'ol'; @@ -83,7 +78,6 @@ export abstract class ElementFeatureManager< > implements FeatureManager { - abstract override readonly type: keyof ExerciseState; public readonly togglePopup$ = new Subject>(); protected readonly movementAnimator = new MovementAnimator( this.olMap, diff --git a/frontend/src/app/pages/exercises/exercise/shared/exercise-map/feature-managers/element-manager.ts b/frontend/src/app/pages/exercises/exercise/shared/exercise-map/feature-managers/element-manager.ts index 4fae9eb95..9e2d5cd07 100644 --- a/frontend/src/app/pages/exercises/exercise/shared/exercise-map/feature-managers/element-manager.ts +++ b/frontend/src/app/pages/exercises/exercise/shared/exercise-map/feature-managers/element-manager.ts @@ -19,18 +19,11 @@ export abstract class ElementManager< UnsupportedChangeProperties > = Exclude, UnsupportedChangeProperties> > { - /** - * When an element gets (dragged &) dropped, this identifies the type of the dropped element. - * @example `patients` - */ - abstract readonly type: string; - /** * This should be called if a new element is added. */ public onElementCreated(element: Element) { const feature = this.createFeature(element); - feature.set(featureKeys.type, this.type); feature.set(featureKeys.value, element); } @@ -117,10 +110,7 @@ export abstract class ElementManager< ): ElementFeature | undefined; public getElementFromFeature(feature: Feature) { - return { - type: feature.get(featureKeys.type), - value: feature.get(featureKeys.value), - }; + return feature.get(featureKeys.value); } private areAllPropertiesSupported( @@ -140,6 +130,4 @@ export abstract class ElementManager< */ const featureKeys = { value: 'elementValue', - // TODO: In the future the type should be saved in the element itself - type: 'elementType', }; diff --git a/frontend/src/app/pages/exercises/exercise/shared/exercise-map/feature-managers/map-images-feature-manager.ts b/frontend/src/app/pages/exercises/exercise/shared/exercise-map/feature-managers/map-images-feature-manager.ts index 43d58bc70..407f3df62 100644 --- a/frontend/src/app/pages/exercises/exercise/shared/exercise-map/feature-managers/map-images-feature-manager.ts +++ b/frontend/src/app/pages/exercises/exercise/shared/exercise-map/feature-managers/map-images-feature-manager.ts @@ -15,9 +15,8 @@ import { ImageStyleHelper } from '../utility/style-helper/image-style-helper'; import { createPoint, ElementFeatureManager } from './element-feature-manager'; export class MapImageFeatureManager extends ElementFeatureManager { - readonly type = 'mapImages'; private readonly imageStyleHelper = new ImageStyleHelper( - (feature) => this.getElementFromFeature(feature)!.value.image + (feature) => (this.getElementFromFeature(feature) as MapImage).image ); private readonly popupHelper = new ImagePopupHelper(this.olMap, this.layer); @@ -45,7 +44,8 @@ export class MapImageFeatureManager extends ElementFeatureManager { resolution ); style.setZIndex( - this.getElementFromFeature(feature as Feature)!.value.zIndex + (this.getElementFromFeature(feature as Feature) as MapImage) + .zIndex ); return style; }); @@ -68,7 +68,7 @@ export class MapImageFeatureManager extends ElementFeatureManager { } override isFeatureTranslatable(feature: Feature): boolean { - const mapImage = this.getElementFromFeature(feature).value as MapImage; + const mapImage = this.getElementFromFeature(feature) as MapImage; return ( selectStateSnapshot(selectCurrentRole, this.store) === 'trainer' && !mapImage.isLocked diff --git a/frontend/src/app/pages/exercises/exercise/shared/exercise-map/feature-managers/material-feature-manager.ts b/frontend/src/app/pages/exercises/exercise/shared/exercise-map/feature-managers/material-feature-manager.ts index b6da79e5a..6fd61a460 100644 --- a/frontend/src/app/pages/exercises/exercise/shared/exercise-map/feature-managers/material-feature-manager.ts +++ b/frontend/src/app/pages/exercises/exercise/shared/exercise-map/feature-managers/material-feature-manager.ts @@ -16,13 +16,12 @@ import { createPoint, ElementFeatureManager } from './element-feature-manager'; export class MaterialFeatureManager extends ElementFeatureManager< WithPosition > { - readonly type = 'materials'; private readonly imageStyleHelper = new ImageStyleHelper( - (feature) => this.getElementFromFeature(feature)!.value.image + (feature) => (this.getElementFromFeature(feature) as Material).image ); private readonly nameStyleHelper = new NameStyleHelper( (feature) => { - const material = this.getElementFromFeature(feature)!.value; + const material = this.getElementFromFeature(feature) as Material; return { name: material.vehicleName, offsetY: material.image.height / 2 / normalZoom, diff --git a/frontend/src/app/pages/exercises/exercise/shared/exercise-map/feature-managers/patient-feature-manager.ts b/frontend/src/app/pages/exercises/exercise/shared/exercise-map/feature-managers/patient-feature-manager.ts index e2ff69fee..dae0854fc 100644 --- a/frontend/src/app/pages/exercises/exercise/shared/exercise-map/feature-managers/patient-feature-manager.ts +++ b/frontend/src/app/pages/exercises/exercise/shared/exercise-map/feature-managers/patient-feature-manager.ts @@ -21,11 +21,10 @@ import { createPoint, ElementFeatureManager } from './element-feature-manager'; export class PatientFeatureManager extends ElementFeatureManager< WithPosition > { - readonly type = 'patients'; private readonly popupHelper = new ImagePopupHelper(this.olMap, this.layer); private readonly imageStyleHelper = new ImageStyleHelper((feature) => { - const patient = this.getElementFromFeature(feature)!.value; + const patient = this.getElementFromFeature(feature) as Patient; return { ...patient.image, rotation: patient.pretriageInformation.isWalkable @@ -36,7 +35,7 @@ export class PatientFeatureManager extends ElementFeatureManager< private readonly circleStyleHelper = new CircleStyleHelper( (feature) => { - const patient = this.getElementFromFeature(feature)!.value; + const patient = this.getElementFromFeature(feature) as Patient; const configuration = selectStateSnapshot( selectConfiguration, this.store @@ -59,8 +58,8 @@ export class PatientFeatureManager extends ElementFeatureManager< }, 0.025, (feature) => - this.getElementFromFeature(feature)!.value.pretriageInformation - .isWalkable + (this.getElementFromFeature(feature) as Patient) + .pretriageInformation.isWalkable ? [0, 0.25] : [-0.25, 0] ); diff --git a/frontend/src/app/pages/exercises/exercise/shared/exercise-map/feature-managers/personnel-feature-manager.ts b/frontend/src/app/pages/exercises/exercise/shared/exercise-map/feature-managers/personnel-feature-manager.ts index 3d42f47ee..9dca667c8 100644 --- a/frontend/src/app/pages/exercises/exercise/shared/exercise-map/feature-managers/personnel-feature-manager.ts +++ b/frontend/src/app/pages/exercises/exercise/shared/exercise-map/feature-managers/personnel-feature-manager.ts @@ -16,13 +16,12 @@ import { createPoint, ElementFeatureManager } from './element-feature-manager'; export class PersonnelFeatureManager extends ElementFeatureManager< WithPosition > { - readonly type = 'personnel'; private readonly imageStyleHelper = new ImageStyleHelper( - (feature) => this.getElementFromFeature(feature)!.value.image + (feature) => (this.getElementFromFeature(feature) as Personnel).image ); private readonly nameStyleHelper = new NameStyleHelper( (feature) => { - const personnel = this.getElementFromFeature(feature)!.value; + const personnel = this.getElementFromFeature(feature) as Personnel; return { name: personnel.vehicleName, offsetY: personnel.image.height / 2 / normalZoom, diff --git a/frontend/src/app/pages/exercises/exercise/shared/exercise-map/feature-managers/simulated-region-feature-manager.ts b/frontend/src/app/pages/exercises/exercise/shared/exercise-map/feature-managers/simulated-region-feature-manager.ts index acfa70e1d..fd1c7dd1b 100644 --- a/frontend/src/app/pages/exercises/exercise/shared/exercise-map/feature-managers/simulated-region-feature-manager.ts +++ b/frontend/src/app/pages/exercises/exercise/shared/exercise-map/feature-managers/simulated-region-feature-manager.ts @@ -26,8 +26,6 @@ export class SimulatedRegionFeatureManager extends ElementFeatureManager implements FeatureManager> { - readonly type = 'simulatedRegions'; - override unsupportedChangeProperties = new Set(['id'] as const); constructor( @@ -63,8 +61,9 @@ export class SimulatedRegionFeatureManager ResizeRectangleInteraction.onResize( feature, ({ topLeftCoordinate, scale }) => { - const currentElement = this.getElementFromFeature(feature)! - .value as SimulatedRegion; + const currentElement = this.getElementFromFeature( + feature + ) as SimulatedRegion; this.exerciseService.proposeAction( { type: '[SimulatedRegion] Resize simulated region', diff --git a/frontend/src/app/pages/exercises/exercise/shared/exercise-map/feature-managers/transfer-lines-feature-manager.ts b/frontend/src/app/pages/exercises/exercise/shared/exercise-map/feature-managers/transfer-lines-feature-manager.ts index 999de802b..4c5ba8065 100644 --- a/frontend/src/app/pages/exercises/exercise/shared/exercise-map/feature-managers/transfer-lines-feature-manager.ts +++ b/frontend/src/app/pages/exercises/exercise/shared/exercise-map/feature-managers/transfer-lines-feature-manager.ts @@ -19,7 +19,6 @@ export class TransferLinesFeatureManager > implements FeatureManager> { - readonly type = 'transferLines'; readonly unsupportedChangeProperties = new Set(['id'] as const); constructor(public readonly layer: VectorLayer>) { diff --git a/frontend/src/app/pages/exercises/exercise/shared/exercise-map/feature-managers/transfer-point-feature-manager.ts b/frontend/src/app/pages/exercises/exercise/shared/exercise-map/feature-managers/transfer-point-feature-manager.ts index 3682c6c10..1122425fe 100644 --- a/frontend/src/app/pages/exercises/exercise/shared/exercise-map/feature-managers/transfer-point-feature-manager.ts +++ b/frontend/src/app/pages/exercises/exercise/shared/exercise-map/feature-managers/transfer-point-feature-manager.ts @@ -19,7 +19,6 @@ import { NameStyleHelper } from '../utility/style-helper/name-style-helper'; import { createPoint, ElementFeatureManager } from './element-feature-manager'; export class TransferPointFeatureManager extends ElementFeatureManager { - readonly type = 'transferPoints'; private readonly popupHelper = new ImagePopupHelper(this.olMap, this.layer); constructor( @@ -61,7 +60,8 @@ export class TransferPointFeatureManager extends ElementFeatureManager ({ - name: this.getElementFromFeature(feature)!.value.internalName, + name: (this.getElementFromFeature(feature) as TransferPoint) + .internalName, offsetY: 0, }), 0.2, @@ -75,14 +75,15 @@ export class TransferPointFeatureManager extends ElementFeatureManager fix getElementFromFeature typings const droppedElement = this.getElementFromFeature(droppedFeature); - const droppedOnTransferPoint: TransferPoint = - this.getElementFromFeature(droppedOnFeature)!.value!; + const droppedOnTransferPoint = this.getElementFromFeature( + droppedOnFeature + ) as TransferPoint; if (!droppedElement || !droppedOnTransferPoint) { console.error('Could not find element for the features'); return false; } if ( - droppedElement.type !== 'vehicles' && + droppedElement.type !== 'vehicle' && droppedElement.type !== 'personnel' ) { return false; @@ -106,7 +107,7 @@ export class TransferPointFeatureManager extends ElementFeatureManager > { - readonly type = 'vehicles'; - private readonly imageStyleHelper = new ImageStyleHelper( - (feature) => this.getElementFromFeature(feature)!.value.image + (feature) => (this.getElementFromFeature(feature) as Vehicle).image ); private readonly nameStyleHelper = new NameStyleHelper( (feature) => { - const vehicle = this.getElementFromFeature(feature)!.value; + const vehicle = this.getElementFromFeature(feature) as Vehicle; return { name: vehicle.name, offsetY: vehicle.image.height / 2 / normalZoom, @@ -66,29 +64,26 @@ export class VehicleFeatureManager extends ElementFeatureManager< const droppedElement = this.getElementFromFeature(droppedFeature); const droppedOnVehicle = this.getElementFromFeature( droppedOnFeature - ) as { - type: 'vehicles'; - value: Vehicle; - }; + ) as Vehicle; if (!droppedElement || !droppedOnVehicle) { console.error('Could not find element for the features'); return false; } if ( (droppedElement.type === 'personnel' && - droppedOnVehicle.value.personnelIds[droppedElement.value.id]) || - (droppedElement.type === 'materials' && - droppedOnVehicle.value.materialIds[droppedElement.value.id]) || - (droppedElement.type === 'patients' && - Object.keys(droppedOnVehicle.value.patientIds).length < - droppedOnVehicle.value.patientCapacity) + droppedOnVehicle.personnelIds[droppedElement.id]) || + (droppedElement.type === 'material' && + droppedOnVehicle.materialIds[droppedElement.id]) || + (droppedElement.type === 'patient' && + Object.keys(droppedOnVehicle.patientIds).length < + droppedOnVehicle.patientCapacity) ) { // TODO: user feedback (e.g. toast) this.exerciseService.proposeAction( { type: '[Vehicle] Load vehicle', - vehicleId: droppedOnVehicle.value.id, - elementToBeLoadedId: droppedElement.value.id, + vehicleId: droppedOnVehicle.id, + elementToBeLoadedId: droppedElement.id, elementToBeLoadedType: droppedElement.type, }, true diff --git a/frontend/src/app/pages/exercises/exercise/shared/exercise-map/feature-managers/viewport-feature-manager.ts b/frontend/src/app/pages/exercises/exercise/shared/exercise-map/feature-managers/viewport-feature-manager.ts index 5fa0b86d0..efb4d6cd4 100644 --- a/frontend/src/app/pages/exercises/exercise/shared/exercise-map/feature-managers/viewport-feature-manager.ts +++ b/frontend/src/app/pages/exercises/exercise/shared/exercise-map/feature-managers/viewport-feature-manager.ts @@ -37,8 +37,6 @@ export class ViewportFeatureManager extends ElementFeatureManager implements FeatureManager> { - readonly type = 'viewports'; - override unsupportedChangeProperties = new Set(['id'] as const); constructor( @@ -74,8 +72,9 @@ export class ViewportFeatureManager ResizeRectangleInteraction.onResize( feature, ({ topLeftCoordinate, scale }) => { - const currentElement = this.getElementFromFeature(feature)! - .value as Viewport; + const currentElement = this.getElementFromFeature( + feature + ) as Viewport; this.exerciseService.proposeAction( { type: '[Viewport] Resize viewport', diff --git a/shared/src/store/action-reducers/vehicle.ts b/shared/src/store/action-reducers/vehicle.ts index 5d9778d05..6a0a1dfc8 100644 --- a/shared/src/store/action-reducers/vehicle.ts +++ b/shared/src/store/action-reducers/vehicle.ts @@ -105,13 +105,13 @@ export class LoadVehicleAction implements Action { public readonly vehicleId!: UUID; @IsLiteralUnion({ - materials: true, - patients: true, + material: true, + patient: true, personnel: true, }) public readonly elementToBeLoadedType!: - | 'materials' - | 'patients' + | 'material' + | 'patient' | 'personnel'; @IsUUID(4, uuidValidationOptions) @@ -277,7 +277,7 @@ export namespace VehicleActionReducers { ) => { const vehicle = getElement(draftState, 'vehicle', vehicleId); switch (elementToBeLoadedType) { - case 'materials': { + case 'material': { const material = getElement( draftState, 'material', @@ -314,7 +314,7 @@ export namespace VehicleActionReducers { ); break; } - case 'patients': { + case 'patient': { const patient = getElement( draftState, 'patient', From e577366585d13aef94015820b778fa51ce62998b Mon Sep 17 00:00:00 2001 From: Lukas Radermacher <49586507+lukasrad02@users.noreply.github.com> Date: Wed, 25 Jan 2023 12:15:53 +0100 Subject: [PATCH 05/18] Fix bug after merge --- shared/src/store/action-reducers/vehicle.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shared/src/store/action-reducers/vehicle.ts b/shared/src/store/action-reducers/vehicle.ts index 7d9c2e1a0..227586a35 100644 --- a/shared/src/store/action-reducers/vehicle.ts +++ b/shared/src/store/action-reducers/vehicle.ts @@ -361,7 +361,7 @@ export namespace VehicleActionReducers { Object.keys(vehicle.materialIds).forEach((materialId) => { getElement( draftState, - 'materials', + 'material', materialId ).metaPosition = { type: 'vehicle', From 9a519a40df7d7167b48e2945532d4e619c1781a1 Mon Sep 17 00:00:00 2001 From: Lukas Radermacher <49586507+lukasrad02@users.noreply.github.com> Date: Wed, 25 Jan 2023 13:40:07 +0100 Subject: [PATCH 06/18] Rename selector map to plural map and add validation --- shared/src/models/element.ts | 26 +++++++++++++++++++ shared/src/state.ts | 4 +-- shared/src/store/action-reducers/exercise.ts | 13 ++++++---- .../utils/calculate-treatments.ts | 4 +-- .../action-reducers/utils/get-element.ts | 12 ++++----- .../action-reducers/utils/spatial-elements.ts | 16 ++++++------ shared/src/utils/type-state-selector-map.ts | 15 ++++++++--- 7 files changed, 64 insertions(+), 26 deletions(-) create mode 100644 shared/src/models/element.ts diff --git a/shared/src/models/element.ts b/shared/src/models/element.ts new file mode 100644 index 000000000..7458f0e68 --- /dev/null +++ b/shared/src/models/element.ts @@ -0,0 +1,26 @@ +import type { AlarmGroup } from './alarm-group'; +import type { Client } from './client'; +import type { Hospital } from './hospital'; +import type { MapImage } from './map-image'; +import type { + Material, + Patient, + Personnel, + SimulatedRegion, + TransferPoint, + Vehicle, + Viewport, +} from '.'; + +export type Element = + | AlarmGroup + | Client + | Hospital + | MapImage + | Material + | Patient + | Personnel + | SimulatedRegion + | TransferPoint + | Vehicle + | Viewport; diff --git a/shared/src/state.ts b/shared/src/state.ts index 481294460..3601e4401 100644 --- a/shared/src/state.ts +++ b/shared/src/state.ts @@ -44,7 +44,7 @@ import { SpatialTree, } from './models/utils'; import type { MaterialType } from './models/utils/material-type'; -import type { SpatialElementSelector } from './store/action-reducers/utils/spatial-elements'; +import type { SpatialElementPlural } from './store/action-reducers/utils/spatial-elements'; import type { UUID } from './utils'; import { uuid, uuidValidationOptions } from './utils'; import { IsIdMap, IsLiteralUnion } from './utils/validators'; @@ -123,7 +123,7 @@ export class ExerciseState { // Mutable` could still have immutable objects in spatialTree @IsObject() public readonly spatialTrees: { - [type in SpatialElementSelector]: SpatialTree; + [type in SpatialElementPlural]: SpatialTree; } = { materials: SpatialTree.create(), patients: SpatialTree.create(), diff --git a/shared/src/store/action-reducers/exercise.ts b/shared/src/store/action-reducers/exercise.ts index 123cd68b8..c3ac6b555 100644 --- a/shared/src/store/action-reducers/exercise.ts +++ b/shared/src/store/action-reducers/exercise.ts @@ -11,8 +11,8 @@ import { Patient } from '../../models'; import { getStatus } from '../../models/utils'; import type { ExerciseState } from '../../state'; import type { Mutable } from '../../utils'; -import type { TypeSelectorMap } from '../../utils/type-state-selector-map'; -import { typeSelectorMap } from '../../utils/type-state-selector-map'; +import type { ElementTypePluralMap } from '../../utils/type-state-selector-map'; +import { elementTypePluralMap } from '../../utils/type-state-selector-map'; import { IsValue } from '../../utils/validators'; import type { Action, ActionReducer } from '../action-reducer'; import { ReducerError } from '../reducer-error'; @@ -132,14 +132,17 @@ export namespace ExerciseActionReducers { }; } -type TransferTypeSelectorMap = Pick; +type TransferTypePluralMap = Pick< + ElementTypePluralMap, + 'personnel' | 'vehicle' +>; function refreshTransfer( draftState: Mutable, - type: keyof TransferTypeSelectorMap, + type: keyof TransferTypePluralMap, tickInterval: number ): void { - const elements = draftState[typeSelectorMap[type]]; + const elements = draftState[elementTypePluralMap[type]]; Object.values(elements).forEach((element: Mutable) => { if (!element.transfer) { return; diff --git a/shared/src/store/action-reducers/utils/calculate-treatments.ts b/shared/src/store/action-reducers/utils/calculate-treatments.ts index 23ebff3b2..ce2921232 100644 --- a/shared/src/store/action-reducers/utils/calculate-treatments.ts +++ b/shared/src/store/action-reducers/utils/calculate-treatments.ts @@ -6,7 +6,7 @@ import { SpatialTree } from '../../../models/utils/spatial-tree'; import type { ExerciseState } from '../../../state'; import { maxTreatmentRange } from '../../../state-helpers/max-treatment-range'; import type { Mutable, UUID } from '../../../utils'; -import { typeSelectorMap } from '../../../utils/type-state-selector-map'; +import { elementTypePluralMap } from '../../../utils/type-state-selector-map'; import { getElement } from './get-element'; // TODO: `caterFor` and `treat` are currently used as synonyms without a clear distinction. @@ -104,7 +104,7 @@ function updateCateringAroundPatient( elementIdsToBeSkipped: Set ) { const elementsInTreatmentRange = SpatialTree.findAllElementsInCircle( - state.spatialTrees[typeSelectorMap[elementType]], + state.spatialTrees[elementTypePluralMap[elementType]], position, maxTreatmentRange ).filter((elementId) => !elementIdsToBeSkipped.has(elementId)); diff --git a/shared/src/store/action-reducers/utils/get-element.ts b/shared/src/store/action-reducers/utils/get-element.ts index 4ed737ed1..8479887be 100644 --- a/shared/src/store/action-reducers/utils/get-element.ts +++ b/shared/src/store/action-reducers/utils/get-element.ts @@ -1,7 +1,7 @@ import type { ExerciseState } from '../../../state'; import type { Mutable, UUID } from '../../../utils'; -import type { TypeSelectorMap } from '../../../utils/type-state-selector-map'; -import { typeSelectorMap } from '../../../utils/type-state-selector-map'; +import type { ElementTypePluralMap } from '../../../utils/type-state-selector-map'; +import { elementTypePluralMap } from '../../../utils/type-state-selector-map'; import { ReducerError } from '../../reducer-error'; /** @@ -9,16 +9,16 @@ import { ReducerError } from '../../reducer-error'; * @throws ReducerError if the element does not exist */ export function getElement< - ElementType extends keyof TypeSelectorMap, + ElementType extends keyof ElementTypePluralMap, State extends ExerciseState | Mutable >( state: State, elementType: ElementType, elementId: UUID -): State[TypeSelectorMap[ElementType]][UUID] { - const element = state[typeSelectorMap[elementType]][ +): State[ElementTypePluralMap[ElementType]][UUID] { + const element = state[elementTypePluralMap[elementType]][ elementId - ] as State[TypeSelectorMap[ElementType]][UUID]; + ] as State[ElementTypePluralMap[ElementType]][UUID]; if (!element) { throw new ReducerError( `Element of type ${elementType} with id ${elementId} does not exist` diff --git a/shared/src/store/action-reducers/utils/spatial-elements.ts b/shared/src/store/action-reducers/utils/spatial-elements.ts index f2d4f59bf..e129ca5ae 100644 --- a/shared/src/store/action-reducers/utils/spatial-elements.ts +++ b/shared/src/store/action-reducers/utils/spatial-elements.ts @@ -3,8 +3,8 @@ import { SpatialTree } from '../../../models/utils/spatial-tree'; import type { ExerciseState } from '../../../state'; import type { Mutable, UUID } from '../../../utils'; import { cloneDeepMutable } from '../../../utils'; -import type { TypeSelectorMap } from '../../../utils/type-state-selector-map'; -import { typeSelectorMap } from '../../../utils/type-state-selector-map'; +import type { ElementTypePluralMap } from '../../../utils/type-state-selector-map'; +import { elementTypePluralMap } from '../../../utils/type-state-selector-map'; import { updateTreatments } from './calculate-treatments'; import { getElement } from './get-element'; @@ -14,8 +14,8 @@ import { getElement } from './get-element'; * In addition, the respective functions must be called when an element gets added or removed. */ type SpatialElementType = 'material' | 'patient' | 'personnel'; -type SpatialTypeSelectorMap = Pick; -export type SpatialElementSelector = SpatialTypeSelectorMap[SpatialElementType]; +type SpatialTypePluralMap = Pick; +export type SpatialElementPlural = SpatialTypePluralMap[SpatialElementType]; /** * Adds an element with a position and executes side effects to guarantee the consistency of the state. @@ -31,7 +31,7 @@ export function addElementPosition( return; } SpatialTree.addElement( - state.spatialTrees[typeSelectorMap[elementType]], + state.spatialTrees[elementTypePluralMap[elementType]], element.id, element.position ); @@ -51,14 +51,14 @@ export function updateElementPosition( const startPosition = element.position; if (startPosition !== undefined) { SpatialTree.moveElement( - state.spatialTrees[typeSelectorMap[elementType]], + state.spatialTrees[elementTypePluralMap[elementType]], element.id, startPosition, targetPosition ); } else { SpatialTree.addElement( - state.spatialTrees[typeSelectorMap[elementType]], + state.spatialTrees[elementTypePluralMap[elementType]], element.id, targetPosition ); @@ -85,7 +85,7 @@ export function removeElementPosition( return; } SpatialTree.removeElement( - state.spatialTrees[typeSelectorMap[elementType]], + state.spatialTrees[elementTypePluralMap[elementType]], element.id, element.position ); diff --git a/shared/src/utils/type-state-selector-map.ts b/shared/src/utils/type-state-selector-map.ts index 09ab1ac85..443ffbb3c 100644 --- a/shared/src/utils/type-state-selector-map.ts +++ b/shared/src/utils/type-state-selector-map.ts @@ -1,4 +1,10 @@ -export const typeSelectorMap = { +// eslint-disable-next-line @typescript-eslint/no-shadow +import type { Element } from '../models/element'; +import type { ExerciseState } from '../state'; + +type ElementType = Element['type']; + +export const elementTypePluralMap = { alarmGroup: 'alarmGroups', client: 'clients', hospital: 'hospitals', @@ -10,6 +16,9 @@ export const typeSelectorMap = { transferPoint: 'transferPoints', vehicle: 'vehicles', viewport: 'viewports', -} as const; -export type TypeSelectorMap = typeof typeSelectorMap; + // Typescript does not allow literal types for indexes + // eslint-disable-next-line @typescript-eslint/consistent-indexed-object-style +} as const satisfies Record; + +export type ElementTypePluralMap = typeof elementTypePluralMap; From 8783aac7041617dae8ed43a7913fa79660be54e1 Mon Sep 17 00:00:00 2001 From: Lukas Radermacher <49586507+lukasrad02@users.noreply.github.com> Date: Wed, 25 Jan 2023 14:15:11 +0100 Subject: [PATCH 07/18] Simplify featureKeys --- .../exercise-map/feature-managers/element-manager.ts | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/frontend/src/app/pages/exercises/exercise/shared/exercise-map/feature-managers/element-manager.ts b/frontend/src/app/pages/exercises/exercise/shared/exercise-map/feature-managers/element-manager.ts index 9e2d5cd07..664e37d3c 100644 --- a/frontend/src/app/pages/exercises/exercise/shared/exercise-map/feature-managers/element-manager.ts +++ b/frontend/src/app/pages/exercises/exercise/shared/exercise-map/feature-managers/element-manager.ts @@ -24,7 +24,7 @@ export abstract class ElementManager< */ public onElementCreated(element: Element) { const feature = this.createFeature(element); - feature.set(featureKeys.value, element); + feature.set(featureElementKey, element); } /** @@ -62,7 +62,7 @@ export abstract class ElementManager< this.onElementCreated(newElement); return; } - elementFeature.set(featureKeys.value, newElement); + elementFeature.set(featureElementKey, newElement); this.changeFeature( oldElement, newElement, @@ -110,7 +110,7 @@ export abstract class ElementManager< ): ElementFeature | undefined; public getElementFromFeature(feature: Feature) { - return feature.get(featureKeys.value); + return feature.get(featureElementKey); } private areAllPropertiesSupported( @@ -128,6 +128,4 @@ export abstract class ElementManager< /** * The keys of the feature, where the type and most recent value of the respective element are saved to */ -const featureKeys = { - value: 'elementValue', -}; +const featureElementKey = 'element'; From 35519dcaea9ea47037617adac992aa2fb37aa1f9 Mon Sep 17 00:00:00 2001 From: Lukas Radermacher <49586507+lukasrad02@users.noreply.github.com> Date: Wed, 25 Jan 2023 16:08:59 +0100 Subject: [PATCH 08/18] Fix tests by adding type property to demo objects --- backend/src/fuesim-server.spec.ts | 1 + .../utils/calculate-treatments.spec.ts | 12 ++++++------ shared/src/store/reduce-exercise-state.spec.ts | 1 + shared/src/store/validate-exercise-action.spec.ts | 2 ++ 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/backend/src/fuesim-server.spec.ts b/backend/src/fuesim-server.spec.ts index 0d4cd3528..ae28339a5 100644 --- a/backend/src/fuesim-server.spec.ts +++ b/backend/src/fuesim-server.spec.ts @@ -38,6 +38,7 @@ describe('Exercise saving', () => { alarmGroup: { alarmGroupVehicles: {}, id: uuid(), + type: 'alarmGroup', name: 'Alarm Group', }, }, diff --git a/shared/src/store/action-reducers/utils/calculate-treatments.spec.ts b/shared/src/store/action-reducers/utils/calculate-treatments.spec.ts index e811becfc..ca0fde55a 100644 --- a/shared/src/store/action-reducers/utils/calculate-treatments.spec.ts +++ b/shared/src/store/action-reducers/utils/calculate-treatments.spec.ts @@ -22,7 +22,7 @@ interface Catering { * The id of the material or personnel catering */ catererId: UUID; - catererType: 'materials' | 'personnel'; + catererType: 'material' | 'personnel'; /** * All patients treated by {@link catererId} */ @@ -43,11 +43,11 @@ function assertCatering( for (const catering of caterings) { // Update all the patients const patients = catering.patientIds.map((patientId) => - getElement(draftState, 'patients', patientId) + getElement(draftState, 'patient', patientId) ); for (const patient of patients) { patient[ - catering.catererType === 'materials' + catering.catererType === 'material' ? 'assignedMaterialIds' : 'assignedPersonnelIds' ][catering.catererId] = true; @@ -288,7 +288,7 @@ describe('calculate treatment', () => { assertCatering(beforeState, newState, [ { catererId: ids.material, - catererType: 'materials', + catererType: 'material', patientIds: [ids.greenPatient], }, ]); @@ -324,7 +324,7 @@ describe('calculate treatment', () => { assertCatering(beforeState, newState, [ { catererId: ids.material, - catererType: 'materials', + catererType: 'material', patientIds: [ids.redPatient], }, ]); @@ -392,7 +392,7 @@ describe('calculate treatment', () => { assertCatering(beforeState, newState, [ { catererId: ids.material, - catererType: 'materials', + catererType: 'material', patientIds: [ids.redPatient, ids.greenPatient], }, ]); diff --git a/shared/src/store/reduce-exercise-state.spec.ts b/shared/src/store/reduce-exercise-state.spec.ts index 49afadc2d..ac72e6cdd 100644 --- a/shared/src/store/reduce-exercise-state.spec.ts +++ b/shared/src/store/reduce-exercise-state.spec.ts @@ -12,6 +12,7 @@ describe('exerciseReducer', () => { function generateViewport(): Viewport { return { id: uuid(), + type: 'viewport', name: 'Test', size: { width: 100, height: 100 }, position: { x: 0, y: 0 }, diff --git a/shared/src/store/validate-exercise-action.spec.ts b/shared/src/store/validate-exercise-action.spec.ts index 9bc74a97d..da9aab590 100644 --- a/shared/src/store/validate-exercise-action.spec.ts +++ b/shared/src/store/validate-exercise-action.spec.ts @@ -71,6 +71,7 @@ describe('validateExerciseAction', () => { type: '[Viewport] Add viewport', viewport: { id: 'b02c7756-ea52-427f-9fc3-0e163799544d', + type: 'viewport', name: '', size: { height: 1, @@ -112,6 +113,7 @@ describe('validateExerciseAction', () => { type: '[Viewport] Add viewport', viewport: { id: 'b02c7756-ea52-427f-9fc3-0e163799544d', + type: 'viewport', name: '', size: { height: 1, From 7375f1015991575a0a162e658cbaa37a7f5d77b9 Mon Sep 17 00:00:00 2001 From: Lukas Radermacher <49586507+lukasrad02@users.noreply.github.com> Date: Wed, 25 Jan 2023 18:29:07 +0100 Subject: [PATCH 09/18] WIP: Add migration --- .../state-migrations/17-add-type-property.ts | 297 ++++++++++++++++++ .../state-migrations/migration-functions.ts | 2 + shared/src/state.ts | 2 +- 3 files changed, 300 insertions(+), 1 deletion(-) create mode 100644 shared/src/state-migrations/17-add-type-property.ts diff --git a/shared/src/state-migrations/17-add-type-property.ts b/shared/src/state-migrations/17-add-type-property.ts new file mode 100644 index 000000000..76a61f972 --- /dev/null +++ b/shared/src/state-migrations/17-add-type-property.ts @@ -0,0 +1,297 @@ +import type { UUID } from '../utils'; +import type { Migration } from './migration-functions'; + +export const addTypeProperty17: Migration = { + actions: (_initialState, actions) => { + actions.forEach((action) => { + const actionType = (action as { type: string } | null)?.type; + + if (actionType === '[AlarmGroup] Add AlarmGroup') { + const typedAction = action as { + alarmGroup: { + type: 'alarmGroup'; + }; + }; + + typedAction.alarmGroup.type = 'alarmGroup'; + } + + if (actionType === '[Client] Add client') { + const typedAction = action as { + client: { + type: 'client'; + }; + }; + + typedAction.client.type = 'client'; + } + + if (actionType === '[Hospital] Add hospital') { + const typedAction = action as { + hospital: { + type: 'hospital'; + }; + }; + + typedAction.hospital.type = 'hospital'; + } + + if (actionType === '[MapImageTemplate] Add mapImageTemplate') { + const typedAction = action as { + mapImageTemplate: { + type: 'mapImageTemplate'; + }; + }; + + typedAction.mapImageTemplate.type = 'mapImageTemplate'; + } + + if (actionType === '[MapImage] Add MapImage') { + const typedAction = action as { + mapImage: { + type: 'mapImage'; + }; + }; + + typedAction.mapImage.type = 'mapImage'; + } + + if (actionType === '[Patient] Add patient') { + const typedAction = action as { + patient: { + type: 'patient'; + healthStates: { + [key: UUID]: { type: 'patientHealthState' }; + }; + }; + }; + + typedAction.patient.type = 'patient'; + Object.values(typedAction.patient.healthStates).forEach( + (healthState) => { + healthState.type = 'patientHealthState'; + } + ); + } + + if (actionType === '[SimulatedRegion] Add simulated region') { + const typedAction = action as { + simulatedRegion: { + type: 'simulatedRegion'; + }; + }; + + typedAction.simulatedRegion.type = 'simulatedRegion'; + } + + if (actionType === '[TransferPoint] Add TransferPoint') { + const typedAction = action as { + transferPoint: { + type: 'transferPoint'; + }; + }; + + typedAction.transferPoint.type = 'transferPoint'; + } + + if (actionType === '[Vehicle] Add vehicle') { + const typedAction = action as { + vehicle: { + type: 'vehicle'; + }; + materials: { type: 'material' }[]; + personnel: { type: 'personnel' }[]; + }; + + typedAction.vehicle.type = 'vehicle'; + typedAction.materials.forEach((material) => { + material.type = 'material'; + }); + typedAction.personnel.forEach((personnel) => { + personnel.type = 'personnel'; + }); + } + + if (actionType === '[Viewport] Add viewport') { + const typedAction = action as { + viewport: { + type: 'viewport'; + }; + }; + + typedAction.viewport.type = 'viewport'; + } + }); + }, + state: (state) => { + const typedState = state as { + alarmGroups: { + [key: UUID]: { + type: 'alarmGroup'; + }; + }; + clients: { + [key: UUID]: { + type: 'client'; + }; + }; + eocLog: { type: 'logEntry' }[]; + configuration: { type: 'exerciseConfiguration' }; + hospitalPatients: { + [key: UUID]: { + type: 'hospitalPatient'; + }; + }; + hospitals: { + [key: UUID]: { + type: 'hospital'; + }; + }; + mapImageTemplates: { type: 'mapImageTemplate' }[]; + mapImages: { + [key: UUID]: { + type: 'mapImage'; + }; + }; + materialTemplates: { type: 'materialTemplate' }[]; + materials: { + [key: UUID]: { + type: 'material'; + }; + }; + patientCategories: { + type: 'patientCategory'; + patientTemplates: { type: 'patientTemplate' }[]; + }[]; + patients: { + [key: UUID]: { + type: 'patient'; + healthStates: { + [key: UUID]: { type: 'patientHealthState' }; + }; + }; + }; + personnelTemplates: { type: 'personnelTemplate' }[]; + personnel: { + [key: UUID]: { + type: 'personnel'; + }; + }; + simulatedRegions: { + [key: UUID]: { + type: 'simulatedRegion'; + }; + }; + transferPoints: { + [key: UUID]: { + type: 'transferPoint'; + }; + }; + vehicleTemplates: { type: 'vehicleTemplate' }[]; + vehicles: { + [key: UUID]: { + type: 'vehicle'; + }; + }; + viewports: { + [key: UUID]: { + type: 'viewport'; + }; + }; + }; + + Object.values(typedState.alarmGroups).forEach((alarmGroup) => { + alarmGroup.type = 'alarmGroup'; + }); + + Object.values(typedState.clients).forEach((client) => { + client.type = 'client'; + }); + + Object.values(typedState.eocLog).forEach((logEntry) => { + logEntry.type = 'logEntry'; + }); + + typedState.configuration.type = 'exerciseConfiguration'; + + Object.values(typedState.hospitalPatients).forEach( + (hospitalPatient) => { + hospitalPatient.type = 'hospitalPatient'; + } + ); + + Object.values(typedState.hospitals).forEach((hospital) => { + hospital.type = 'hospital'; + }); + + Object.values(typedState.mapImageTemplates).forEach( + (mapImageTemplate) => { + mapImageTemplate.type = 'mapImageTemplate'; + } + ); + + Object.values(typedState.mapImages).forEach((mapImage) => { + mapImage.type = 'mapImage'; + }); + + Object.values(typedState.materialTemplates).forEach( + (materialTemplate) => { + materialTemplate.type = 'materialTemplate'; + } + ); + + Object.values(typedState.materials).forEach((material) => { + material.type = 'material'; + }); + + Object.values(typedState.patientCategories).forEach( + (patientCategory) => { + patientCategory.type = 'patientCategory'; + patientCategory.patientTemplates.forEach((patientTemplate) => { + patientTemplate.type = 'patientTemplate'; + }); + } + ); + + Object.values(typedState.patients).forEach((patient) => { + patient.type = 'patient'; + Object.values(patient.healthStates).forEach((healthState) => { + healthState.type = 'patientHealthState'; + }); + }); + + Object.values(typedState.personnelTemplates).forEach( + (personnelTemplates) => { + personnelTemplates.type = 'personnelTemplate'; + } + ); + + Object.values(typedState.personnel).forEach((personnel) => { + personnel.type = 'personnel'; + }); + + Object.values(typedState.simulatedRegions).forEach( + (simulatedRegion) => { + simulatedRegion.type = 'simulatedRegion'; + } + ); + + Object.values(typedState.transferPoints).forEach((transferPoint) => { + transferPoint.type = 'transferPoint'; + }); + + Object.values(typedState.vehicleTemplates).forEach( + (vehicleTemplate) => { + vehicleTemplate.type = 'vehicleTemplate'; + } + ); + + Object.values(typedState.vehicles).forEach((vehicle) => { + vehicle.type = 'vehicle'; + }); + + Object.values(typedState.viewports).forEach((viewport) => { + viewport.type = 'viewport'; + }); + }, +}; diff --git a/shared/src/state-migrations/migration-functions.ts b/shared/src/state-migrations/migration-functions.ts index 8bbed2f7b..965436bb5 100644 --- a/shared/src/state-migrations/migration-functions.ts +++ b/shared/src/state-migrations/migration-functions.ts @@ -5,6 +5,7 @@ import { addMapImageZIndex13 } from './13-add-map-image-zindex'; import { addPersonnelAndMaterialToState14 } from './14-add-personnel-and-material-templates-to-state'; import { addSimulatedRegions15 } from './15-add-simulated-regions'; import { addMetaPosition16 } from './16-add-meta-position'; +import { addTypeProperty17 } from './17-add-type-property'; import { updateEocLog3 } from './3-update-eoc-log'; import { removeSetParticipantIdAction4 } from './4-remove-set-participant-id-action'; import { removeStatistics5 } from './5-remove-statistics'; @@ -55,4 +56,5 @@ export const migrations: { 14: addPersonnelAndMaterialToState14, 15: addSimulatedRegions15, 16: addMetaPosition16, + 17: addTypeProperty17, }; diff --git a/shared/src/state.ts b/shared/src/state.ts index 3601e4401..de96e2b71 100644 --- a/shared/src/state.ts +++ b/shared/src/state.ts @@ -148,5 +148,5 @@ export class ExerciseState { * * This number MUST be increased every time a change to any object (that is part of the state or the state itself) is made in a way that there may be states valid before that are no longer valid. */ - static readonly currentStateVersion = 16; + static readonly currentStateVersion = 17; } From 41e0654e03fb26ecc62c145c7232a71546ed02c9 Mon Sep 17 00:00:00 2001 From: Lukas Radermacher <49586507+lukasrad02@users.noreply.github.com> Date: Thu, 26 Jan 2023 09:35:28 +0100 Subject: [PATCH 10/18] Update shared/src/utils/type-state-selector-map.ts Co-authored-by: Julian Schmidt --- shared/src/utils/type-state-selector-map.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/shared/src/utils/type-state-selector-map.ts b/shared/src/utils/type-state-selector-map.ts index 443ffbb3c..4d28070b9 100644 --- a/shared/src/utils/type-state-selector-map.ts +++ b/shared/src/utils/type-state-selector-map.ts @@ -17,8 +17,6 @@ export const elementTypePluralMap = { vehicle: 'vehicles', viewport: 'viewports', - // Typescript does not allow literal types for indexes - // eslint-disable-next-line @typescript-eslint/consistent-indexed-object-style -} as const satisfies Record; +} as const satisfies { [Key in ElementType]: keyof ExerciseState}; export type ElementTypePluralMap = typeof elementTypePluralMap; From 06b3e37058f1d776b2dcb7c9a1849d25a9bea7ce Mon Sep 17 00:00:00 2001 From: Lukas Radermacher <49586507+lukasrad02@users.noreply.github.com> Date: Thu, 26 Jan 2023 09:40:37 +0100 Subject: [PATCH 11/18] Rename file to reflect variable name change --- shared/src/store/action-reducers/exercise.ts | 4 ++-- .../src/store/action-reducers/utils/calculate-treatments.ts | 2 +- shared/src/store/action-reducers/utils/get-element.ts | 4 ++-- shared/src/store/action-reducers/utils/spatial-elements.ts | 4 ++-- ...{type-state-selector-map.ts => element-type-plural-map.ts} | 0 5 files changed, 7 insertions(+), 7 deletions(-) rename shared/src/utils/{type-state-selector-map.ts => element-type-plural-map.ts} (100%) diff --git a/shared/src/store/action-reducers/exercise.ts b/shared/src/store/action-reducers/exercise.ts index c3ac6b555..2236e1297 100644 --- a/shared/src/store/action-reducers/exercise.ts +++ b/shared/src/store/action-reducers/exercise.ts @@ -11,8 +11,8 @@ import { Patient } from '../../models'; import { getStatus } from '../../models/utils'; import type { ExerciseState } from '../../state'; import type { Mutable } from '../../utils'; -import type { ElementTypePluralMap } from '../../utils/type-state-selector-map'; -import { elementTypePluralMap } from '../../utils/type-state-selector-map'; +import type { ElementTypePluralMap } from '../../utils/element-type-plural-map'; +import { elementTypePluralMap } from '../../utils/element-type-plural-map'; import { IsValue } from '../../utils/validators'; import type { Action, ActionReducer } from '../action-reducer'; import { ReducerError } from '../reducer-error'; diff --git a/shared/src/store/action-reducers/utils/calculate-treatments.ts b/shared/src/store/action-reducers/utils/calculate-treatments.ts index ce2921232..090729406 100644 --- a/shared/src/store/action-reducers/utils/calculate-treatments.ts +++ b/shared/src/store/action-reducers/utils/calculate-treatments.ts @@ -6,7 +6,7 @@ import { SpatialTree } from '../../../models/utils/spatial-tree'; import type { ExerciseState } from '../../../state'; import { maxTreatmentRange } from '../../../state-helpers/max-treatment-range'; import type { Mutable, UUID } from '../../../utils'; -import { elementTypePluralMap } from '../../../utils/type-state-selector-map'; +import { elementTypePluralMap } from '../../../utils/element-type-plural-map'; import { getElement } from './get-element'; // TODO: `caterFor` and `treat` are currently used as synonyms without a clear distinction. diff --git a/shared/src/store/action-reducers/utils/get-element.ts b/shared/src/store/action-reducers/utils/get-element.ts index 8479887be..653f6087c 100644 --- a/shared/src/store/action-reducers/utils/get-element.ts +++ b/shared/src/store/action-reducers/utils/get-element.ts @@ -1,7 +1,7 @@ import type { ExerciseState } from '../../../state'; import type { Mutable, UUID } from '../../../utils'; -import type { ElementTypePluralMap } from '../../../utils/type-state-selector-map'; -import { elementTypePluralMap } from '../../../utils/type-state-selector-map'; +import type { ElementTypePluralMap } from '../../../utils/element-type-plural-map'; +import { elementTypePluralMap } from '../../../utils/element-type-plural-map'; import { ReducerError } from '../../reducer-error'; /** diff --git a/shared/src/store/action-reducers/utils/spatial-elements.ts b/shared/src/store/action-reducers/utils/spatial-elements.ts index e129ca5ae..43fa7a604 100644 --- a/shared/src/store/action-reducers/utils/spatial-elements.ts +++ b/shared/src/store/action-reducers/utils/spatial-elements.ts @@ -3,8 +3,8 @@ import { SpatialTree } from '../../../models/utils/spatial-tree'; import type { ExerciseState } from '../../../state'; import type { Mutable, UUID } from '../../../utils'; import { cloneDeepMutable } from '../../../utils'; -import type { ElementTypePluralMap } from '../../../utils/type-state-selector-map'; -import { elementTypePluralMap } from '../../../utils/type-state-selector-map'; +import type { ElementTypePluralMap } from '../../../utils/element-type-plural-map'; +import { elementTypePluralMap } from '../../../utils/element-type-plural-map'; import { updateTreatments } from './calculate-treatments'; import { getElement } from './get-element'; diff --git a/shared/src/utils/type-state-selector-map.ts b/shared/src/utils/element-type-plural-map.ts similarity index 100% rename from shared/src/utils/type-state-selector-map.ts rename to shared/src/utils/element-type-plural-map.ts From cc7ad9a226d38c95cbe7fdd79f0b32712622c9cc Mon Sep 17 00:00:00 2001 From: Lukas Radermacher <49586507+lukasrad02@users.noreply.github.com> Date: Thu, 26 Jan 2023 10:21:26 +0100 Subject: [PATCH 12/18] Replace type literals by element property --- .../send-alarm-group-interface.component.ts | 2 +- shared/src/store/action-reducers/transfer-point.ts | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/frontend/src/app/pages/exercises/exercise/shared/emergency-operations-center/send-alarm-group-interface/send-alarm-group-interface.component.ts b/frontend/src/app/pages/exercises/exercise/shared/emergency-operations-center/send-alarm-group-interface/send-alarm-group-interface.component.ts index 9e7adba02..06755aa93 100644 --- a/frontend/src/app/pages/exercises/exercise/shared/emergency-operations-center/send-alarm-group-interface/send-alarm-group-interface.component.ts +++ b/frontend/src/app/pages/exercises/exercise/shared/emergency-operations-center/send-alarm-group-interface/send-alarm-group-interface.component.ts @@ -112,7 +112,7 @@ export class SendAlarmGroupInterfaceComponent implements OnDestroy { }), this.exerciseService.proposeAction({ type: '[Transfer] Add to transfer', - elementType: 'vehicle', + elementType: vehicleParameters.vehicle.type, elementId: vehicleParameters.vehicle.id, startPoint: AlarmGroupStartPoint.create( alarmGroup.name, diff --git a/shared/src/store/action-reducers/transfer-point.ts b/shared/src/store/action-reducers/transfer-point.ts index caad44b2c..f72d916c9 100644 --- a/shared/src/store/action-reducers/transfer-point.ts +++ b/shared/src/store/action-reducers/transfer-point.ts @@ -243,7 +243,7 @@ export namespace TransferPointActionReducers { vehicle.transfer?.targetTransferPointId === transferPointId ) { - letElementArrive(draftState, 'vehicle', vehicleId); + letElementArrive(draftState, vehicle.type, vehicleId); } } for (const personnelId of Object.keys(draftState.personnel)) { @@ -256,7 +256,11 @@ export namespace TransferPointActionReducers { personnel.transfer?.targetTransferPointId === transferPointId ) { - letElementArrive(draftState, 'personnel', personnelId); + letElementArrive( + draftState, + personnel.type, + personnelId + ); } } // TODO: If we can assume that the transfer points are always connected to each other, From e759288a1fec8a0b0a980603b6bed651b28af3fc Mon Sep 17 00:00:00 2001 From: Lukas Radermacher <49586507+lukasrad02@users.noreply.github.com> Date: Thu, 26 Jan 2023 10:23:22 +0100 Subject: [PATCH 13/18] Run prettier --- shared/src/utils/element-type-plural-map.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/shared/src/utils/element-type-plural-map.ts b/shared/src/utils/element-type-plural-map.ts index 4d28070b9..630dcd7ef 100644 --- a/shared/src/utils/element-type-plural-map.ts +++ b/shared/src/utils/element-type-plural-map.ts @@ -16,7 +16,6 @@ export const elementTypePluralMap = { transferPoint: 'transferPoints', vehicle: 'vehicles', viewport: 'viewports', - -} as const satisfies { [Key in ElementType]: keyof ExerciseState}; +} as const satisfies { [Key in ElementType]: keyof ExerciseState }; export type ElementTypePluralMap = typeof elementTypePluralMap; From 366268c98ebb2ca705569f7e3895a87fb03a22eb Mon Sep 17 00:00:00 2001 From: Lukas Radermacher <49586507+lukasrad02@users.noreply.github.com> Date: Thu, 26 Jan 2023 11:12:53 +0100 Subject: [PATCH 14/18] Finish migration --- .../state-migrations/17-add-type-property.ts | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/shared/src/state-migrations/17-add-type-property.ts b/shared/src/state-migrations/17-add-type-property.ts index 76a61f972..d56865040 100644 --- a/shared/src/state-migrations/17-add-type-property.ts +++ b/shared/src/state-migrations/17-add-type-property.ts @@ -84,6 +84,21 @@ export const addTypeProperty17: Migration = { typedAction.simulatedRegion.type = 'simulatedRegion'; } + if ( + actionType === '[Transfer] Add to transfer' || + actionType === '[Transfer] Edit transfer' || + actionType === '[Transfer] Finish transfer' || + actionType === '[Transfer] Toggle pause transfer' + ) { + const typedAction = action as { + elementType: 'personnel' | 'vehicle' | 'vehicles'; + }; + + if (typedAction.elementType === 'vehicles') { + typedAction.elementType = 'vehicle'; + } + } + if (actionType === '[TransferPoint] Add TransferPoint') { const typedAction = action as { transferPoint: { @@ -112,6 +127,23 @@ export const addTypeProperty17: Migration = { }); } + if (actionType === '[Vehicle] Load vehicle') { + const typedAction = action as { + elementToBeLoadedType: + | 'material' + | 'materials' + | 'patient' + | 'patients' + | 'personnel'; + }; + + if (typedAction.elementToBeLoadedType === 'materials') { + typedAction.elementToBeLoadedType = 'material'; + } else if (typedAction.elementToBeLoadedType === 'patients') { + typedAction.elementToBeLoadedType = 'patient'; + } + } + if (actionType === '[Viewport] Add viewport') { const typedAction = action as { viewport: { From 84b8ed727e274f8e4b26651a64f7c501395bca2e Mon Sep 17 00:00:00 2001 From: Lukas Radermacher <49586507+lukasrad02@users.noreply.github.com> Date: Thu, 26 Jan 2023 14:49:09 +0100 Subject: [PATCH 15/18] Fix migration to set correct type on EocLogEntry --- shared/src/state-migrations/17-add-type-property.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/shared/src/state-migrations/17-add-type-property.ts b/shared/src/state-migrations/17-add-type-property.ts index d56865040..afd36843f 100644 --- a/shared/src/state-migrations/17-add-type-property.ts +++ b/shared/src/state-migrations/17-add-type-property.ts @@ -167,7 +167,7 @@ export const addTypeProperty17: Migration = { type: 'client'; }; }; - eocLog: { type: 'logEntry' }[]; + eocLog: { type: 'eocLogEntry' }[]; configuration: { type: 'exerciseConfiguration' }; hospitalPatients: { [key: UUID]: { @@ -241,7 +241,7 @@ export const addTypeProperty17: Migration = { }); Object.values(typedState.eocLog).forEach((logEntry) => { - logEntry.type = 'logEntry'; + logEntry.type = 'eocLogEntry'; }); typedState.configuration.type = 'exerciseConfiguration'; From fc368d9bd5df3a4487424ae08d37fde955c42904 Mon Sep 17 00:00:00 2001 From: Lukas Radermacher <49586507+lukasrad02@users.noreply.github.com> Date: Thu, 26 Jan 2023 14:51:01 +0100 Subject: [PATCH 16/18] Make imports consistent --- shared/src/models/element.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/shared/src/models/element.ts b/shared/src/models/element.ts index 7458f0e68..9aa445366 100644 --- a/shared/src/models/element.ts +++ b/shared/src/models/element.ts @@ -1,8 +1,8 @@ -import type { AlarmGroup } from './alarm-group'; -import type { Client } from './client'; -import type { Hospital } from './hospital'; -import type { MapImage } from './map-image'; import type { + AlarmGroup, + Client, + Hospital, + MapImage, Material, Patient, Personnel, From 38e92cac3f86fb1d705c704fef130a67c8218010 Mon Sep 17 00:00:00 2001 From: Lukas Radermacher <49586507+lukasrad02@users.noreply.github.com> Date: Thu, 26 Jan 2023 14:54:53 +0100 Subject: [PATCH 17/18] Reuse literal union from transfer in exercise --- shared/src/store/action-reducers/exercise.ts | 4 ++-- shared/src/store/action-reducers/transfer.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/shared/src/store/action-reducers/exercise.ts b/shared/src/store/action-reducers/exercise.ts index 2236e1297..00eaf3a3f 100644 --- a/shared/src/store/action-reducers/exercise.ts +++ b/shared/src/store/action-reducers/exercise.ts @@ -16,7 +16,7 @@ import { elementTypePluralMap } from '../../utils/element-type-plural-map'; import { IsValue } from '../../utils/validators'; import type { Action, ActionReducer } from '../action-reducer'; import { ReducerError } from '../reducer-error'; -import { letElementArrive } from './transfer'; +import { letElementArrive, TransferableElementType } from './transfer'; import { updateTreatments } from './utils/calculate-treatments'; import { PatientUpdate } from './utils/patient-updates'; @@ -134,7 +134,7 @@ export namespace ExerciseActionReducers { type TransferTypePluralMap = Pick< ElementTypePluralMap, - 'personnel' | 'vehicle' + TransferableElementType >; function refreshTransfer( diff --git a/shared/src/store/action-reducers/transfer.ts b/shared/src/store/action-reducers/transfer.ts index b3ef2ed45..eb0b2f5f1 100644 --- a/shared/src/store/action-reducers/transfer.ts +++ b/shared/src/store/action-reducers/transfer.ts @@ -17,7 +17,7 @@ import { updateElementPosition, } from './utils/spatial-elements'; -type TransferableElementType = 'personnel' | 'vehicle'; +export type TransferableElementType = 'personnel' | 'vehicle'; const transferableElementTypeAllowedValues: AllowedValues = { personnel: true, vehicle: true }; From bab16eaea5a9f06104360397f0dc3655db8bd2e3 Mon Sep 17 00:00:00 2001 From: Lukas Radermacher <49586507+lukasrad02@users.noreply.github.com> Date: Thu, 26 Jan 2023 16:55:15 +0100 Subject: [PATCH 18/18] Fix linter --- .../exercise-map/feature-managers/moveable-feature-manager.ts | 1 - shared/src/store/action-reducers/exercise.ts | 3 ++- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/app/pages/exercises/exercise/shared/exercise-map/feature-managers/moveable-feature-manager.ts b/frontend/src/app/pages/exercises/exercise/shared/exercise-map/feature-managers/moveable-feature-manager.ts index 162f6a3f4..94034d561 100644 --- a/frontend/src/app/pages/exercises/exercise/shared/exercise-map/feature-managers/moveable-feature-manager.ts +++ b/frontend/src/app/pages/exercises/exercise/shared/exercise-map/feature-managers/moveable-feature-manager.ts @@ -1,4 +1,3 @@ -import type { ExerciseState } from 'digital-fuesim-manv-shared'; import type { MapBrowserEvent, Feature } from 'ol'; import type Point from 'ol/geom/Point'; import type { TranslateEvent } from 'ol/interaction/Translate'; diff --git a/shared/src/store/action-reducers/exercise.ts b/shared/src/store/action-reducers/exercise.ts index 00eaf3a3f..1fd29a514 100644 --- a/shared/src/store/action-reducers/exercise.ts +++ b/shared/src/store/action-reducers/exercise.ts @@ -16,7 +16,8 @@ import { elementTypePluralMap } from '../../utils/element-type-plural-map'; import { IsValue } from '../../utils/validators'; import type { Action, ActionReducer } from '../action-reducer'; import { ReducerError } from '../reducer-error'; -import { letElementArrive, TransferableElementType } from './transfer'; +import type { TransferableElementType } from './transfer'; +import { letElementArrive } from './transfer'; import { updateTreatments } from './utils/calculate-treatments'; import { PatientUpdate } from './utils/patient-updates';