Skip to content

Commit 11f8ae8

Browse files
committed
feat: add measure selectors and bar
Signed-off-by: Julius Makowski <[email protected]>
1 parent f74beb7 commit 11f8ae8

9 files changed

Lines changed: 133 additions & 5 deletions

File tree

frontend/src/app/pages/exercises/exercise/exercise/exercise.component.html

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -209,9 +209,7 @@ <h2 class="mb-0">
209209
@if (!ownClient.isInWaitingRoom) {
210210
@switch (ownClient.role.specificRole) {
211211
@case ('mapOperator') {
212-
<app-exercise-map
213-
class="h-100 rounded overflow-hidden"
214-
></app-exercise-map>
212+
<app-map-operator-map-editor />
215213
}
216214
@case ('trainer') {
217215
<app-trainer-map-editor></app-trainer-map-editor>

frontend/src/app/pages/exercises/exercise/exercise/exercise.component.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@ import {
3939
import { selectOwnClient } from '../../../../state/application/selectors/shared.selectors';
4040
import { selectStateSnapshot } from '../../../../state/get-state-snapshot';
4141
import { TimeTravelComponent } from '../shared/time-travel/time-travel.component';
42-
import { ExerciseMapComponent } from '../shared/exercise-map/exercise-map.component';
4342
import { TrainerMapEditorComponent } from '../shared/trainer-map-editor/trainer-map-editor.component';
4443
import { EmergencyOperationsCenterFullComponent } from '../shared/emergency-operations-center/emergency-operations-center-full/emergency-operations-center-full.component';
4544
import { FormatDurationPipe } from '../../../../shared/pipes/format-duration.pipe';
@@ -51,6 +50,7 @@ import {
5150
openParticipantsModal,
5251
openTrainersModal,
5352
} from '../shared/clients-modal/open-clients-modal';
53+
import { MapOperatorMapComponent } from '../shared/map-operator-map/map-operator-map.component';
5454

5555
@Component({
5656
selector: 'app-exercise',
@@ -66,13 +66,13 @@ import {
6666
NgbDropdownButtonItem,
6767
NgbDropdownItem,
6868
TimeTravelComponent,
69-
ExerciseMapComponent,
7069
TrainerMapEditorComponent,
7170
EmergencyOperationsCenterFullComponent,
7271
AsyncPipe,
7372
FormatDurationPipe,
7473
ParallelExerciseStatusBarComponent,
7574
CopyButtonComponent,
75+
MapOperatorMapComponent,
7676
],
7777
})
7878
export class ExerciseComponent implements OnDestroy {
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<div class="container-fluid position-relative d-flex flex-column h-100">
2+
<div class="flex-fill row">
3+
<app-exercise-map class="h-100 rounded overflow-hidden" />
4+
</div>
5+
6+
<div
7+
class="position-absolute start-50 translate-middle-x bottom-0 w-100 px-2 px-md-4 pb-2 d-flex flex-column align-items-center"
8+
>
9+
<button
10+
class="map-toolbar-toggle btn btn-light btn-sm shadow rounded-circle mb-2"
11+
(click)="toggleToolbar()"
12+
>
13+
{{ isToolbarVisible() ? '▼' : '▲' }}
14+
</button>
15+
16+
<div
17+
class="toolbar-animated-collapse w-100"
18+
[class.show]="isToolbarVisible()"
19+
>
20+
<app-map-operator-toolbar />
21+
</div>
22+
</div>
23+
</div>
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
.toolbar-animated-collapse {
2+
max-height: 0;
3+
opacity: 0;
4+
overflow: hidden;
5+
transform: translateY(0.5rem);
6+
transition:
7+
max-height 220ms ease,
8+
opacity 180ms ease,
9+
transform 220ms ease;
10+
}
11+
12+
.toolbar-animated-collapse.show {
13+
max-height: 24rem;
14+
opacity: 1;
15+
transform: translateY(0);
16+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { Component, HostListener, inject, signal } from '@angular/core';
2+
import { FormsModule } from '@angular/forms';
3+
import { DragElementService } from '../core/drag-element.service';
4+
import { TransferLinesService } from '../core/transfer-lines.service';
5+
import { ExerciseMapComponent } from '../exercise-map/exercise-map.component';
6+
import { MapOperatorToolbarComponent } from '../map-operator-toolbar/map-operator-toolbar.component';
7+
8+
@Component({
9+
selector: 'app-map-operator-map-editor',
10+
templateUrl: './map-operator-map.component.html',
11+
styleUrls: ['./map-operator-map.component.scss'],
12+
imports: [ExerciseMapComponent, FormsModule, MapOperatorToolbarComponent],
13+
})
14+
/**
15+
* A wrapper around the map that allows map operators to take measures.
16+
*/
17+
export class MapOperatorMapComponent {
18+
readonly dragElementService = inject(DragElementService);
19+
readonly transferLinesService = inject(TransferLinesService);
20+
21+
public readonly isToolbarVisible = signal(true);
22+
23+
public toggleToolbar() {
24+
this.isToolbarVisible.update((value) => !value);
25+
}
26+
27+
@HostListener('document:mousedown', ['$event'])
28+
public onDocumentMouseDown(event: MouseEvent) {
29+
const target = event.target as HTMLElement | null;
30+
if (!target) {
31+
return;
32+
}
33+
if (target.closest('app-map-operator-toolbar, .map-toolbar-toggle')) {
34+
return;
35+
}
36+
this.isToolbarVisible.set(false);
37+
}
38+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<div class="bg-body bg-opacity-50 rounded shadow p-2">
2+
<div class="row g-2">
3+
@for (measureTemplate of measureTemplates(); track measureTemplate.id) {
4+
<div class="col-6 col-sm-4 col-md-3 col-lg-2">
5+
<button
6+
class="btn p-0 border rounded bg-white overflow-hidden d-flex flex-column"
7+
(click)="onMeasureTemplateSelect(measureTemplate)"
8+
>
9+
<div class="ratio ratio-1x1 bg-body-tertiary">
10+
<div
11+
class="d-flex align-items-center justify-content-center p-2 h-100 w-100"
12+
>
13+
<img
14+
[src]="measureTemplate.image.url"
15+
[alt]="measureTemplate.name"
16+
class="img-fluid object-fit-contain"
17+
/>
18+
</div>
19+
</div>
20+
<div class="border-top p-2 small text-center text-truncate">
21+
{{ measureTemplate.name }}
22+
</div>
23+
</button>
24+
</div>
25+
}
26+
</div>
27+
</div>

frontend/src/app/pages/exercises/exercise/shared/map-operator-toolbar/map-operator-toolbar.component.scss

Whitespace-only changes.
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { Component, computed, inject } from '@angular/core';
2+
import { Store } from '@ngrx/store';
3+
import type { AppState } from '../../../../../state/app.state';
4+
import { selectMeasureTemplates } from '../../../../../state/application/selectors/exercise.selectors';
5+
6+
@Component({
7+
selector: 'app-map-operator-toolbar',
8+
templateUrl: './map-operator-toolbar.component.html',
9+
styleUrls: ['./map-operator-toolbar.component.scss'],
10+
})
11+
export class MapOperatorToolbarComponent {
12+
private readonly store = inject<Store<AppState>>(Store);
13+
14+
private readonly measureTemplatesMap = this.store.selectSignal(
15+
selectMeasureTemplates
16+
);
17+
public readonly measureTemplates = computed(() =>
18+
Object.values(this.measureTemplatesMap())
19+
);
20+
21+
public onMeasureTemplateSelect(template: any) {
22+
console.log('Selected measure template:', template);
23+
}
24+
}

frontend/src/app/state/application/selectors/exercise.selectors.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ export const selectVehicles = selectPropertyFactory('vehicles');
4040
export const selectPersonnel = selectPropertyFactory('personnel');
4141
export const selectAlarmGroups = selectPropertyFactory('alarmGroups');
4242
export const selectMaterials = selectPropertyFactory('materials');
43+
export const selectMeasures = selectPropertyFactory('measures');
4344
export const selectTransferPoints = selectPropertyFactory('transferPoints');
4445
export const selectHospitals = selectPropertyFactory('hospitals');
4546
export const selectHospitalPatients = selectPropertyFactory('hospitalPatients');
@@ -53,6 +54,7 @@ export const selectMaterialTemplates =
5354
selectPropertyFactory('materialTemplates');
5455
export const selectMapImagesTemplates =
5556
selectPropertyFactory('mapImageTemplates');
57+
export const selectMeasureTemplates = selectPropertyFactory('measureTemplates');
5658
// Array properties
5759
export const selectPatientCategories =
5860
selectPropertyFactory('patientCategories');

0 commit comments

Comments
 (0)