@3cr/viewer-browser 0.0.220 → 0.0.246
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.
- package/components.d.ts +6 -2
- package/dist/Viewer3CR.js +32 -32
- package/dist/Viewer3CR.mjs +17808 -14315
- package/dist/Viewer3CR.umd.js +32 -32
- package/index.html +1 -1
- package/package.json +4 -3
- package/playground/index.html +4 -12
- package/src/App.vue +29 -14
- package/src/__tests__/main.spec.ts +4 -4
- package/src/assets/styles.scss +6 -2
- package/src/components/demo/DemoPatientModal.vue +12 -22
- package/src/components/demo/licence/DemoLicenceInfoModal.vue +11 -29
- package/src/components/demo/options.ts +42 -39
- package/src/components/demo/patient/DemoPatientInfoModal.vue +11 -29
- package/src/components/modal/CloseViewerModal.vue +1 -1
- package/src/components/modal/MftpWebGL3DRModal.vue +133 -120
- package/src/components/modal/ViewerActionRail.vue +0 -6
- package/src/components/modal/ViewerNavigationDrawerContent.vue +25 -16
- package/src/components/modal/ViewerNavigationDrawerFooter.vue +32 -9
- package/src/components/modal/ViewerNavigationDrawerHeader.vue +6 -1
- package/src/components/modal/WebGL3DR.vue +7 -0
- package/src/components/modal/__tests__/ViewerNavigationDrawerHeader.spec.ts +3 -2
- package/src/components/modal/buttons/AutoAnnotateBtn.vue +181 -13
- package/src/components/modal/menus/FileMenu.vue +2 -9
- package/src/components/modal/menus/SettingsMenu.vue +2 -7
- package/src/components/navigation/mcad/McadGlobalActions.vue +20 -0
- package/src/components/navigation/mcad/McadGlobalOpacitySlider.vue +103 -0
- package/src/components/navigation/mcad/McadGlobalScanViewBtn.vue +28 -0
- package/src/components/navigation/mcad/McadGlobalVisibilityBtn.vue +38 -0
- package/src/components/shared/LoadingSpinner.vue +27 -36
- package/src/components/views/AnnotationTreeView.vue +3 -1
- package/src/components/views/MarkupTreeView.vue +3 -1
- package/src/components/views/McadObjectTreeView.vue +46 -36
- package/src/components/views/modals/DataOverlayGeneralModal.vue +71 -0
- package/src/components/views/modals/DataOverlayMarkupModal.vue +60 -0
- package/src/components/views/modals/DataOverlayModal.vue +54 -55
- package/src/components/views/shared/Opacity.vue +0 -1
- package/src/composables/useDebounce.ts +11 -0
- package/src/composables/useIntroJs.ts +23 -6
- package/src/composables/useViewerOptions.ts +1 -0
- package/src/functions/guards/isDataOverlayAngle.ts +6 -0
- package/src/functions/guards/isDataOverlayLength.ts +6 -0
- package/src/functions/guards/isDataOverlayPolygon.ts +6 -0
- package/src/functions/modelHelper.ts +2 -0
- package/src/functions/notification.ts +63 -29
- package/src/main.ts +22 -13
- package/src/models/loadViewerOptions.ts +39 -0
- package/src/models/loadViewerPayload.ts +0 -7
- package/src/services/viewer-3cr.service.ts +13 -1
- package/src/tools/data-overlay.tool.ts +71 -0
- package/src/types/data-overlay-event.ts +5 -0
- package/src/types/data-overlay-info.ts +4 -0
- package/src/types/demo-type.ts +4 -0
- package/src/components/modal/actions/HideViewAction.vue +0 -38
- package/src/components/views/modals/DataOverlayModalManager.vue +0 -104
- package/src/components/views/modals/__tests__/DataOverlayModal.spec.ts +0 -33
- package/src/components/views/modals/__tests__/DataOverlayModalManager.spec.ts +0 -93
|
@@ -1,78 +1,69 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<v-
|
|
3
|
-
<v-
|
|
4
|
-
<v-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
>
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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
|
-
|
|
158
|
-
(
|
|
159
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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/
|
|
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:
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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 {
|
|
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:
|
|
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
|
-
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
|
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(
|
|
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(
|
|
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();
|