@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,78 +1,69 @@
1
1
  <template>
2
- <v-dialog :model-value="value" :no-click-animation="true" fullscreen persistent>
3
- <v-card class="pa-0 ma-0 position-relative motif-background overflow-y-hidden rounded-0" height="100vh">
4
- <v-app-bar density="compact">
5
- <file-menu @close="alterValue(false)" />
6
- <settings-menu
7
- v-model="introJsMenu"
8
- :activator-props="{ disabled: introJsMenuDisabled }"
9
- :persistent="introJsMenuDisabled"
10
- :no-click-animation="introJsMenuDisabled"
11
- />
12
- <auto-annotate-btn class="mr-2 transparent" />
13
- <v-spacer />
14
- <div class="font-weight-bold">
15
- <span v-if="isDemo" class="text-capitalize">3DICOM {{ demoType }} DEMO |</span>
16
- <span v-else class="text-capitalize">3DICOM Patient Viewer |</span>
17
- Not for Diagnostic Use
18
- </div>
19
- <v-spacer />
20
- <v-btn
21
- class="ma-1 mr-0 pa-1"
22
- data-testid="layout-2x2"
23
- height="36"
24
- style="min-width: 36px !important"
25
- :color="isLayout2x2 ? 'secondary' : 'primary'"
26
- @click="viewer3cr.layouts(LayoutActions.lo02)"
27
- >
28
- <v-icon>grid_view</v-icon>
29
- <v-tooltip location="bottom" activator="parent"> Change Layout to 2:2 </v-tooltip>
30
- </v-btn>
31
- <v-btn
32
- class="ma-1 mr-1 pa-1"
33
- data-testid="layout-1x3"
34
- height="36"
35
- style="min-width: 36px !important"
36
- :color="isLayout1x3 ? 'secondary' : 'primary'"
37
- @click="viewer3cr.layouts(LayoutActions.lo03)"
38
- >
39
- <v-icon style="rotate: -90deg">view_comfy</v-icon>
40
- <v-tooltip location="bottom" activator="parent"> Change Layout to 1:3 </v-tooltip>
41
- </v-btn>
42
- <v-btn variant="flat" color="red" @click="alterValue(false)"> Close Viewer </v-btn>
43
- </v-app-bar>
44
- <ViewerNavigationDrawer v-model:drawer="drawer" @update:expanded="expanded = $event" />
45
- <v-main>
46
- <WebGL3DR
47
- v-show="value && !scanLoading"
48
- ref="webGl3dr"
49
- @instance_loaded="load"
50
- @dblclick="viewer3cr.viewSelection(ViewSelectionActions.vs05)"
51
- @mouseenter="onMouseEnter"
52
- @mouseleave="onMouseLeave"
53
- >
54
- <div
55
- v-for="layout in scanState.Layout.PositionData"
56
- :key="getCurrentView(layout)"
57
- :style="generateDivStyleForLayout(layout, webGl3dr!.canvas)"
58
- >
59
- <ViewerScanView class="w-100 h-100" :view="getCurrentView(layout)" />
60
- </div>
61
- </WebGL3DR>
62
- <data-overlay-modal-manager />
63
- </v-main>
2
+ <v-app>
3
+ <v-app-bar density="compact">
4
+ <file-menu v-if="showFileMenu" @close="m_closeDialog = true" />
5
+ <settings-menu
6
+ v-model="introJsMenu"
7
+ v-if="showSettingsMenu"
8
+ :activator-props="{ disabled: introJsMenuDisabled }"
9
+ :persistent="introJsMenuDisabled"
10
+ :no-click-animation="introJsMenuDisabled"
11
+ />
12
+ <auto-annotate-btn v-if="showAiMenu" class="mr-2 transparent" />
13
+ <v-spacer />
14
+ <div class="font-weight-bold">
15
+ <span class="text-capitalize">{{ viewerTitle }}</span>
16
+ </div>
17
+ <v-spacer />
18
+ <v-btn
19
+ v-if="show2x2Layout"
20
+ class="ma-1 mr-0 pa-1"
21
+ data-testid="layout-2x2"
22
+ height="36"
23
+ style="min-width: 36px !important"
24
+ :color="isLayout2x2 ? 'primary' : 'default'"
25
+ @click="viewer3cr.layouts(LayoutActions.lo02)"
26
+ >
27
+ <v-icon>grid_view</v-icon>
28
+ <v-tooltip location="bottom" activator="parent"> Change Layout to 2:2 </v-tooltip>
29
+ </v-btn>
30
+ <v-btn
31
+ v-if="show1x3Layout"
32
+ class="ma-1 mr-1 pa-1"
33
+ data-testid="layout-1x3"
34
+ height="36"
35
+ style="min-width: 36px !important"
36
+ :color="isLayout1x3 ? 'primary' : 'default'"
37
+ @click="viewer3cr.layouts(LayoutActions.lo03)"
38
+ >
39
+ <v-icon style="rotate: -90deg">view_comfy</v-icon>
40
+ <v-tooltip location="bottom" activator="parent"> Change Layout to 1:3 </v-tooltip>
41
+ </v-btn>
42
+ <v-btn variant="flat" color="red" @click="m_closeDialog = true">Close Viewer</v-btn>
43
+ </v-app-bar>
44
+ <ViewerNavigationDrawer v-model:drawer="drawer" @update:expanded="expanded = $event" />
45
+ <v-main class="motif-background">
64
46
  <LoadingSpinner v-if="scanLoading" text="Rendering your scan in 3D" />
65
- </v-card>
66
- </v-dialog>
67
- <DemoLicenceInfoModal v-model:modal="m_demo" :is-modal-open="value" />
68
- <DemoLicenceShareToMobileModal v-model:modal="m_demoLicenceShareToMobile" />
69
- <DemoLicenceSendToPartyModal v-model:modal="m_demoLicenceSendToParty" />
70
- <DemoLicenceEnableCloudStorageModal v-model:modal="m_demoLicenseEnableCloudStorage" />
71
- <DemoPatientInfoModal v-model:modal="m_demoPatient" :is-modal-open="value" />
72
- <DemoPatientShareToMobileModal v-model:modal="m_demoPatientShareToMobile" />
73
- <DemoPatientSendToPartyModal v-model:modal="m_demoPatientSendToParty" />
74
- <DemoPatientEnableCloudStorageModal v-model:modal="m_demoPatientEnableCloudStorage" />
75
- <CloseViewerModal v-model:modal="m_closeDialog" @close="value = false" />
47
+ <WebGL3DR
48
+ v-show="!scanLoading"
49
+ ref="webGl3dr"
50
+ @instance_loaded="load"
51
+ @dblclick="viewer3cr.viewSelection(ViewSelectionActions.vs05)"
52
+ @mouseenter="onMouseEnter"
53
+ @mouseleave="onMouseLeave"
54
+ >
55
+ <div
56
+ v-for="layout in scanState.Layout.PositionData"
57
+ :key="getCurrentView(layout)"
58
+ :style="generateDivStyleForLayout(layout, webGl3dr!.canvas)"
59
+ >
60
+ <ViewerScanView class="w-100 h-100" :view="getCurrentView(layout)" />
61
+ </div>
62
+ </WebGL3DR>
63
+ <data-overlay-modal />
64
+ </v-main>
65
+ <CloseViewerModal v-model:modal="m_closeDialog" />
66
+ </v-app>
76
67
  </template>
77
68
 
78
69
  <script setup lang="ts">
@@ -81,27 +72,15 @@ import {
81
72
  DataOverlaysActions,
82
73
  FileManagementActions,
83
74
  FrontEndInterfaces,
75
+ GraphicType,
84
76
  LayoutActions,
85
77
  McadActions,
86
78
  NotificationsActions,
87
79
  ScanStateActions,
88
80
  ViewSelectionActions
89
81
  } from '@3cr/types-ts';
90
- import { nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue';
91
- import {
92
- checkIsDemo,
93
- demoOptions,
94
- demoType,
95
- isDemo,
96
- m_demo,
97
- m_demoLicenceSendToParty,
98
- m_demoLicenceShareToMobile,
99
- m_demoLicenseEnableCloudStorage,
100
- m_demoPatient,
101
- m_demoPatientEnableCloudStorage,
102
- m_demoPatientSendToParty,
103
- m_demoPatientShareToMobile
104
- } from '@/components/demo/options';
82
+ import { computed, nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue';
83
+ import { checkIsDemo, demoOptions, demoType, isDemo } from '@/components/demo/options';
105
84
  import { handleNotification } from '@/functions/notification';
106
85
  import { defaultLoadViewerPayload, LoadViewerPayload } from '@/models/loadViewerPayload';
107
86
  import { defaultLoadViewerOptions, LoadViewerOptions } from '@/models/loadViewerOptions';
@@ -127,6 +106,7 @@ import { useScanMovement } from '@/composables/useScanMovement';
127
106
  import { useScanSliders } from '@/composables/useScanSliders';
128
107
  import { useIntroJs } from '@/composables/useIntroJs';
129
108
  import WebGL3DR from '@/components/modal/WebGL3DR.vue';
109
+ import { DemoType } from '@/types/demo-type';
130
110
 
131
111
  interface Props {
132
112
  payload?: LoadViewerPayload;
@@ -145,7 +125,6 @@ const opts = useViewerOptions();
145
125
  const { intro } = useIntroJs();
146
126
  const webGl3dr = ref<typeof WebGL3DR>();
147
127
  const observer = ref<ResizeObserver>();
148
- const value = ref<boolean>(false);
149
128
  const drawer = ref<boolean>(false);
150
129
  const expanded = ref<number | undefined>();
151
130
  const scanLoading = ref<boolean>(true);
@@ -153,18 +132,41 @@ const m_closeDialog = ref<boolean>(false);
153
132
  const introJsMenu = ref<boolean>(false);
154
133
  const introJsMenuDisabled = ref<boolean>(false);
155
134
 
135
+ const demo = computed<string>(() => {
136
+ switch (demoType.value) {
137
+ case DemoType.Licence:
138
+ return 'Licence';
139
+ default:
140
+ return 'Patient';
141
+ }
142
+ });
143
+
144
+ const showFileMenu = computed(() => opts.value.ShowFileMenu !== false);
145
+
146
+ const showSettingsMenu = computed(() => opts.value.ShowSettingsMenu !== false);
147
+
148
+ const showAiMenu = computed(() => opts.value.ShowAiMenu !== false);
149
+
150
+ const show2x2Layout = computed(() => opts.value.Show2x2Layout !== false);
151
+
152
+ const show1x3Layout = computed(() => opts.value.Show1x3Layout !== false);
153
+
154
+ const viewerTitle = computed(() => {
155
+ return (
156
+ opts.value.ViewerTitle ??
157
+ `3DICOM ${isDemo.value ? `${demo.value} DEMO` : 'Patient Viewer'} | Not for Diagnostic Use`
158
+ );
159
+ });
160
+
156
161
  watch(
157
- () => props.options,
158
- (value: LoadViewerOptions) => {
159
- opts.value = isDemo.value ? demoOptions.value : value;
162
+ props,
163
+ (props) => {
164
+ checkIsDemo(props.payload);
165
+ opts.value = isDemo.value ? demoOptions.value : props.options;
160
166
  },
161
167
  { immediate: true }
162
168
  );
163
169
 
164
- watch(isDemo, (demo: boolean) => {
165
- opts.value = demo ? demoOptions.value : props.options;
166
- });
167
-
168
170
  /* istanbul ignore next -- @preserve */
169
171
  watch(
170
172
  () => intro.value.getCurrentStep(),
@@ -173,7 +175,9 @@ watch(
173
175
  introJsMenu.value = lock;
174
176
  introJsMenuDisabled.value = lock;
175
177
  setTimeout(() => {
176
- intro.value.refresh(true);
178
+ if (intro.value.isActive()) {
179
+ intro.value.refresh(true);
180
+ }
177
181
  }, 400);
178
182
  }
179
183
  );
@@ -185,18 +189,37 @@ onMounted(() => {
185
189
  });
186
190
 
187
191
  viewer3cr.addActionHandler(FileManagementActions.fm02, async (message: string) => {
192
+ // Set up the scan state
188
193
  setInitialScanStateFromPayload(message);
194
+ transactionStarted.value = false;
195
+ scanLoading.value = false;
196
+ drawer.value = true;
197
+ await nextTick();
189
198
  if (opts.value.OnScanLoaded) {
190
199
  await opts.value.OnScanLoaded(scanState.value);
191
200
  }
192
201
 
193
- await nextTick();
194
- transactionStarted.value = false;
195
- scanLoading.value = false;
196
- drawer.value = true;
202
+ // Set up the navigation cube
197
203
  observer.value = useNavigationCubeObserver(webGl3dr.value!.canvas);
204
+ switch (demoType.value) {
205
+ case DemoType.Licence:
206
+ await viewer3cr.setNavCubeGraphics(GraphicType.LETTER);
207
+ break;
208
+ default:
209
+ await viewer3cr.setNavCubeGraphics(GraphicType.BODY_SYMBOL);
210
+ break;
211
+ }
212
+
213
+ // Load the demo data
198
214
  await loadDemoData();
199
- await intro.value.start();
215
+
216
+ // Load the tutorial
217
+ if (opts.value.ShowTutorial !== false) {
218
+ await intro.value.start();
219
+ if (!intro.value.isActive() && opts.value.OnInteractionReady) {
220
+ await opts.value.OnInteractionReady();
221
+ }
222
+ }
200
223
  });
201
224
 
202
225
  viewer3cr.addActionHandler(
@@ -259,16 +282,6 @@ onBeforeUnmount(() => {
259
282
  }
260
283
  });
261
284
 
262
- function alterValue(val: boolean) {
263
- if (!val) {
264
- m_closeDialog.value = true;
265
- return;
266
- } else {
267
- checkIsDemo(props.payload);
268
- }
269
- value.value = val;
270
- }
271
-
272
285
  /* istanbul ignore next -- @preserve */
273
286
  async function loadSession(url: string): Promise<void> {
274
287
  if (url.includes('3crs')) {
@@ -287,6 +300,12 @@ async function load(): Promise<void> {
287
300
  await viewer3cr.loadScan(props.payload);
288
301
  }
289
302
 
303
+ async function close(): Promise<void> {
304
+ if (opts.value.OnExitViewer) {
305
+ await opts.value.OnExitViewer();
306
+ }
307
+ }
308
+
290
309
  async function onMouseEnter(): Promise<void> {
291
310
  await viewer3cr.hoverOverCanvas(true);
292
311
  }
@@ -301,18 +320,12 @@ async function loadDemoData(): Promise<void> {
301
320
  const BASE_STATE_URL = 'https://webgl-3dr.singular.health/demo';
302
321
  mockDemoViewerStlDecimator();
303
322
  await viewer3cr.addDataOverlaySession({ Url: `${BASE_STATE_URL}/state/data_overlay.3crds` });
304
- await viewer3cr.addDataOverlaySession({ Url: `${BASE_STATE_URL}/state/markups.3crds` });
323
+ await viewer3cr.addDataOverlaySession({ Url: `${BASE_STATE_URL}/state/markups_2.3crds` });
305
324
  // await viewer3cr.loadMcadObjects({ Url: `${BASE_STATE_URL}/state/mcad.3crms` });
306
325
  }
307
326
  }
308
327
 
309
- defineExpose({
310
- alterValue,
311
- loadSession,
312
- load,
313
- m_closeDialog,
314
- value
315
- });
328
+ defineExpose({ loadSession, load, close, m_closeDialog });
316
329
  </script>
317
330
 
318
331
  <style>
@@ -38,7 +38,6 @@ import Rotate2dAction from '@/components/modal/actions/Rotate2dAction.vue';
38
38
  import Slice3dAction from '@/components/modal/actions/Slice3dAction.vue';
39
39
  import ZoomAction from '@/components/modal/actions/ZoomAction.vue';
40
40
  import { useViewer3cr } from '@/composables/useViewer3cr';
41
- import HideViewAction from '@/components/modal/actions/HideViewAction.vue';
42
41
 
43
42
  interface Props {
44
43
  view: ScanView;
@@ -95,11 +94,6 @@ const actions = computed<ActionItem[]>(() => [
95
94
  width: 48,
96
95
  condition: true
97
96
  },
98
- {
99
- component: HideViewAction,
100
- width: 48,
101
- condition: true
102
- },
103
97
  {
104
98
  component: ZoomAction,
105
99
  width: 48,
@@ -68,19 +68,21 @@ const expanded = ref<number | undefined>(0);
68
68
 
69
69
  const items = computed<SettingsItem[]>(() => [
70
70
  {
71
- icon: 'display_settings',
72
- text: 'Display Settings',
73
- tooltip: 'Change the range of visible anatomy in the scan by only showing areas with certain density of tissue',
71
+ icon: options.value.DisplaySettings?.Icon ?? 'display_settings',
72
+ text: options.value.DisplaySettings?.Title ?? 'Display Settings',
73
+ tooltip:
74
+ options.value.DisplaySettings?.Tooltip ??
75
+ 'Change the range of visible anatomy in the scan by only showing areas with certain density of tissue',
74
76
  component: ViewerDisplaySettings,
75
- visible: true,
77
+ visible: showDisplaySettings.value,
76
78
  props: {
77
79
  class: 'pa-4'
78
80
  }
79
81
  },
80
82
  {
81
- icon: 'draw',
82
- text: 'Annotations',
83
- tooltip: 'Display annotations on your scans',
83
+ icon: options.value.Annotations?.Icon ?? 'draw',
84
+ text: options.value.Annotations?.Title ?? 'Annotations',
85
+ tooltip: options.value.Annotations?.Tooltip ?? 'Display annotations on your scans',
84
86
  component: AnnotationTreeView,
85
87
  visible: !!showAnnotations.value,
86
88
  props: {
@@ -88,9 +90,9 @@ const items = computed<SettingsItem[]>(() => [
88
90
  }
89
91
  },
90
92
  {
91
- icon: 'design_services',
92
- text: 'Markups',
93
- tooltip: 'Display markups on your scans',
93
+ icon: options.value.Markups?.Icon ?? 'design_services',
94
+ text: options.value.Markups?.Title ?? 'Markups',
95
+ tooltip: options.value.Markups?.Tooltip ?? 'Display markups on your scans',
94
96
  component: Markups,
95
97
  visible: !!showMarkups.value,
96
98
  props: {
@@ -98,9 +100,9 @@ const items = computed<SettingsItem[]>(() => [
98
100
  }
99
101
  },
100
102
  {
101
- icon: 'deployed_code',
102
- text: 'MCAD',
103
- tooltip: 'Display MCAD objects on your scans',
103
+ icon: options.value.Mcad?.Icon ?? 'deployed_code',
104
+ text: options.value.Mcad?.Title ?? 'MCAD',
105
+ tooltip: options.value.Mcad?.Tooltip ?? 'Display MCAD objects on your scans',
104
106
  component: McadObjects,
105
107
  visible: !!showMcadObjects.value,
106
108
  props: {
@@ -109,16 +111,23 @@ const items = computed<SettingsItem[]>(() => [
109
111
  }
110
112
  ]);
111
113
 
114
+ const showDisplaySettings = computed(() => {
115
+ return options.value.DisplaySettings?.Show !== false;
116
+ });
117
+
112
118
  const showAnnotations = computed(() => {
113
- return options.value.OnLoadSavedSession && hasAnnotations.value;
119
+ const opts = options.value;
120
+ return opts.Annotations?.Show !== false && opts.OnLoadSavedSession && hasAnnotations.value;
114
121
  });
115
122
 
116
123
  const showMarkups = computed(() => {
117
- return options.value.OnLoadSavedSession && hasMarkups.value;
124
+ const opts = options.value;
125
+ return opts.Markups?.Show !== false && opts.OnLoadSavedSession && hasMarkups.value;
118
126
  });
119
127
 
120
128
  const showMcadObjects = computed(() => {
121
- return options.value.OnLoadSavedSession && hasMcadObjects.value;
129
+ const opts = options.value;
130
+ return opts.Mcad?.Show !== false && opts.OnLoadSavedSession && hasMcadObjects.value;
122
131
  });
123
132
 
124
133
  watch(
@@ -2,7 +2,7 @@
2
2
  <div>
3
3
  <div class="pa-1 d-flex flex-wrap justify-space-between" :class="drawer ? 'flex-row' : 'flex-col'">
4
4
  <template v-for="(item, index) of footerItems" :key="index">
5
- <div v-if="item.visible()" class="ma-auto" :data-testid="`footer-${index}`">
5
+ <div v-if="item.visible" class="ma-auto" :data-testid="`footer-${index}`">
6
6
  <v-tooltip :location="drawer ? 'top' : 'right'">
7
7
  <template #activator="{ props }">
8
8
  <v-btn v-bind="props" variant="text" size="36" :icon="true" :disabled="disabled" @click="item.click()">
@@ -22,7 +22,7 @@
22
22
  </template>
23
23
 
24
24
  <script setup lang="ts">
25
- import { ref } from 'vue';
25
+ import { computed } from 'vue';
26
26
  import { currentColourPreset, initialScanState } from '@/models/scanState';
27
27
  import { ViewSelectionActions } from '@3cr/types-ts';
28
28
  import { useViewer3cr } from '@/composables/useViewer3cr';
@@ -37,7 +37,7 @@ interface Props {
37
37
  interface FooterItem {
38
38
  text: string;
39
39
  iconProps: Record<string, any>;
40
- visible: () => boolean;
40
+ visible: boolean;
41
41
  click: () => void | Promise<void>;
42
42
  }
43
43
 
@@ -49,11 +49,34 @@ withDefaults(defineProps<Props>(), {
49
49
  const viewer3cr = useViewer3cr();
50
50
  const version3cr = useVersion3cr();
51
51
  const options = useViewerOptions();
52
- const footerItems = ref<FooterItem[]>([
52
+
53
+ const showReset = computed(() => options.value.ShowResetScan !== false);
54
+
55
+ const showEnableCloudStorage = computed(() => {
56
+ const opts = options.value;
57
+ return opts.ShowEnableCloudStorage !== false && !!opts.OnEnableCloudStorage;
58
+ });
59
+
60
+ const showSendTo3rdParty = computed(() => {
61
+ const opts = options.value;
62
+ return opts.ShowSendTo3rdParty !== false && !!opts.OnSendTo3rdParty;
63
+ });
64
+
65
+ const showShareToMobile = computed(() => {
66
+ const opts = options.value;
67
+ return opts.ShowShareToMobile !== false && !!opts.OnShareToMobile;
68
+ });
69
+
70
+ const showScreenshot = computed(() => {
71
+ const opts = options.value;
72
+ return opts.ShowScreenshot !== false && !!opts.OnScreenshot;
73
+ });
74
+
75
+ const footerItems = computed<FooterItem[]>(() => [
53
76
  {
54
77
  text: 'Reset Scan',
55
78
  iconProps: { icon: 'refresh', color: 'red' },
56
- visible: () => true,
79
+ visible: showReset.value,
57
80
  click: async () => {
58
81
  currentColourPreset.value = initialScanState.value.ColourPresets[0];
59
82
  await viewer3cr.viewSelection(ViewSelectionActions.vs05);
@@ -63,7 +86,7 @@ const footerItems = ref<FooterItem[]>([
63
86
  {
64
87
  text: 'Enable Cloud Storage',
65
88
  iconProps: { icon: 'cloud', color: 'blue' },
66
- visible: () => !!options.value.OnEnableCloudStorage,
89
+ visible: showEnableCloudStorage.value,
67
90
  click: async () => {
68
91
  await options.value.OnEnableCloudStorage!();
69
92
  }
@@ -71,7 +94,7 @@ const footerItems = ref<FooterItem[]>([
71
94
  {
72
95
  text: 'Send to 3rd Party',
73
96
  iconProps: { icon: 'send', color: 'green' },
74
- visible: () => !!options.value.OnSendTo3rdParty,
97
+ visible: showSendTo3rdParty.value,
75
98
  click: async () => {
76
99
  await options.value.OnSendTo3rdParty!();
77
100
  }
@@ -79,7 +102,7 @@ const footerItems = ref<FooterItem[]>([
79
102
  {
80
103
  text: 'Share to Mobile / VR',
81
104
  iconProps: { icon: 'share', color: 'yellow' },
82
- visible: () => !!options.value.OnShareToMobile,
105
+ visible: showShareToMobile.value,
83
106
  click: async () => {
84
107
  await options.value.OnShareToMobile!();
85
108
  }
@@ -87,7 +110,7 @@ const footerItems = ref<FooterItem[]>([
87
110
  {
88
111
  text: 'Screenshot View',
89
112
  iconProps: { icon: 'screenshot_region', color: 'green' },
90
- visible: () => !!options.value.OnScreenshot,
113
+ visible: showScreenshot.value,
91
114
  click: async () => {
92
115
  await options.value.OnScreenshot!();
93
116
  }
@@ -1,5 +1,9 @@
1
1
  <template>
2
- <div v-if="!isDemo || demoType === 'patient'" class="d-flex align-center pb-1" :class="!drawer ? 'py-2' : 'pa-2'">
2
+ <div
3
+ v-if="!isDemo || demoType === DemoType.Patient"
4
+ class="d-flex align-center pb-1"
5
+ :class="!drawer ? 'py-2' : 'pa-2'"
6
+ >
3
7
  <v-img v-if="drawer" src="@/assets/logos/3dicom/white.svg" height="64" alt="logo" />
4
8
  <v-img v-else src="@/assets/logos/3dicom/white-mini.svg" height="48" alt="logo" />
5
9
  </div>
@@ -11,6 +15,7 @@
11
15
 
12
16
  <script setup lang="ts">
13
17
  import { demoType, isDemo } from '@/components/demo/options';
18
+ import { DemoType } from '@/types/demo-type';
14
19
 
15
20
  interface Props {
16
21
  drawer?: boolean;
@@ -10,8 +10,10 @@
10
10
  <script setup lang="ts">
11
11
  import { onMounted, ref } from 'vue';
12
12
  import { useViewer3cr } from '@/composables/useViewer3cr';
13
+ import { useViewerOptions } from '@/composables/useViewerOptions';
13
14
 
14
15
  const viewer3cr = useViewer3cr();
16
+ const options = useViewerOptions();
15
17
  const canvas = ref<HTMLCanvasElement>();
16
18
  const overlay = ref<HTMLDivElement>();
17
19
 
@@ -54,6 +56,11 @@ onMounted(async () => {
54
56
  injectEventListener('contextmenu');
55
57
 
56
58
  await viewer3cr.register(canvas.value!);
59
+
60
+ if (options.value.OnViewerLoaded) {
61
+ await options.value.OnViewerLoaded(viewer3cr);
62
+ }
63
+
57
64
  emit('instance_loaded');
58
65
  });
59
66
 
@@ -2,6 +2,7 @@ import { mount } from '@vue/test-utils';
2
2
  import { demoType } from '@/components/demo/options';
3
3
  import { nextTick } from 'vue';
4
4
  import ViewerNavigationDrawerHeader from '@/components/modal/ViewerNavigationDrawerHeader.vue';
5
+ import { DemoType } from '@/types/demo-type';
5
6
 
6
7
  describe('ViewerNavigationDrawerHeader tests', () => {
7
8
  it('should mount', () => {
@@ -22,7 +23,7 @@ describe('ViewerNavigationDrawerHeader tests', () => {
22
23
  });
23
24
 
24
25
  it('should show collapsed patient logo', async () => {
25
- vi.spyOn(demoType, 'value', 'get').mockReturnValue('patient');
26
+ vi.spyOn(demoType, 'value', 'get').mockReturnValue(DemoType.Patient);
26
27
  const props = { drawer: false };
27
28
  const wrapper = mount(ViewerNavigationDrawerHeader, { props });
28
29
  await nextTick();
@@ -30,7 +31,7 @@ describe('ViewerNavigationDrawerHeader tests', () => {
30
31
  });
31
32
 
32
33
  it('should show expanded patient logo', async () => {
33
- vi.spyOn(demoType, 'value', 'get').mockReturnValue('patient');
34
+ vi.spyOn(demoType, 'value', 'get').mockReturnValue(DemoType.Patient);
34
35
  const props = { drawer: true };
35
36
  const wrapper = mount(ViewerNavigationDrawerHeader, { props });
36
37
  await nextTick();