@3cr/viewer-browser 0.0.220 → 0.0.247

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/components.d.ts +6 -2
  2. package/dist/Viewer3CR.js +32 -32
  3. package/dist/Viewer3CR.mjs +17808 -14315
  4. package/dist/Viewer3CR.umd.js +32 -32
  5. package/index.html +1 -1
  6. package/package.json +4 -3
  7. package/playground/index.html +4 -12
  8. package/src/App.vue +29 -14
  9. package/src/__tests__/main.spec.ts +4 -4
  10. package/src/assets/styles.scss +6 -2
  11. package/src/components/demo/DemoPatientModal.vue +12 -22
  12. package/src/components/demo/licence/DemoLicenceInfoModal.vue +11 -29
  13. package/src/components/demo/options.ts +42 -39
  14. package/src/components/demo/patient/DemoPatientInfoModal.vue +11 -29
  15. package/src/components/modal/CloseViewerModal.vue +1 -1
  16. package/src/components/modal/MftpWebGL3DRModal.vue +133 -120
  17. package/src/components/modal/ViewerActionRail.vue +0 -6
  18. package/src/components/modal/ViewerNavigationDrawerContent.vue +25 -16
  19. package/src/components/modal/ViewerNavigationDrawerFooter.vue +32 -9
  20. package/src/components/modal/ViewerNavigationDrawerHeader.vue +6 -1
  21. package/src/components/modal/WebGL3DR.vue +7 -0
  22. package/src/components/modal/__tests__/ViewerNavigationDrawerHeader.spec.ts +3 -2
  23. package/src/components/modal/buttons/AutoAnnotateBtn.vue +181 -13
  24. package/src/components/modal/menus/FileMenu.vue +2 -9
  25. package/src/components/modal/menus/SettingsMenu.vue +2 -7
  26. package/src/components/navigation/mcad/McadGlobalActions.vue +20 -0
  27. package/src/components/navigation/mcad/McadGlobalOpacitySlider.vue +103 -0
  28. package/src/components/navigation/mcad/McadGlobalScanViewBtn.vue +28 -0
  29. package/src/components/navigation/mcad/McadGlobalVisibilityBtn.vue +38 -0
  30. package/src/components/shared/LoadingSpinner.vue +27 -36
  31. package/src/components/views/AnnotationTreeView.vue +3 -1
  32. package/src/components/views/MarkupTreeView.vue +3 -1
  33. package/src/components/views/McadObjectTreeView.vue +46 -36
  34. package/src/components/views/modals/DataOverlayGeneralModal.vue +71 -0
  35. package/src/components/views/modals/DataOverlayMarkupModal.vue +60 -0
  36. package/src/components/views/modals/DataOverlayModal.vue +54 -55
  37. package/src/components/views/shared/Opacity.vue +0 -1
  38. package/src/composables/useDebounce.ts +11 -0
  39. package/src/composables/useIntroJs.ts +23 -6
  40. package/src/composables/useViewerOptions.ts +1 -0
  41. package/src/functions/guards/isDataOverlayAngle.ts +6 -0
  42. package/src/functions/guards/isDataOverlayLength.ts +6 -0
  43. package/src/functions/guards/isDataOverlayPolygon.ts +6 -0
  44. package/src/functions/modelHelper.ts +2 -0
  45. package/src/functions/notification.ts +63 -29
  46. package/src/main.ts +23 -15
  47. package/src/models/loadViewerOptions.ts +39 -0
  48. package/src/models/loadViewerPayload.ts +0 -7
  49. package/src/services/viewer-3cr.service.ts +13 -1
  50. package/src/tools/data-overlay.tool.ts +71 -0
  51. package/src/types/data-overlay-event.ts +5 -0
  52. package/src/types/data-overlay-info.ts +4 -0
  53. package/src/types/demo-type.ts +4 -0
  54. package/src/components/modal/actions/HideViewAction.vue +0 -38
  55. package/src/components/views/modals/DataOverlayModalManager.vue +0 -104
  56. package/src/components/views/modals/__tests__/DataOverlayModal.spec.ts +0 -33
  57. package/src/components/views/modals/__tests__/DataOverlayModalManager.spec.ts +0 -93
@@ -1,41 +1,45 @@
1
1
  <template>
2
- <v-treeview class="py-0" :items="data" item-value="id" density="compact" open-strategy="single">
3
- <template #prepend="{ item }">
4
- <visibility-btn :is-visible="item.isVisible" @click.stop="toggleVisibility(item)" />
5
- </template>
6
- <template #title="{ item }">
7
- <div class="d-flex align-center pr-2">
8
- <object-label class="mr-1" :label="item.title">
9
- <template #prepend>
10
- <object-color class="ml-1 mr-2" :color="getColor(item)" />
2
+ <div>
3
+ <mcad-global-actions :items="items" />
4
+ <v-treeview class="py-0" :items="data" item-value="id" density="compact" open-strategy="single">
5
+ <template #prepend="{ item }">
6
+ <visibility-btn :is-visible="item.isVisible" @click.stop="toggleVisibility(item)" />
7
+ </template>
8
+ <template #title="{ item }">
9
+ <div class="d-flex align-center">
10
+ <object-label class="mr-1" :label="item.title">
11
+ <template #prepend>
12
+ <object-color class="ml-1 mr-2" :color="getColor(item)" />
13
+ </template>
14
+ </object-label>
15
+ <v-spacer />
16
+ <opacity
17
+ :model-value="getOpacity(item)"
18
+ max-width="72px"
19
+ @update:model-value="onOpacityUpdate(item, $event)"
20
+ @mousedown.stop
21
+ @click.stop
22
+ />
23
+ </div>
24
+ </template>
25
+ <template #append="{ item }">
26
+ <v-menu v-if="showMenu">
27
+ <template #activator="{ props }">
28
+ <v-btn v-bind="props" data-testid="menu" size="32" variant="text" icon="more_vert" />
11
29
  </template>
12
- </object-label>
13
- <v-spacer />
14
- <opacity
15
- :model-value="getOpacity(item)"
16
- @update:model-value="onOpacityUpdate(item, $event)"
17
- @mousedown.stop
18
- @click.stop
19
- />
20
- </div>
21
- </template>
22
- <template #append="{ item }">
23
- <v-menu>
24
- <template #activator="{ props }">
25
- <v-btn v-bind="props" data-testid="menu" size="32" variant="text" icon="more_vert" />
26
- </template>
27
- <v-list>
28
- <v-list-item data-testid="download" prepend-icon="download" @click="downloadMcad(item.key)">
29
- <v-list-item-title>Download</v-list-item-title>
30
- </v-list-item>
31
- </v-list>
32
- </v-menu>
33
- </template>
34
- </v-treeview>
30
+ <v-list>
31
+ <v-list-item data-testid="download" prepend-icon="download" @click="downloadMcad(item.key)">
32
+ <v-list-item-title>Download</v-list-item-title>
33
+ </v-list-item>
34
+ </v-list>
35
+ </v-menu>
36
+ </template>
37
+ </v-treeview>
38
+ </div>
35
39
  </template>
36
40
 
37
41
  <script setup lang="ts">
38
- import { ref, watch } from 'vue';
42
+ import { computed, ref, watch } from 'vue';
39
43
  import { useViewer3cr } from '@/composables/useViewer3cr';
40
44
  import { clamp } from '@/functions/clamp';
41
45
  import { rgbaNormalizedToCss } from '@/functions/rgbaToCss';
@@ -55,6 +59,10 @@ const viewer3cr = useViewer3cr();
55
59
  const options = useViewerOptions();
56
60
  const data = ref<McadObjectTreeViewItem[]>([]);
57
61
 
62
+ const showMenu = computed(() => {
63
+ return !!options.value.OnDownloadMcad;
64
+ });
65
+
58
66
  watch(
59
67
  () => props.items,
60
68
  (items) => {
@@ -64,7 +72,9 @@ watch(
64
72
  );
65
73
 
66
74
  function setTreeViewData(mcadObjects: DataOverlayMcad[]): void {
67
- data.value = mcadObjects.map((mcadObject) => mapToTreeViewItem(mcadObject));
75
+ data.value = mcadObjects
76
+ .sort((a, b) => a.Title.localeCompare(b.Title))
77
+ .map((mcadObject) => mapToTreeViewItem(mcadObject));
68
78
  }
69
79
 
70
80
  async function downloadMcad(id: string): Promise<void> {
@@ -74,8 +84,8 @@ async function downloadMcad(id: string): Promise<void> {
74
84
  }
75
85
 
76
86
  function getColor(item: McadObjectTreeViewItem): string {
77
- const { r, g, b, a } = item.colour;
78
- return rgbaNormalizedToCss(r + 0.2, g + 0.2, b + 0.2, a - 0.2);
87
+ const { r, g, b } = item.colour;
88
+ return rgbaNormalizedToCss(r, g, b, 1);
79
89
  }
80
90
 
81
91
  function getOpacity(item: McadObjectTreeViewItem): number {
@@ -0,0 +1,71 @@
1
+ <template>
2
+ <v-card density="compact">
3
+ <v-card-title>{{ dataOverlay.Title }}</v-card-title>
4
+ <v-list density="compact" :lines="false">
5
+ <v-list-item v-if="hasDescription" prepend-icon="description">
6
+ <v-list-item-title>Description</v-list-item-title>
7
+ <v-list-item-subtitle>{{ dataOverlay.Description }}</v-list-item-subtitle>
8
+ </v-list-item>
9
+ <v-list-item v-else prepend-icon="psychology">
10
+ <v-list-item-title>Smart Description</v-list-item-title>
11
+ <v-list-item-subtitle v-if="hasSmartDescription">
12
+ {{ smartDescriptions[dataOverlay.Title].GptResponse }}
13
+ </v-list-item-subtitle>
14
+ <v-list-item-subtitle v-else>
15
+ <v-progress-linear class="mt-2" indeterminate />
16
+ </v-list-item-subtitle>
17
+ </v-list-item>
18
+ <v-list-item prepend-icon="info">
19
+ <v-list-item-title>Resources</v-list-item-title>
20
+ <v-list-item-subtitle v-if="hasActions">
21
+ <template v-for="action in dataOverlay.CallToAction!.Actions">
22
+ <a @click.stop class="text-small" :href="action.ActionData.Url" target="_blank">
23
+ {{ action.ActionData.Description }}
24
+ </a>
25
+ <br />
26
+ </template>
27
+ </v-list-item-subtitle>
28
+ <v-list-item-subtitle v-else class="font-italic">No resources provided</v-list-item-subtitle>
29
+ </v-list-item>
30
+ </v-list>
31
+ </v-card>
32
+ </template>
33
+
34
+ <script setup lang="ts">
35
+ import { DataOverlay } from '@/types/data-overlay';
36
+ import { computed, ref, watch } from 'vue';
37
+ import { GptResponsePayload } from '@/types/gpt-response-payload';
38
+ import { GptService } from '@/services/gpt.service';
39
+
40
+ interface Props {
41
+ dataOverlay: DataOverlay;
42
+ }
43
+
44
+ const props = defineProps<Props>();
45
+
46
+ const smartDescriptions = ref<Record<string, GptResponsePayload>>({});
47
+
48
+ const hasDescription = computed(() => !!props.dataOverlay.Description);
49
+
50
+ const hasSmartDescription = computed(() => props.dataOverlay.Title in smartDescriptions.value);
51
+
52
+ const hasActions = computed(() => {
53
+ return props.dataOverlay.CallToAction?.Actions && props.dataOverlay.CallToAction.Actions.length > 0;
54
+ });
55
+
56
+ watch(
57
+ () => props.dataOverlay,
58
+ async (value: DataOverlay) => {
59
+ await generateSmartDescriptions(value);
60
+ },
61
+ { immediate: true }
62
+ );
63
+
64
+ async function generateSmartDescriptions(overlay: DataOverlay): Promise<void> {
65
+ const key = overlay.Title;
66
+ if (!(key in smartDescriptions.value)) {
67
+ const response = await GptService.Instantiate().GenerateAnnotations(key);
68
+ smartDescriptions.value[key] = response.data;
69
+ }
70
+ }
71
+ </script>
@@ -0,0 +1,60 @@
1
+ <template>
2
+ <v-card density="compact">
3
+ <v-card-title>{{ markup.Title }}</v-card-title>
4
+ <v-list density="compact" :lines="false">
5
+ <v-list-item prepend-icon="analytics">
6
+ <v-list-item-title>Data</v-list-item-title>
7
+ <v-list-item-subtitle>
8
+ <template v-if="'SegmentLengths' in markup">
9
+ <v-row no-gutters v-for="(segment, idx) in markup.SegmentLengths">
10
+ <v-col style="width: 200px">Segment {{ idx + 1 }}</v-col>
11
+ <v-col class="text-mono">{{ segment }}{{ markup.Units }}</v-col>
12
+ </v-row>
13
+ <v-row no-gutters>
14
+ <v-col style="width: 200px">Total Length</v-col>
15
+ <v-col class="text-mono">{{ markup.TotalLength }}{{ markup.Units }}</v-col>
16
+ </v-row>
17
+ </template>
18
+ <template v-if="'Area' in markup">
19
+ <v-row no-gutters>
20
+ <v-col style="width: 200px">Area</v-col>
21
+ <v-col class="text-mono">{{ markup.Area }}</v-col>
22
+ </v-row>
23
+ <v-row no-gutters>
24
+ <v-col style="width: 200px">Perimeter</v-col>
25
+ <v-col class="text-mono">{{ markup.Perimeter }}</v-col>
26
+ </v-row>
27
+ <v-row no-gutters>
28
+ <v-col style="width: 200px">Average HU</v-col>
29
+ <v-col class="text-mono">{{ markup.AverageHU }}</v-col>
30
+ </v-row>
31
+ <v-row no-gutters>
32
+ <v-col style="width: 200px">Lowest HU</v-col>
33
+ <v-col class="text-mono">{{ markup.LowestHU }}</v-col>
34
+ </v-row>
35
+ <v-row no-gutters>
36
+ <v-col style="width: 200px">Highest HU</v-col>
37
+ <v-col class="text-mono">{{ markup.HighestHU }}</v-col>
38
+ </v-row>
39
+ </template>
40
+ <template v-if="'SegmentAngles' in markup">
41
+ <v-row no-gutters v-for="(angle, idx) in markup.SegmentAngles">
42
+ <v-col style="width: 200px">Angle {{ idx + 1 }}</v-col>
43
+ <v-col class="text-mono">{{ angle.Acute }}{{ markup.Units }}</v-col>
44
+ </v-row>
45
+ </template>
46
+ </v-list-item-subtitle>
47
+ </v-list-item>
48
+ </v-list>
49
+ </v-card>
50
+ </template>
51
+
52
+ <script setup lang="ts">
53
+ import { DataOverlayMarkup } from '@/types/data-overlay-markup';
54
+
55
+ interface Props {
56
+ markup: DataOverlayMarkup;
57
+ }
58
+
59
+ defineProps<Props>();
60
+ </script>
@@ -1,89 +1,88 @@
1
1
  <template>
2
- <v-menu
3
- v-model="modalState"
4
- min-width="100"
5
- max-width="400"
6
- persistent
7
- :no-click-animation="true"
8
- :target="item.target"
9
- >
10
- <v-card density="compact">
11
- <v-card-title>{{ item.title }}</v-card-title>
12
- <v-list density="compact" :lines="false">
13
- <v-list-item v-for="data of item.data">
14
- <template #prepend>
15
- <v-icon class="mr-n4" :icon="data.icon" />
16
- </template>
17
- <v-list-item-title>{{ data.title }}</v-list-item-title>
18
-
19
- <v-list-item-subtitle v-if="data.subtitle">{{ data.subtitle }}</v-list-item-subtitle>
20
-
21
- <v-list-item-subtitle v-if="data.actions && data.actions.length > 0">
22
- <template v-for="action in data.actions">
23
- <a @click.stop class="text-small" :href="action.url" target="_blank">
24
- {{ action.description }}
25
- </a>
26
- <br />
27
- </template>
28
- </v-list-item-subtitle>
29
- </v-list-item>
30
- </v-list>
31
- </v-card>
2
+ <v-menu v-model="menu" max-width="500" persistent :no-click-animation="true" :target="menuTarget">
3
+ <template v-if="selected">
4
+ <data-overlay-markup-modal v-if="isMarkup" :markup="selected as DataOverlayMarkup" />
5
+ <data-overlay-general-modal v-else :data-overlay="selected" />
6
+ </template>
32
7
  </v-menu>
33
8
  </template>
34
9
 
35
10
  <script setup lang="ts">
36
- import { DataOverlayInfo } from '@/types/data-overlay-info';
37
- import { computed } from 'vue';
11
+ import { computed, onMounted, onUnmounted, ref, watch } from 'vue';
12
+ import { useEventListener } from '@/composables/useEventListener';
13
+ import { DataOverlay } from '@/types/data-overlay';
14
+ import { DataOverlayMarkup } from '@/types/data-overlay-markup';
15
+ import { isDataOverlayLength } from '@/functions/guards/isDataOverlayLength';
16
+ import { isDataOverlayPolygon } from '@/functions/guards/isDataOverlayPolygon';
17
+ import { isDataOverlayAngle } from '@/functions/guards/isDataOverlayAngle';
18
+ import { useDataOverlayTool } from '@/tools/data-overlay.tool';
38
19
 
39
- interface Props {
40
- modal?: boolean;
41
- item: DataOverlayInfo;
42
- }
20
+ useEventListener(document, 'mousemove', onMouseMove);
21
+ useEventListener(document, 'mousedown', onMouseDown);
43
22
 
44
- type Emits = {
45
- 'update:modal': [boolean];
46
- };
23
+ const dataOverlayTool = useDataOverlayTool();
24
+ const menu = ref<boolean>(false);
25
+ const currentTarget = ref<[number, number]>([0, 0]);
26
+ const menuTarget = ref<[number, number]>([0, 0]);
47
27
 
48
- const props = withDefaults(defineProps<Props>(), {
49
- modal: false
28
+ const selected = computed(() => dataOverlayTool.selected.value);
29
+
30
+ const isMarkup = computed(() => {
31
+ const val = selected.value;
32
+ return val && (isDataOverlayLength(val) || isDataOverlayPolygon(val) || isDataOverlayAngle(val));
50
33
  });
51
34
 
52
- const emit = defineEmits<Emits>();
35
+ onMounted(() => {
36
+ dataOverlayTool.activate();
37
+ });
53
38
 
54
- const modalState = computed({
55
- get(): boolean {
56
- return props.modal;
57
- },
58
- set(value: boolean): void {
59
- emit('update:modal', value);
60
- }
39
+ onUnmounted(() => {
40
+ dataOverlayTool.deactivate();
61
41
  });
42
+
43
+ watch(
44
+ selected,
45
+ async (value: DataOverlay | null) => {
46
+ if (value !== null) {
47
+ menuTarget.value = currentTarget.value;
48
+ menu.value = true;
49
+ }
50
+ },
51
+ { immediate: true }
52
+ );
53
+
54
+ function onMouseMove(event: MouseEvent): void {
55
+ currentTarget.value = [event.x, event.y];
56
+ }
57
+
58
+ function onMouseDown(): void {
59
+ menu.value = false;
60
+ }
62
61
  </script>
63
62
 
64
63
  <style scoped lang="scss">
65
- .v-card {
64
+ :deep(.v-card) {
66
65
  background: rgba(var(--v-theme-surface), 0.5) !important;
67
- backdrop-filter: blur(48px);
66
+ backdrop-filter: blur(24px);
68
67
  }
69
68
 
70
- .v-card-title {
69
+ :deep(.v-card-title) {
71
70
  background: none;
72
71
  font-size: 14px;
73
72
  padding-bottom: 0;
74
73
  }
75
74
 
76
- .v-list {
75
+ :deep(.v-list) {
77
76
  background: none;
78
77
  padding-top: 4px;
79
78
  padding-bottom: 4px;
80
79
  }
81
80
 
82
- .v-list-item-title {
81
+ :deep(.v-list-item-title) {
83
82
  font-size: 13px;
84
83
  }
85
84
 
86
- .v-list-item-subtitle {
85
+ :deep(.v-list-item-subtitle) {
87
86
  font-size: 12px;
88
87
  }
89
88
  </style>
@@ -4,7 +4,6 @@
4
4
  density="compact"
5
5
  hide-details
6
6
  max="100"
7
- max-width="64px"
8
7
  min="0"
9
8
  suffix="%"
10
9
  type="number"
@@ -0,0 +1,11 @@
1
+ export type Debouncable<Args extends unknown[]> = (...args: Args) => any;
2
+
3
+ export type Debounced<Args extends unknown[]> = (...args: Args) => void;
4
+
5
+ export function useDebounce<Args extends unknown[]>(callback: Debouncable<Args>, delay: number): Debounced<Args> {
6
+ let timeout: NodeJS.Timeout;
7
+ return function (...args: Args): void {
8
+ clearTimeout(timeout);
9
+ timeout = setTimeout(callback, delay, ...args);
10
+ };
11
+ }
@@ -5,13 +5,17 @@ import { computed, nextTick, ref, watch, WatchStopHandle } from 'vue';
5
5
  import { ScanView } from '@3cr/types-ts';
6
6
  import { useViewer3cr } from '@/composables/useViewer3cr';
7
7
  import { isFullscreen, previousLayout, scanState } from '@/models/scanState';
8
+ import { DemoType } from '@/types/demo-type';
8
9
  import introJs from 'intro.js';
9
-
10
- const viewer3cr = useViewer3cr();
10
+ import { useViewerOptions } from '@/composables/useViewerOptions';
11
11
 
12
12
  const demo = computed<string>(() => {
13
- const value = demoType.value;
14
- return value.charAt(0).toUpperCase() + value.substring(1);
13
+ switch (demoType.value) {
14
+ case DemoType.Licence:
15
+ return 'Licence';
16
+ default:
17
+ return 'Patient';
18
+ }
15
19
  });
16
20
 
17
21
  const options = computed<Partial<TourOptions>>(() => ({
@@ -71,11 +75,16 @@ function blurActiveElement(): void {
71
75
  }
72
76
 
73
77
  export function useIntroJs() {
78
+ const viewer3cr = useViewer3cr();
79
+ const viewerOptions = useViewerOptions();
80
+
74
81
  let scanStateHandle: WatchStopHandle;
75
82
 
76
83
  function onStart(this: Tour): void {
77
84
  scanStateHandle = watch(scanState, () => {
78
- this.refresh(true);
85
+ if (this.isActive()) {
86
+ this.refresh(true);
87
+ }
79
88
  });
80
89
  }
81
90
 
@@ -106,9 +115,16 @@ export function useIntroJs() {
106
115
  }
107
116
 
108
117
  /* istanbul ignore next: does not work with jsdom -- @preserve */
109
- function onExit(this: Tour): void {
118
+ function onBeforeExit(this: Tour): boolean {
110
119
  scanStateHandle();
111
120
  setTimeout(() => blurActiveElement(), 500);
121
+ return true;
122
+ }
123
+
124
+ async function onExit(this: Tour): Promise<void> {
125
+ if (viewerOptions.value.OnInteractionReady) {
126
+ await viewerOptions.value.OnInteractionReady();
127
+ }
112
128
  }
113
129
 
114
130
  const tour = introJs
@@ -117,6 +133,7 @@ export function useIntroJs() {
117
133
  .onStart(onStart)
118
134
  .onBeforeChange(onBeforeChange)
119
135
  .onChange(onChange)
136
+ .onBeforeExit(onBeforeExit)
120
137
  .onExit(onExit);
121
138
 
122
139
  const intro = ref<Tour>(tour);
@@ -11,6 +11,7 @@ const ax = axios.create();
11
11
  export function useViewerOptions() {
12
12
  return options;
13
13
  }
14
+
14
15
  export function mockDemoViewerStlDecimator() {
15
16
  options.value.OnResolveUrls = async (keys: string[]) =>
16
17
  await Promise.all(
@@ -0,0 +1,6 @@
1
+ import { DataOverlayAngle } from '@/types/data-overlay-angle';
2
+ import { DataOverlay } from '@/types/data-overlay';
3
+
4
+ export function isDataOverlayAngle(markup: DataOverlay): markup is DataOverlayAngle {
5
+ return 'SegmentAngles' in markup;
6
+ }
@@ -0,0 +1,6 @@
1
+ import { DataOverlayLength } from '@/types/data-overlay-length';
2
+ import { DataOverlay } from '@/types/data-overlay';
3
+
4
+ export function isDataOverlayLength(markup: DataOverlay): markup is DataOverlayLength {
5
+ return 'SegmentLengths' in markup;
6
+ }
@@ -0,0 +1,6 @@
1
+ import { DataOverlayPolygon } from '@/types/data-overlay-polygon';
2
+ import { DataOverlay } from '@/types/data-overlay';
3
+
4
+ export function isDataOverlayPolygon(markup: DataOverlay): markup is DataOverlayPolygon {
5
+ return 'Area' in markup;
6
+ }
@@ -3,6 +3,7 @@ import {
3
3
  CurrentDataOverlayState,
4
4
  CurrentMcadState,
5
5
  CurrentScanState,
6
+ GraphicType,
6
7
  InitialDataOverlayState,
7
8
  InitialMcadState,
8
9
  InitialScanState,
@@ -97,6 +98,7 @@ export function inflateScanState(): CurrentScanState {
97
98
  },
98
99
  NavigationCube: {
99
100
  Version: '1.0.0',
101
+ NavCubeGraphicType: GraphicType.BODY_SYMBOL,
100
102
  Transform: {
101
103
  Version: '1.0.0',
102
104
  AnchorPoint: AnchorPoint.DEFAULT,
@@ -1,48 +1,82 @@
1
1
  import { InteractivityActions, NavigationCubeActions, NotificationPayload, NotificationsActions } from '@3cr/types-ts';
2
2
  import { useNotification } from '@kyvg/vue3-notification';
3
+ import { t } from '@3cr/translations-ts';
4
+
3
5
  const { notify } = useNotification();
4
6
 
5
- export async function handleNotification(action: NotificationsActions, message: string) {
6
- const notification = JSON.parse(message) as NotificationPayload;
7
+ type NotificationType = 'success' | 'info' | 'warn' | 'error';
7
8
 
8
- switch (notification.Action) {
9
- case InteractivityActions.in01:
10
- case InteractivityActions.in02:
11
- case InteractivityActions.in03:
12
- case InteractivityActions.in04:
13
- case NavigationCubeActions.nc01:
14
- return;
15
- default:
16
- }
9
+ function getNotificationType(action: NotificationsActions): NotificationType {
10
+ switch (action) {
11
+ case NotificationsActions.no01:
12
+ return 'success';
17
13
 
18
- if (notification.Action.startsWith('sl')) {
19
- return;
14
+ case NotificationsActions.no02:
15
+ return 'error';
16
+
17
+ case NotificationsActions.no03:
18
+ return 'warn';
19
+
20
+ default:
21
+ case NotificationsActions.no04:
22
+ return 'info';
20
23
  }
24
+ }
21
25
 
22
- let type = 'success';
26
+ function getNotificationTitle(action: NotificationsActions): string {
23
27
  switch (action) {
24
- // Muting no01
25
28
  case NotificationsActions.no01:
26
- type = 'success';
27
- return;
29
+ return 'Success';
28
30
 
29
31
  case NotificationsActions.no02:
30
- type = 'error';
31
- break;
32
+ return 'Error';
32
33
 
33
34
  case NotificationsActions.no03:
34
- type = 'warn';
35
- break;
35
+ return 'Warning';
36
36
 
37
- // Muting no04
37
+ default:
38
38
  case NotificationsActions.no04:
39
- type = 'info';
40
- return;
39
+ return 'Information';
40
+ }
41
+ }
42
+
43
+ function getNotificationText(notification: NotificationPayload): string {
44
+ return t(notification.Code, {});
45
+ }
46
+
47
+ function shouldDisplayNotification(action: NotificationsActions, notification: NotificationPayload): boolean {
48
+ // Hide type specific messages
49
+ const hiddenTypes = [NotificationsActions.no01, NotificationsActions.no04];
50
+ if (hiddenTypes.includes(action)) {
51
+ return false;
52
+ }
53
+
54
+ // Hide all slider actions
55
+ if (notification.Action.startsWith('sl')) {
56
+ return false;
41
57
  }
42
58
 
43
- notify({
44
- title: notification.Code,
45
- text: notification.Action,
46
- type
47
- });
59
+ // Hide action specific messages
60
+ const hiddenActions = [
61
+ InteractivityActions.in01,
62
+ InteractivityActions.in02,
63
+ InteractivityActions.in03,
64
+ InteractivityActions.in04,
65
+ NavigationCubeActions.nc01
66
+ ] as string[];
67
+ if (hiddenActions.includes(notification.Action)) {
68
+ return false;
69
+ }
70
+
71
+ return true;
72
+ }
73
+
74
+ export async function handleNotification(action: NotificationsActions, message: string) {
75
+ const notification = JSON.parse(message) as NotificationPayload;
76
+ if (shouldDisplayNotification(action, notification)) {
77
+ const title = getNotificationTitle(action);
78
+ const text = getNotificationText(notification);
79
+ const type = getNotificationType(action);
80
+ notify({ title, text, type });
81
+ }
48
82
  }