@3cr/viewer-browser 0.0.1
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/.browserslistrc +4 -0
- package/.editorconfig +5 -0
- package/.idea/git_toolbox_prj.xml +15 -0
- package/.idea/vcs.xml +6 -0
- package/.idea/workspace.xml +183 -0
- package/.vite/deps/_metadata.json +8 -0
- package/.vite/deps/package.json +3 -0
- package/README.md +81 -0
- package/components.d.ts +19 -0
- package/dist/3cr-viewer-browser.mjs +18644 -0
- package/dist/3cr-viewer-browser.umd.js +18 -0
- package/dist/style.css +5 -0
- package/index.html +24 -0
- package/index.ts +37 -0
- package/package.json +41 -0
- package/src/App.vue +40 -0
- package/src/assets/images/MainBackdrop.svg +48 -0
- package/src/assets/images/dark/3DICOM.png +0 -0
- package/src/assets/images/dark/3dicom-logo.svg +1 -0
- package/src/assets/images/dark/Singular-Health-Disc-Mono.svg +9 -0
- package/src/assets/images/dark/Singular-Health-Trademark-mono.svg +23 -0
- package/src/assets/images/light/3DICOM.png +0 -0
- package/src/assets/images/light/3dicom-logo.svg +1 -0
- package/src/assets/images/light/Singular-Health-Disc-Mono.svg +9 -0
- package/src/assets/images/light/Singular-Health-Trademark-mono.svg +23 -0
- package/src/assets/logo.png +0 -0
- package/src/assets/logo.svg +6 -0
- package/src/components/DoubleSliderSelector.vue +112 -0
- package/src/components/ExpansionHeaderMiniMenu.vue +19 -0
- package/src/components/HelloWorld.vue +157 -0
- package/src/components/LoadingSpinner.vue +157 -0
- package/src/components/MftpWebGL3DRModal.vue +995 -0
- package/src/components/README.md +35 -0
- package/src/components/SliderSelector.vue +101 -0
- package/src/components/VerticalSliderSelector.vue +83 -0
- package/src/components/WebGL3DR.vue +107 -0
- package/src/helpers/layoutOverlayStyle.ts +86 -0
- package/src/helpers/modelHelper.ts +109 -0
- package/src/helpers/models.ts +69 -0
- package/src/helpers/utils.ts +16 -0
- package/src/main.ts +23 -0
- package/src/plugins/README.md +3 -0
- package/src/plugins/index.ts +15 -0
- package/src/plugins/vuetify.ts +27 -0
- package/src/types/window.shim.ts +24 -0
- package/src/vite-env.d.ts +7 -0
- package/tsconfig.json +34 -0
- package/tsconfig.node.json +9 -0
- package/vite.config.mts +74 -0
|
@@ -0,0 +1,995 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<v-dialog
|
|
3
|
+
class="pa-0 ma-0 overflow-y-auto overflow-x-hidden"
|
|
4
|
+
:modelValue="value"
|
|
5
|
+
style="max-height: unset"
|
|
6
|
+
:scrollable="false"
|
|
7
|
+
fullscreen
|
|
8
|
+
persistent
|
|
9
|
+
@click:outside.prevent.stop="alterValue(false)"
|
|
10
|
+
@input="alterValue"
|
|
11
|
+
>
|
|
12
|
+
<v-dialog v-model="m_closeDialog">
|
|
13
|
+
<v-card class="pa-1 ma-auto position-relative" max-width="450">
|
|
14
|
+
<v-card-title>Close Viewer?</v-card-title>
|
|
15
|
+
<v-card-text>Are you sure you want to close the Online Viewer?</v-card-text>
|
|
16
|
+
<v-card-actions>
|
|
17
|
+
<v-btn variant="text" color="red" @click="m_closeDialog = false"> Cancel </v-btn>
|
|
18
|
+
<v-spacer />
|
|
19
|
+
<v-btn color="primary" @click="closeModal"> Close without saving </v-btn>
|
|
20
|
+
<v-btn color="primary" @click="closeModal"> Save Session </v-btn>
|
|
21
|
+
</v-card-actions>
|
|
22
|
+
</v-card>
|
|
23
|
+
</v-dialog>
|
|
24
|
+
<v-card
|
|
25
|
+
id="invoice-table-modal"
|
|
26
|
+
class="pa-0 ma-0 position-relative motif-background overflow-y-hidden rounded-0"
|
|
27
|
+
height="100vh"
|
|
28
|
+
>
|
|
29
|
+
<v-toolbar dense>
|
|
30
|
+
<v-menu :close-on-content-click="false" :close-on-click="true" open-on-hover offset-y>
|
|
31
|
+
<template #activator="{ props, isActive }">
|
|
32
|
+
<v-btn
|
|
33
|
+
v-bind="props"
|
|
34
|
+
:color="isActive ? 'secondary' : 'primary'"
|
|
35
|
+
class="ma-1 ml-n1 pa-1"
|
|
36
|
+
>
|
|
37
|
+
<v-icon>description</v-icon> File
|
|
38
|
+
</v-btn>
|
|
39
|
+
</template>
|
|
40
|
+
<v-card class="">
|
|
41
|
+
<v-list>
|
|
42
|
+
<v-list-item-group>
|
|
43
|
+
<v-list-item disabled>
|
|
44
|
+
<v-list-item-icon><v-icon> upload </v-icon></v-list-item-icon>
|
|
45
|
+
<v-list-item-title
|
|
46
|
+
>Load New DICOM Series <v-chip x-small color="success">Coming Soon</v-chip></v-list-item-title
|
|
47
|
+
>
|
|
48
|
+
</v-list-item>
|
|
49
|
+
<!-- <v-list-item @click="downloadDcm">-->
|
|
50
|
+
<!-- <v-list-item-icon><v-icon> download </v-icon></v-list-item-icon>-->
|
|
51
|
+
<!-- <v-list-item-title>Download DICOM Series</v-list-item-title>-->
|
|
52
|
+
<!-- </v-list-item>-->
|
|
53
|
+
<v-list-item disabled>
|
|
54
|
+
<v-list-item-icon><v-icon> sync </v-icon></v-list-item-icon>
|
|
55
|
+
<v-list-item-title
|
|
56
|
+
>Load Saved Session <v-chip x-small color="success">Coming Soon</v-chip></v-list-item-title
|
|
57
|
+
>
|
|
58
|
+
</v-list-item>
|
|
59
|
+
<v-list-item disabled>
|
|
60
|
+
<v-list-item-icon><v-icon> share </v-icon></v-list-item-icon>
|
|
61
|
+
<v-list-item-title>Share <v-chip x-small color="success">Coming Soon</v-chip></v-list-item-title>
|
|
62
|
+
</v-list-item>
|
|
63
|
+
<v-list-item @click="alterValue(false)">
|
|
64
|
+
<v-list-item-icon><v-icon> close </v-icon></v-list-item-icon>
|
|
65
|
+
<v-list-item-title>Close Viewer</v-list-item-title>
|
|
66
|
+
</v-list-item>
|
|
67
|
+
</v-list-item-group>
|
|
68
|
+
</v-list>
|
|
69
|
+
</v-card>
|
|
70
|
+
</v-menu>
|
|
71
|
+
<v-menu :close-on-content-click="false" :close-on-click="true" open-on-hover offset-y>
|
|
72
|
+
<template #activator="{ props, isActive }">
|
|
73
|
+
<v-btn v-bind="props" :color="isActive ? 'secondary' : 'primary'" class="">
|
|
74
|
+
<v-icon>settings</v-icon> Settings
|
|
75
|
+
</v-btn>
|
|
76
|
+
</template>
|
|
77
|
+
<v-card min-width="400" class="pb-2">
|
|
78
|
+
<v-card-subtitle>Viewer Settings</v-card-subtitle>
|
|
79
|
+
<SliderSelector
|
|
80
|
+
v-model="scanState.InteractionSettings.PanSensivitity"
|
|
81
|
+
:min="0"
|
|
82
|
+
:max="100"
|
|
83
|
+
label="Pan Sensitivity"
|
|
84
|
+
/>
|
|
85
|
+
<SliderSelector
|
|
86
|
+
v-model="scanState.InteractionSettings.ZoomSensitivity"
|
|
87
|
+
:min="0"
|
|
88
|
+
:max="100"
|
|
89
|
+
label="Zoom Sensitivity"
|
|
90
|
+
/>
|
|
91
|
+
<SliderSelector
|
|
92
|
+
v-model="scanState.InteractionSettings.RotateSensitivity"
|
|
93
|
+
:min="0"
|
|
94
|
+
:max="100"
|
|
95
|
+
label="Rotate Sensitivity"
|
|
96
|
+
/>
|
|
97
|
+
<SliderSelector
|
|
98
|
+
v-model="scanState.InteractionSettings.CameraRotateSensitivity"
|
|
99
|
+
:min="0"
|
|
100
|
+
:max="100"
|
|
101
|
+
label="Camera Rotate Sensitivity"
|
|
102
|
+
/>
|
|
103
|
+
</v-card>
|
|
104
|
+
</v-menu>
|
|
105
|
+
|
|
106
|
+
<v-spacer />
|
|
107
|
+
<div class="font-weight-bold">Not for Diagnostic Use</div>
|
|
108
|
+
<v-spacer />
|
|
109
|
+
<!-- <v-btn-->
|
|
110
|
+
<!-- class="ma-1 mr-0 pa-1"-->
|
|
111
|
+
<!-- height="36"-->
|
|
112
|
+
<!-- style="min-width: 36px !important"-->
|
|
113
|
+
<!-- :color="scanState.Layout.PositionData.length === 1 ? 'secondary' : 'primary'"-->
|
|
114
|
+
<!-- @click="layouts('lo_01')"-->
|
|
115
|
+
<!-- ><v-icon>fullscreen</v-icon></v-btn-->
|
|
116
|
+
<!-- >-->
|
|
117
|
+
<v-btn
|
|
118
|
+
class="ma-1 mr-0 pa-1"
|
|
119
|
+
height="36"
|
|
120
|
+
style="min-width: 36px !important"
|
|
121
|
+
:color="layoutIs2x2() ? 'secondary' : 'primary'"
|
|
122
|
+
@click="layouts('lo_02')"
|
|
123
|
+
><v-icon>grid_view</v-icon></v-btn
|
|
124
|
+
>
|
|
125
|
+
<v-btn
|
|
126
|
+
class="ma-1 mr-0 pa-1"
|
|
127
|
+
height="36"
|
|
128
|
+
style="min-width: 36px !important"
|
|
129
|
+
:color="layoutIs1x3() ? 'secondary' : 'primary'"
|
|
130
|
+
@click="layouts('lo_03')"
|
|
131
|
+
><v-icon style="rotate: -90deg">view_comfy</v-icon></v-btn
|
|
132
|
+
>
|
|
133
|
+
<v-btn class="ma-1 ml-1 pa-1 white--text mr-n1" color="red" @click="alterValue(false)">Close Viewer</v-btn>
|
|
134
|
+
</v-toolbar>
|
|
135
|
+
<v-navigation-drawer
|
|
136
|
+
v-model="drawer"
|
|
137
|
+
permanent
|
|
138
|
+
:mini-variant="drawerCollapsed"
|
|
139
|
+
mini-variant-width="68"
|
|
140
|
+
style="opacity: 0.95; margin-top: 48px; height: calc(100vh - 40px)"
|
|
141
|
+
absolute
|
|
142
|
+
dark
|
|
143
|
+
class="rounded-0 motif-background"
|
|
144
|
+
width="300px"
|
|
145
|
+
>
|
|
146
|
+
<template v-slot:prepend>
|
|
147
|
+
<div class="d-flex align-center pb-1" :class="drawerCollapsed ? 'py-2' : 'pa-2'" @click="snap">
|
|
148
|
+
<img
|
|
149
|
+
v-if="!drawerCollapsed"
|
|
150
|
+
src="@/assets/images/dark/3dicom-logo.svg"
|
|
151
|
+
height="48"
|
|
152
|
+
alt="logo"
|
|
153
|
+
class="ma-auto"
|
|
154
|
+
style="opacity: 0.9"
|
|
155
|
+
/>
|
|
156
|
+
<img
|
|
157
|
+
v-if="drawerCollapsed"
|
|
158
|
+
src="@/assets/images/dark/3DICOM.png"
|
|
159
|
+
height="64"
|
|
160
|
+
alt="logo"
|
|
161
|
+
class="ma-auto"
|
|
162
|
+
style="opacity: 0.9"
|
|
163
|
+
/>
|
|
164
|
+
</div>
|
|
165
|
+
<div
|
|
166
|
+
class="text-center mx-auto pa-0 white--text sub-type"
|
|
167
|
+
:style="drawerCollapsed ? 'font-size: 100%' : 'font-size: 140%'"
|
|
168
|
+
@click="snap"
|
|
169
|
+
>
|
|
170
|
+
Online Viewer
|
|
171
|
+
</div>
|
|
172
|
+
</template>
|
|
173
|
+
<template v-slot:append>
|
|
174
|
+
<v-divider></v-divider>
|
|
175
|
+
<v-list>
|
|
176
|
+
<v-tooltip right v-for="(item, index) in footerItems" :key="index">
|
|
177
|
+
<template #activator="{ props }">
|
|
178
|
+
<v-list-item
|
|
179
|
+
class="px-2"
|
|
180
|
+
:class="[drawerCollapsed && 'py-1']"
|
|
181
|
+
target="_blank"
|
|
182
|
+
v-bind="props"
|
|
183
|
+
:disabled="!(instanceLoaded && !scanLoading)"
|
|
184
|
+
@click="item.click()"
|
|
185
|
+
>
|
|
186
|
+
<v-list-item-icon
|
|
187
|
+
:class="[drawerCollapsed && 'mx-auto']"
|
|
188
|
+
:style="drawerCollapsed && 'margin-left: 7px !important; margin-right: 7px !important;'"
|
|
189
|
+
>
|
|
190
|
+
<v-icon :large="drawerCollapsed" :color="item.color">
|
|
191
|
+
{{ item.icon || 'radio_button_checked' }}
|
|
192
|
+
</v-icon>
|
|
193
|
+
</v-list-item-icon>
|
|
194
|
+
<v-list-item-content v-if="!drawerCollapsed">
|
|
195
|
+
<v-list-item-title>
|
|
196
|
+
{{ item.text }}
|
|
197
|
+
</v-list-item-title>
|
|
198
|
+
</v-list-item-content>
|
|
199
|
+
</v-list-item>
|
|
200
|
+
</template>
|
|
201
|
+
{{ item.text }}
|
|
202
|
+
</v-tooltip>
|
|
203
|
+
</v-list>
|
|
204
|
+
</template>
|
|
205
|
+
<v-list v-if="drawerCollapsed">
|
|
206
|
+
<v-tooltip right v-for="(item, index) in miniMenu" :key="item.text">
|
|
207
|
+
<template #activator="{ props }">
|
|
208
|
+
<v-list-item
|
|
209
|
+
:disabled="!(instanceLoaded && !scanLoading)"
|
|
210
|
+
class="px-2 py-1"
|
|
211
|
+
v-bind="props"
|
|
212
|
+
@click="
|
|
213
|
+
openPanels = index;
|
|
214
|
+
drawerCollapsed = false;
|
|
215
|
+
"
|
|
216
|
+
>
|
|
217
|
+
<v-list-item-icon class="mx-auto" style="margin-left: 7px !important; margin-right: 7px !important">
|
|
218
|
+
<v-icon large>
|
|
219
|
+
{{ item.icon || 'radio_button_checked' }}
|
|
220
|
+
</v-icon>
|
|
221
|
+
</v-list-item-icon>
|
|
222
|
+
</v-list-item>
|
|
223
|
+
</template>
|
|
224
|
+
{{ item.tooltip }}
|
|
225
|
+
</v-tooltip>
|
|
226
|
+
</v-list>
|
|
227
|
+
<v-expansion-panels
|
|
228
|
+
v-else
|
|
229
|
+
v-model="openPanels"
|
|
230
|
+
style="max-height: 95vh"
|
|
231
|
+
dark
|
|
232
|
+
accordion
|
|
233
|
+
class="mt-1 overflow-y-scroll transparent"
|
|
234
|
+
>
|
|
235
|
+
<v-expansion-panel class="transparent">
|
|
236
|
+
<ExpansionHeaderMiniMenu :mini-menu="miniMenu[0]" />
|
|
237
|
+
<v-expansion-panel-text>
|
|
238
|
+
<DoubleSliderSelector
|
|
239
|
+
v-model="windowSlider"
|
|
240
|
+
label="Skin to Bone"
|
|
241
|
+
:min="huMinMax.min"
|
|
242
|
+
:max="huMinMax.max"
|
|
243
|
+
/>
|
|
244
|
+
<DoubleSliderSelector v-model="thresholdSlider" label="Fine Adjustment" v-bind="huMinMax" />
|
|
245
|
+
<v-card-actions class="py-1">
|
|
246
|
+
<v-select
|
|
247
|
+
:value="getCurrentGreyscalePreset()"
|
|
248
|
+
:items="initialScanState.GreyscalePresets"
|
|
249
|
+
label="Greyscale Preset"
|
|
250
|
+
item-text="Name"
|
|
251
|
+
dense
|
|
252
|
+
outlined
|
|
253
|
+
persistent-placeholder
|
|
254
|
+
hide-details
|
|
255
|
+
return-object
|
|
256
|
+
:menu-props="{
|
|
257
|
+
closeOnContentClick: true,
|
|
258
|
+
}"
|
|
259
|
+
placeholder="Select a Density Preset"
|
|
260
|
+
@change="setPreset(PresetActions.pr01, $event)"
|
|
261
|
+
>
|
|
262
|
+
<template #item="{ props, item }">
|
|
263
|
+
<v-list-item
|
|
264
|
+
v-bind="props"
|
|
265
|
+
ripple
|
|
266
|
+
:title="item.raw.Name"
|
|
267
|
+
lines="three"
|
|
268
|
+
|
|
269
|
+
@mousedown.prevent
|
|
270
|
+
@click="setPreset(PresetActions.pr01, item.raw)"
|
|
271
|
+
>
|
|
272
|
+
<template v-slot:prepend>
|
|
273
|
+
<v-icon :icon="getIconForPreset(item.raw.Name)"></v-icon>
|
|
274
|
+
</template>
|
|
275
|
+
<template v-slot:subtitle>
|
|
276
|
+
Skin Density: <span class="text-mono">{{ item.raw.Lower }}</span>
|
|
277
|
+
<v-spacer />
|
|
278
|
+
Bone Density: <span class="text-mono">{{ item.raw.Upper }}</span>
|
|
279
|
+
</template>
|
|
280
|
+
</v-list-item>
|
|
281
|
+
</template>
|
|
282
|
+
<template v-slot:selection="{ item }">
|
|
283
|
+
<span v-if="item.raw === undefined || !item.raw.Name">None</span>
|
|
284
|
+
<span v-else>{{ item.raw.Name }}</span>
|
|
285
|
+
</template>
|
|
286
|
+
</v-select>
|
|
287
|
+
</v-card-actions>
|
|
288
|
+
<v-card-actions class="py-1">
|
|
289
|
+
<v-select
|
|
290
|
+
:value="currentColourPreset"
|
|
291
|
+
label="Colour Preset"
|
|
292
|
+
:items="initialScanState.ColourPresets"
|
|
293
|
+
item-text="Name"
|
|
294
|
+
dense
|
|
295
|
+
hide-details
|
|
296
|
+
outlined
|
|
297
|
+
:menu-props="{
|
|
298
|
+
closeOnContentClick: true,
|
|
299
|
+
}"
|
|
300
|
+
placeholder="Select a Colour Preset"
|
|
301
|
+
return-object
|
|
302
|
+
@change="setPreset(PresetActions.pr02, $event)"
|
|
303
|
+
></v-select>
|
|
304
|
+
</v-card-actions>
|
|
305
|
+
</v-expansion-panel-text>
|
|
306
|
+
</v-expansion-panel>
|
|
307
|
+
<v-expansion-panel class="transparent">
|
|
308
|
+
<ExpansionHeaderMiniMenu :mini-menu="miniMenu[1]" />
|
|
309
|
+
<v-expansion-panel-text>
|
|
310
|
+
<SliderSelector v-model="scanState.Display.Brightness" label="Adjust Brightness" />
|
|
311
|
+
<SliderSelector v-model="scanState.Display.Contrast" label="Adjust Contrast" />
|
|
312
|
+
<SliderSelector v-model="scanState.Display.Opacity" label="Adjust Opacity" />
|
|
313
|
+
</v-expansion-panel-text>
|
|
314
|
+
</v-expansion-panel>
|
|
315
|
+
</v-expansion-panels>
|
|
316
|
+
</v-navigation-drawer>
|
|
317
|
+
|
|
318
|
+
<v-btn
|
|
319
|
+
height="84"
|
|
320
|
+
width="20"
|
|
321
|
+
min-width="20"
|
|
322
|
+
class="my-auto rounded-bl-0 rounded-tl-0 pa-0 slide-item-card"
|
|
323
|
+
style="
|
|
324
|
+
border-bottom-left-radius: 0 !important;
|
|
325
|
+
border-top-left-radius: 0 !important;
|
|
326
|
+
position: absolute;
|
|
327
|
+
top: 50%;
|
|
328
|
+
z-index: 1000;
|
|
329
|
+
transform: translateY(-50%);
|
|
330
|
+
"
|
|
331
|
+
:style="drawerCollapsed ? 'left: 68px' : 'left: 300px'"
|
|
332
|
+
@click="drawerCollapsed = !drawerCollapsed"
|
|
333
|
+
>
|
|
334
|
+
<v-icon>{{ drawerCollapsed ? 'chevron_right' : 'chevron_left' }}</v-icon>
|
|
335
|
+
</v-btn>
|
|
336
|
+
|
|
337
|
+
<div class="position-relative pa-0" :style="drawerCollapsed ? 'margin-left: 68px' : 'margin-left: 300px'">
|
|
338
|
+
<WebGL3DR
|
|
339
|
+
v-show="instanceLoaded && !scanLoading"
|
|
340
|
+
:class="!(instanceLoaded && !scanLoading) && 'no-pointer-events'"
|
|
341
|
+
v-if="value"
|
|
342
|
+
ref="web_gl"
|
|
343
|
+
@on_payload="handleOnPayload"
|
|
344
|
+
@instance_loaded="emit('instanceLoaded');"
|
|
345
|
+
@hover="hoverOverCanvas"
|
|
346
|
+
>
|
|
347
|
+
<div
|
|
348
|
+
class="bordered-event-window"
|
|
349
|
+
v-for="layout in scanState.Layout.PositionData"
|
|
350
|
+
:key="layout.Anchor"
|
|
351
|
+
:style="{
|
|
352
|
+
...generateDivStyleForLayout(layout),
|
|
353
|
+
cursor: getCurrentActiveView(layout) === ScanView.Volume ? 'grab !important' : 'default',
|
|
354
|
+
}"
|
|
355
|
+
>
|
|
356
|
+
<v-hover>
|
|
357
|
+
<template v-slot:default="{ isHovering }">
|
|
358
|
+
<div style="width: 100%; height: 100%">
|
|
359
|
+
<div class="buttons-in-view" v-show="isHovering">
|
|
360
|
+
<div v-if="scanState.Layout.PositionData.length !== 1">
|
|
361
|
+
<v-tooltip top>
|
|
362
|
+
<template #activator="{ props }">
|
|
363
|
+
<v-btn
|
|
364
|
+
class="transparent icon"
|
|
365
|
+
v-bind="props"
|
|
366
|
+
icon
|
|
367
|
+
@click="fullscreenLayout(layout.DefaultView)"
|
|
368
|
+
><v-icon color="white">fullscreen</v-icon></v-btn
|
|
369
|
+
>
|
|
370
|
+
</template>
|
|
371
|
+
Make {{ getViewName(getCurrentActiveView(layout)) }} fullscreen
|
|
372
|
+
</v-tooltip>
|
|
373
|
+
</div>
|
|
374
|
+
<div v-if="scanState.Layout.PositionData.length === 1">
|
|
375
|
+
<v-tooltip top>
|
|
376
|
+
<template #activator="{ props }">
|
|
377
|
+
<v-btn
|
|
378
|
+
v-bind="props"
|
|
379
|
+
icon
|
|
380
|
+
class="transparent icon"
|
|
381
|
+
@click="layouts(previousLayout)"
|
|
382
|
+
><v-icon color="white">fullscreen_exit</v-icon></v-btn
|
|
383
|
+
>
|
|
384
|
+
</template>
|
|
385
|
+
Exit Fullscreen View
|
|
386
|
+
</v-tooltip>
|
|
387
|
+
</div>
|
|
388
|
+
<div v-if="getCurrentActiveView(layout) === ScanView.Volume">
|
|
389
|
+
<v-tooltip top>
|
|
390
|
+
<template #activator="{ props }">
|
|
391
|
+
<v-btn
|
|
392
|
+
v-bind="props"
|
|
393
|
+
icon
|
|
394
|
+
class="transparent icon"
|
|
395
|
+
@click="
|
|
396
|
+
viewSelection('vs_05');
|
|
397
|
+
viewSelection('vs_06');
|
|
398
|
+
"
|
|
399
|
+
><v-icon color="white">home</v-icon></v-btn
|
|
400
|
+
>
|
|
401
|
+
</template>
|
|
402
|
+
Reset volume to default view
|
|
403
|
+
</v-tooltip>
|
|
404
|
+
</div>
|
|
405
|
+
<div v-if="getCurrentActiveView(layout) === ScanView.Volume">
|
|
406
|
+
<v-menu :close-on-content-click="false" :close-on-click="true">
|
|
407
|
+
<template v-slot:activator="{ props }">
|
|
408
|
+
<v-tooltip top>
|
|
409
|
+
<template #activator="{ props: ttprops,}">
|
|
410
|
+
<v-btn
|
|
411
|
+
v-bind="{ ...ttprops, ...props }"
|
|
412
|
+
icon
|
|
413
|
+
class="icon transparent"
|
|
414
|
+
>
|
|
415
|
+
<v-icon color="white">cut</v-icon>
|
|
416
|
+
</v-btn>
|
|
417
|
+
</template>
|
|
418
|
+
Slice the 3D Volume
|
|
419
|
+
</v-tooltip>
|
|
420
|
+
</template>
|
|
421
|
+
<v-card min-width="400" class="pb-2">
|
|
422
|
+
<v-card-title>Slice into the 3D Volume</v-card-title>
|
|
423
|
+
<DoubleSliderSelector v-model="tSlider" label="Transverse" v-bind="tMinMax" />
|
|
424
|
+
<DoubleSliderSelector v-model="sSlider" label="Sagittal" v-bind="sMinMax" />
|
|
425
|
+
<DoubleSliderSelector v-model="cSlider" label="Coronal" v-bind="cMinMax" />
|
|
426
|
+
</v-card>
|
|
427
|
+
</v-menu>
|
|
428
|
+
</div>
|
|
429
|
+
<div v-if="getCurrentActiveView(layout) !== ScanView.Volume">
|
|
430
|
+
<v-menu :close-on-content-click="false" :close-on-click="true" offset-overflow top>
|
|
431
|
+
<template v-slot:activator="{ props }">
|
|
432
|
+
<v-tooltip top>
|
|
433
|
+
<template #activator="{ props: ttprops}">
|
|
434
|
+
<v-btn
|
|
435
|
+
v-bind="{ ...props, ...ttprops }"
|
|
436
|
+
icon
|
|
437
|
+
class="icon transparent"
|
|
438
|
+
>
|
|
439
|
+
<v-icon color="white">360</v-icon>
|
|
440
|
+
</v-btn>
|
|
441
|
+
</template>
|
|
442
|
+
Rotate {{ getViewName(getCurrentActiveView(layout)) }} by an angle
|
|
443
|
+
</v-tooltip>
|
|
444
|
+
</template>
|
|
445
|
+
<v-card min-width="200" width="200" class="pb-2">
|
|
446
|
+
<v-card-subtitle
|
|
447
|
+
>Rotate {{ getViewName(getCurrentActiveView(layout)) }} by an angle</v-card-subtitle
|
|
448
|
+
>
|
|
449
|
+
<v-card-text class="py-0">
|
|
450
|
+
<v-text-field
|
|
451
|
+
hide-details
|
|
452
|
+
outlined
|
|
453
|
+
v-model="rotationDeg"
|
|
454
|
+
type="number"
|
|
455
|
+
suffix="deg"
|
|
456
|
+
></v-text-field>
|
|
457
|
+
</v-card-text>
|
|
458
|
+
<v-card-actions>
|
|
459
|
+
<v-spacer />
|
|
460
|
+
<v-btn color="primary" @click="rotateByDeg(getCurrentActiveView(layout), rotationDeg)"
|
|
461
|
+
>Rotate</v-btn
|
|
462
|
+
>
|
|
463
|
+
</v-card-actions>
|
|
464
|
+
</v-card>
|
|
465
|
+
</v-menu>
|
|
466
|
+
</div>
|
|
467
|
+
</div>
|
|
468
|
+
<div class="slider-in-view" v-if="isHovering">
|
|
469
|
+
<VerticalSliderSelector
|
|
470
|
+
v-if="getCurrentActiveView(layout) === ScanView.Transverse"
|
|
471
|
+
v-model="scanState.Orientations.Transverse.Slice"
|
|
472
|
+
v-bind="tMinMax"
|
|
473
|
+
/>
|
|
474
|
+
<VerticalSliderSelector
|
|
475
|
+
v-if="getCurrentActiveView(layout) === ScanView.Coronal"
|
|
476
|
+
v-model="scanState.Orientations.Coronal.Slice"
|
|
477
|
+
v-bind="cMinMax"
|
|
478
|
+
/>
|
|
479
|
+
<VerticalSliderSelector
|
|
480
|
+
v-if="getCurrentActiveView(layout) === ScanView.Sagittal"
|
|
481
|
+
v-model="scanState.Orientations.Sagittal.Slice"
|
|
482
|
+
v-bind="sMinMax"
|
|
483
|
+
/>
|
|
484
|
+
</div>
|
|
485
|
+
|
|
486
|
+
<div class="top-lhc" v-if="isHovering">
|
|
487
|
+
<div class="white--text">
|
|
488
|
+
{{ getViewName(getCurrentActiveView(layout)) }}
|
|
489
|
+
</div>
|
|
490
|
+
</div>
|
|
491
|
+
</div>
|
|
492
|
+
</template>
|
|
493
|
+
</v-hover>
|
|
494
|
+
</div>
|
|
495
|
+
</WebGL3DR>
|
|
496
|
+
</div>
|
|
497
|
+
|
|
498
|
+
<LoadingSpinner v-if="!instanceLoaded" />
|
|
499
|
+
<LoadingSpinner v-if="scanLoading" text="Rendering your scan in <span class='sub-type'>3DICOM</span>" />
|
|
500
|
+
<v-textarea
|
|
501
|
+
v-if="stateOverlay"
|
|
502
|
+
style="position: absolute; top: 0px; right: -0px; width: 240px; z-index: 10000"
|
|
503
|
+
class="text--white"
|
|
504
|
+
color="white"
|
|
505
|
+
dark
|
|
506
|
+
outlined
|
|
507
|
+
height="800"
|
|
508
|
+
:value="JSON.stringify(scanState, null, 2)"
|
|
509
|
+
disabled
|
|
510
|
+
/>
|
|
511
|
+
</v-card>
|
|
512
|
+
</v-dialog>
|
|
513
|
+
</template>
|
|
514
|
+
<script setup lang="ts" >
|
|
515
|
+
|
|
516
|
+
import ExpansionHeaderMiniMenu from '@/components/ExpansionHeaderMiniMenu.vue';
|
|
517
|
+
import LoadingSpinner from '@/components/LoadingSpinner.vue';
|
|
518
|
+
import WebGL3DR from '@/components/WebGL3DR.vue';
|
|
519
|
+
import DoubleSliderSelector from '@/components/DoubleSliderSelector.vue';
|
|
520
|
+
import SliderSelector from '@/components/SliderSelector.vue';
|
|
521
|
+
import VerticalSliderSelector from '@/components/VerticalSliderSelector.vue';
|
|
522
|
+
|
|
523
|
+
import { generateDivStyleForLayout } from '@/helpers/layoutOverlayStyle';
|
|
524
|
+
import {
|
|
525
|
+
SliderActions,
|
|
526
|
+
PresetActions,
|
|
527
|
+
ScanMovementActions,
|
|
528
|
+
ScanOrientationActions,
|
|
529
|
+
FileManagementActions,
|
|
530
|
+
FrontEndInterfaces,
|
|
531
|
+
} from '@/helpers/models';
|
|
532
|
+
import {
|
|
533
|
+
inflateInitialScanState,
|
|
534
|
+
inflateScanState,
|
|
535
|
+
} from '../helpers/modelHelper';
|
|
536
|
+
import {
|
|
537
|
+
AnchorPoint,
|
|
538
|
+
ColourPresetData,
|
|
539
|
+
CurrentScanState,
|
|
540
|
+
InitialScanState,
|
|
541
|
+
PositionData,
|
|
542
|
+
ScanView,
|
|
543
|
+
} from '@3cr/types-ts';
|
|
544
|
+
|
|
545
|
+
import { toNumber } from '@/helpers/utils';
|
|
546
|
+
import {computed, ref, unref, watch, defineEmits, nextTick} from "vue";
|
|
547
|
+
import {LoadViewerPayload} from "../../index";
|
|
548
|
+
|
|
549
|
+
const emit = defineEmits<{
|
|
550
|
+
instanceLoaded: [void];
|
|
551
|
+
}>();
|
|
552
|
+
|
|
553
|
+
defineExpose({
|
|
554
|
+
alterValue,
|
|
555
|
+
load
|
|
556
|
+
});
|
|
557
|
+
|
|
558
|
+
const emptyPayload = JSON.stringify({ Version: '1.1.0' });
|
|
559
|
+
|
|
560
|
+
const value = ref<boolean>(true);
|
|
561
|
+
const drawer = ref<boolean>(true);
|
|
562
|
+
const drawerCollapsed = ref<boolean>(true);
|
|
563
|
+
const scanLoading = ref<boolean>(true);
|
|
564
|
+
const instanceLoaded = ref<boolean>(true);
|
|
565
|
+
const scanState = ref<CurrentScanState>(inflateScanState());
|
|
566
|
+
const initialScanState = ref<InitialScanState>(inflateInitialScanState());
|
|
567
|
+
const currentColourPreset = ref<ColourPresetData | null>(null);
|
|
568
|
+
const openPanels = ref<number>(0);
|
|
569
|
+
const previousLayout = ref<string>('lo_02');
|
|
570
|
+
const footerItems = ref([
|
|
571
|
+
{
|
|
572
|
+
text: 'Reset Scan',
|
|
573
|
+
icon: 'refresh',
|
|
574
|
+
color: 'red',
|
|
575
|
+
click: () => {
|
|
576
|
+
//@ts-ignore
|
|
577
|
+
viewSelection('vs_05');
|
|
578
|
+
//@ts-ignore
|
|
579
|
+
viewSelection('vs_06');
|
|
580
|
+
},
|
|
581
|
+
},
|
|
582
|
+
{
|
|
583
|
+
text: 'Send to 3rd Party',
|
|
584
|
+
icon: 'send',
|
|
585
|
+
color: 'blue',
|
|
586
|
+
//@ts-ignore
|
|
587
|
+
click: () => alterValue(false),
|
|
588
|
+
},
|
|
589
|
+
{
|
|
590
|
+
text: 'Share to Mobile / VR',
|
|
591
|
+
icon: 'share',
|
|
592
|
+
color: 'yellow',
|
|
593
|
+
//@ts-ignore
|
|
594
|
+
click: () => alterValue(false),
|
|
595
|
+
},
|
|
596
|
+
{
|
|
597
|
+
text: 'Screenshot View',
|
|
598
|
+
icon: 'screenshot_region',
|
|
599
|
+
color: 'green',
|
|
600
|
+
click: () => {
|
|
601
|
+
//@ts-ignore
|
|
602
|
+
snap();
|
|
603
|
+
},
|
|
604
|
+
},
|
|
605
|
+
]);
|
|
606
|
+
|
|
607
|
+
const miniMenu = ref([
|
|
608
|
+
{
|
|
609
|
+
icon: 'display_settings',
|
|
610
|
+
text: 'Tissue Density',
|
|
611
|
+
tooltip:
|
|
612
|
+
'Tissue Density - Change the view of density within the scan, allowing you to see through a certain density of particles',
|
|
613
|
+
},
|
|
614
|
+
{ icon: 'display_settings', text: 'Display', tooltip: 'Display - Alter the view of the current scan' },
|
|
615
|
+
])
|
|
616
|
+
|
|
617
|
+
const huMinMax = ref({
|
|
618
|
+
min: -999999,
|
|
619
|
+
max: 999999,
|
|
620
|
+
})
|
|
621
|
+
|
|
622
|
+
const tMinMax = ref({
|
|
623
|
+
min: 0,
|
|
624
|
+
max: 999999,
|
|
625
|
+
})
|
|
626
|
+
const sMinMax = ref({
|
|
627
|
+
min: 0,
|
|
628
|
+
max: 999999,
|
|
629
|
+
})
|
|
630
|
+
const cMinMax = ref({
|
|
631
|
+
min: 0,
|
|
632
|
+
max: 999999,
|
|
633
|
+
})
|
|
634
|
+
const rotationDeg = ref<number>(0)
|
|
635
|
+
const scanStateIncoming = ref<string>('')
|
|
636
|
+
const transactionStarted = ref<boolean>(false)
|
|
637
|
+
const stateOverlay = ref<boolean>(false)
|
|
638
|
+
const m_closeDialog = ref<boolean>(false)
|
|
639
|
+
|
|
640
|
+
const windowSlider = computed({
|
|
641
|
+
get() {
|
|
642
|
+
return [unref(scanState).Display.WindowLower, unref(scanState).Display.WindowUpper];
|
|
643
|
+
},
|
|
644
|
+
set(value: Array<number>) {
|
|
645
|
+
scanState.value.Display.WindowLower = value[0];
|
|
646
|
+
scanState.value.Display.WindowUpper = value[1];
|
|
647
|
+
}
|
|
648
|
+
})
|
|
649
|
+
const thresholdSlider = computed({
|
|
650
|
+
get() {
|
|
651
|
+
return [Math.trunc(unref(scanState).Display.ThresholdLower), Math.trunc(unref(scanState).Display.ThresholdUpper)];
|
|
652
|
+
},
|
|
653
|
+
set(value: Array<number>) {
|
|
654
|
+
scanState.value.Display.ThresholdLower = value[0];
|
|
655
|
+
scanState.value.Display.ThresholdUpper = value[1];
|
|
656
|
+
}
|
|
657
|
+
})
|
|
658
|
+
const tSlider = computed({
|
|
659
|
+
get() {
|
|
660
|
+
return [Math.trunc(unref(scanState).Slice.TransverseLower), Math.trunc(unref(scanState).Slice.TransverseUpper)];
|
|
661
|
+
},
|
|
662
|
+
set(value: Array<number>) {
|
|
663
|
+
scanState.value.Slice.TransverseLower = value[0];
|
|
664
|
+
scanState.value.Slice.TransverseLower = value[1];
|
|
665
|
+
}
|
|
666
|
+
})
|
|
667
|
+
const sSlider = computed({
|
|
668
|
+
get() {
|
|
669
|
+
return [Math.trunc(unref(scanState).Slice.SagittalLower), Math.trunc(unref(scanState).Slice.SagittalUpper)];
|
|
670
|
+
},
|
|
671
|
+
set(value: Array<number>) {
|
|
672
|
+
scanState.value.Slice.SagittalLower = value[0];
|
|
673
|
+
scanState.value.Slice.SagittalUpper = value[1];
|
|
674
|
+
}
|
|
675
|
+
})
|
|
676
|
+
|
|
677
|
+
const cSlider = computed({
|
|
678
|
+
get() {
|
|
679
|
+
return [Math.trunc(unref(scanState).Slice.CoronalLower), Math.trunc(unref(scanState).Slice.CoronalUpper)];
|
|
680
|
+
},
|
|
681
|
+
set(value: Array<number>) {
|
|
682
|
+
scanState.value.Slice.CoronalLower = value[0];
|
|
683
|
+
scanState.value.Slice.CoronalUpper = value[1];
|
|
684
|
+
}
|
|
685
|
+
})
|
|
686
|
+
const web_gl = ref<typeof WebGL3DR | null>(null);
|
|
687
|
+
|
|
688
|
+
|
|
689
|
+
watch(() => scanState.value.Display.Brightness, async (value: number) => {
|
|
690
|
+
await sliderHandler(SliderActions.sl01, value);
|
|
691
|
+
})
|
|
692
|
+
watch(() => scanState.value.Display.Contrast, async (value: number) => {
|
|
693
|
+
await sliderHandler(SliderActions.sl02, value);
|
|
694
|
+
})
|
|
695
|
+
watch(() => scanState.value.Display.Opacity, async (value: number) => {
|
|
696
|
+
await sliderHandler(SliderActions.sl03, value);
|
|
697
|
+
})
|
|
698
|
+
watch(() => scanState.value.Display.WindowLower, async (value: number) => {
|
|
699
|
+
await sliderHandler(SliderActions.sl04, value);
|
|
700
|
+
})
|
|
701
|
+
watch(() => scanState.value.Display.WindowUpper, async (value: number) => {
|
|
702
|
+
await sliderHandler(SliderActions.sl05, value);
|
|
703
|
+
})
|
|
704
|
+
watch(() => scanState.value.Display.ThresholdLower, async (value: number) => {
|
|
705
|
+
await sliderHandler(SliderActions.sl06, value);
|
|
706
|
+
})
|
|
707
|
+
watch(() => scanState.value.Display.ThresholdUpper, async (value: number) => {
|
|
708
|
+
await sliderHandler(SliderActions.sl07, value);
|
|
709
|
+
})
|
|
710
|
+
watch(() => scanState.value.Slice.TransverseLower, async (value: number) => {
|
|
711
|
+
await sliderHandler(SliderActions.sl08, value);
|
|
712
|
+
})
|
|
713
|
+
watch(() => scanState.value.Orientations.Transverse.Slice, async (value: number) => {
|
|
714
|
+
await sliderHandler(SliderActions.sl09, value);
|
|
715
|
+
})
|
|
716
|
+
watch(() => scanState.value.Slice.TransverseUpper, async (value: number) => {
|
|
717
|
+
await sliderHandler(SliderActions.sl10, value);
|
|
718
|
+
})
|
|
719
|
+
watch(() => scanState.value.Slice.SagittalLower, async (value: number) => {
|
|
720
|
+
await sliderHandler(SliderActions.sl11, value);
|
|
721
|
+
})
|
|
722
|
+
watch(() => scanState.value.Orientations.Sagittal.Slice, async (value: number) => {
|
|
723
|
+
await sliderHandler(SliderActions.sl12, value);
|
|
724
|
+
})
|
|
725
|
+
watch(() => scanState.value.Slice.SagittalUpper, async (value: number) => {
|
|
726
|
+
await sliderHandler(SliderActions.sl13, value);
|
|
727
|
+
})
|
|
728
|
+
watch(() => scanState.value.Slice.CoronalLower, async (value: number) => {
|
|
729
|
+
await sliderHandler(SliderActions.sl14, value);
|
|
730
|
+
})
|
|
731
|
+
watch(() => scanState.value.Orientations.Coronal.Slice, async (value: number) => {
|
|
732
|
+
await sliderHandler(SliderActions.sl15, value);
|
|
733
|
+
})
|
|
734
|
+
watch(() => scanState.value.Slice.CoronalUpper, async (value: number) => {
|
|
735
|
+
await sliderHandler(SliderActions.sl16, value);
|
|
736
|
+
})
|
|
737
|
+
watch(() => scanState.value.InteractionSettings.PanSensivitity, async (value: number) => {
|
|
738
|
+
await scanMovementHandler(ScanMovementActions.sm05, value);
|
|
739
|
+
})
|
|
740
|
+
watch(() => scanState.value.InteractionSettings.ZoomSensitivity, async (value: number) => {
|
|
741
|
+
await scanMovementHandler(ScanMovementActions.sm08, value);
|
|
742
|
+
})
|
|
743
|
+
watch(() => scanState.value.InteractionSettings.RotateSensitivity, async (value: number) => {
|
|
744
|
+
await scanMovementHandler(ScanMovementActions.sm10, value);
|
|
745
|
+
})
|
|
746
|
+
watch(() => scanState.value.InteractionSettings.CameraRotateSensitivity, async (value: number) => {
|
|
747
|
+
await scanMovementHandler(ScanMovementActions.sm12, value);
|
|
748
|
+
})
|
|
749
|
+
|
|
750
|
+
function getIconForPreset(presetName: string): string | undefined {
|
|
751
|
+
if (presetName === 'Bone') return 'fa-solid fa-bone';
|
|
752
|
+
if (presetName === 'Brain') return 'fa-solid fa-brain';
|
|
753
|
+
if (presetName === 'Liver') return '$liver_icon';
|
|
754
|
+
if (presetName === 'Lungs') return 'fa-solid fa-lungs';
|
|
755
|
+
if (presetName === 'Muscle') return '$muscle_icon';
|
|
756
|
+
if (presetName === 'Temporal Bones') return '$temporal_bones_icon';
|
|
757
|
+
if (presetName === 'Soft Tissue') return '$torso_icon';
|
|
758
|
+
if (presetName === 'Skin') return '$skin_icon';
|
|
759
|
+
return undefined
|
|
760
|
+
}
|
|
761
|
+
async function rotateByDeg(view: ScanView, deg: number) {
|
|
762
|
+
await sendPayload(FrontEndInterfaces.scan_orientation, ScanOrientationActions.so01, {
|
|
763
|
+
Version: '0.0.1',
|
|
764
|
+
View: view,
|
|
765
|
+
Angle: deg,
|
|
766
|
+
});
|
|
767
|
+
}
|
|
768
|
+
function getCurrentActiveView(position: PositionData): ScanView {
|
|
769
|
+
return unref(scanState).Layout.PositionData.length !== 1 ? position.DefaultView : unref(scanState).CurrentView;
|
|
770
|
+
}
|
|
771
|
+
function getViewName(index: number) {
|
|
772
|
+
return Object.values(ScanView)
|
|
773
|
+
.filter((value) => typeof value === 'string')
|
|
774
|
+
.map((x) => {
|
|
775
|
+
if (x === 'Volume') return '3D Volume';
|
|
776
|
+
return x;
|
|
777
|
+
})[index];
|
|
778
|
+
}
|
|
779
|
+
async function fullscreenLayout(view: ScanView) {
|
|
780
|
+
await layouts('lo_01');
|
|
781
|
+
await viewSelection(`vs_0${view + 1}`);
|
|
782
|
+
}
|
|
783
|
+
// generateDivStyleForLayout,
|
|
784
|
+
async function scanMovementHandler(action: string, value: number) {
|
|
785
|
+
if (unref(transactionStarted)) return;
|
|
786
|
+
await scanMovement(action, value);
|
|
787
|
+
}
|
|
788
|
+
async function sliderHandler(action: string, value: number) {
|
|
789
|
+
if (unref(transactionStarted)) return;
|
|
790
|
+
await slider(action, value);
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
function closeModal() {
|
|
794
|
+
m_closeDialog.value = false;
|
|
795
|
+
value.value = false;
|
|
796
|
+
}
|
|
797
|
+
function alterValue(val: boolean) {
|
|
798
|
+
if (!val) {
|
|
799
|
+
m_closeDialog.value = true;
|
|
800
|
+
return
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
value.value = val;
|
|
804
|
+
}
|
|
805
|
+
async function sendPayload(interfaceType: string, actionType: string, message: any) {
|
|
806
|
+
await unref(web_gl)?.sendPayload(
|
|
807
|
+
JSON.stringify({
|
|
808
|
+
Version: '0.0.1',
|
|
809
|
+
Interface: interfaceType,
|
|
810
|
+
Action: actionType,
|
|
811
|
+
Message: JSON.stringify(message),
|
|
812
|
+
}),
|
|
813
|
+
);
|
|
814
|
+
}
|
|
815
|
+
async function load(downloadResponse: LoadViewerPayload) {
|
|
816
|
+
instanceLoaded.value = true;
|
|
817
|
+
scanLoading.value = true;
|
|
818
|
+
await sendPayload('file_management', 'fm_01', downloadResponse);
|
|
819
|
+
}
|
|
820
|
+
async function viewSelection(action: string) {
|
|
821
|
+
await sendPayload('view_selection', action, emptyPayload)
|
|
822
|
+
}
|
|
823
|
+
async function layouts(action: string) {
|
|
824
|
+
if (action !== 'lo_01') previousLayout.value = action;
|
|
825
|
+
await sendPayload('layout', action, emptyPayload)
|
|
826
|
+
}
|
|
827
|
+
async function snap() {
|
|
828
|
+
// await unref(web_gl).snap();
|
|
829
|
+
}
|
|
830
|
+
async function setPreset(action: PresetActions, preset: any) {
|
|
831
|
+
await sendPayload('presets', action, preset)
|
|
832
|
+
if (action === PresetActions.pr02) {
|
|
833
|
+
currentColourPreset.value = preset;
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
async function slider(action: string, value: number) {
|
|
837
|
+
await sendPayload('sliders', action, { Version: '0.0.1', Value: toNumber(value) })
|
|
838
|
+
}
|
|
839
|
+
async function scanMovement(action: string, value: number) {
|
|
840
|
+
await sendPayload('scan_movement', action, { Version: '0.0.1', Value: toNumber(value) })
|
|
841
|
+
}
|
|
842
|
+
async function hoverOverCanvas(isHovering: boolean) {
|
|
843
|
+
await sendPayload('interactivity', 'in_01', { Version: '0.0.1', Value: isHovering })
|
|
844
|
+
await sendPayload('interactivity', 'in_02', { Version: '0.0.1', Value: isHovering })
|
|
845
|
+
}
|
|
846
|
+
async function i_scanState(_: string, message: string) {
|
|
847
|
+
transactionStarted.value = true;
|
|
848
|
+
scanStateIncoming.value = JSON.stringify(JSON.parse(message), null, 2);
|
|
849
|
+
|
|
850
|
+
setScanState(message);
|
|
851
|
+
await nextTick();
|
|
852
|
+
transactionStarted.value = false;
|
|
853
|
+
}
|
|
854
|
+
async function i_fileManagement(action: string, message: string) {
|
|
855
|
+
if (action !== FileManagementActions.fm02) return;
|
|
856
|
+
transactionStarted.value = true;
|
|
857
|
+
|
|
858
|
+
scanStateIncoming.value = JSON.stringify(JSON.parse(message), null, 2);
|
|
859
|
+
const obj = JSON.parse(unref(scanStateIncoming)) as InitialScanState;
|
|
860
|
+
|
|
861
|
+
setScanState(JSON.stringify(obj.DefaultDisplaySettings, null, 2));
|
|
862
|
+
initialScanState.value = obj;
|
|
863
|
+
huMinMax.value.max = obj.HuUpper;
|
|
864
|
+
huMinMax.value.min = obj.HuLower;
|
|
865
|
+
sMinMax.value.max = obj.XSlices;
|
|
866
|
+
cMinMax.value.max = obj.YSlices;
|
|
867
|
+
tMinMax.value.max = obj.ZSlices;
|
|
868
|
+
currentColourPreset.value = obj.ColourPresets[0];
|
|
869
|
+
|
|
870
|
+
await nextTick();
|
|
871
|
+
transactionStarted.value = false;
|
|
872
|
+
scanLoading.value = false;
|
|
873
|
+
drawerCollapsed.value = false;
|
|
874
|
+
await hoverOverCanvas(false);
|
|
875
|
+
}
|
|
876
|
+
async function i_notifications(action: string, message: string) {
|
|
877
|
+
// const obj = JSON.parse(message);
|
|
878
|
+
// console.log(obj);
|
|
879
|
+
//{\"Version\":\"1.0.0\",\"Interface\":\"interactivity\",\"Action\":\"in_01\",\"Code\":\"0000\",\"Description\":\"\"}"
|
|
880
|
+
|
|
881
|
+
// if (obj.Description) {
|
|
882
|
+
// if (action === NotificationActions.no01) {
|
|
883
|
+
// await NotificationService.Instantiate().notificationSuccess(obj.Description);
|
|
884
|
+
// }
|
|
885
|
+
// if (action === NotificationActions.no02) {
|
|
886
|
+
// await NotificationService.Instantiate().notificationError(obj.Description);
|
|
887
|
+
// }
|
|
888
|
+
// if (action === NotificationActions.no03) {
|
|
889
|
+
// await NotificationService.Instantiate().notificationWarning(obj.Description);
|
|
890
|
+
// }
|
|
891
|
+
// }
|
|
892
|
+
}
|
|
893
|
+
function layoutIs2x2() {
|
|
894
|
+
return (
|
|
895
|
+
unref(scanState).Layout.PositionData.length > 1 &&
|
|
896
|
+
unref(scanState).Layout.PositionData[0].Anchor === AnchorPoint.TOP_LEFT &&
|
|
897
|
+
unref(scanState).Layout.PositionData[1].Anchor === AnchorPoint.TOP_RIGHT &&
|
|
898
|
+
unref(scanState).Layout.PositionData[2].Anchor === AnchorPoint.BOTTOM_LEFT &&
|
|
899
|
+
unref(scanState).Layout.PositionData[3].Anchor === AnchorPoint.BOTTOM_RIGHT
|
|
900
|
+
);
|
|
901
|
+
}
|
|
902
|
+
function layoutIs1x3() {
|
|
903
|
+
return (
|
|
904
|
+
unref(scanState).Layout.PositionData.length > 1 &&
|
|
905
|
+
unref(scanState).Layout.PositionData[0].Anchor === AnchorPoint.CENTER &&
|
|
906
|
+
unref(scanState).Layout.PositionData[1].Anchor === AnchorPoint.TOP_RIGHT &&
|
|
907
|
+
unref(scanState).Layout.PositionData[2].Anchor === AnchorPoint.RIGHT &&
|
|
908
|
+
unref(scanState).Layout.PositionData[3].Anchor === AnchorPoint.BOTTOM_RIGHT
|
|
909
|
+
);
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
function getCurrentGreyscalePreset() {
|
|
913
|
+
for (const preset of unref(initialScanState).GreyscalePresets) {
|
|
914
|
+
if (
|
|
915
|
+
(unref(scanState).Display.WindowLower === preset.Lower ||
|
|
916
|
+
(preset.Lower > unref(initialScanState).HuLower &&
|
|
917
|
+
unref(scanState).Display.WindowLower === unref(initialScanState).HuLower)) &&
|
|
918
|
+
(unref(scanState).Display.WindowUpper === preset.Upper ||
|
|
919
|
+
(preset.Upper > unref(initialScanState).HuUpper &&
|
|
920
|
+
unref(scanState).Display.WindowUpper === unref(initialScanState).HuUpper))
|
|
921
|
+
) {
|
|
922
|
+
return preset.Name;
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
return undefined;
|
|
926
|
+
}
|
|
927
|
+
function setScanState(message: string) {
|
|
928
|
+
scanState.value = JSON.parse(message) as CurrentScanState;
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
function handleOnPayload(interfaceSet: string | FrontEndInterfaces, actionSet: string, message: string) {
|
|
932
|
+
if (interfaceSet === FrontEndInterfaces.scan_loading) {
|
|
933
|
+
i_scanState(actionSet, message)
|
|
934
|
+
}
|
|
935
|
+
if (interfaceSet === FrontEndInterfaces.file_management) {
|
|
936
|
+
i_fileManagement(actionSet, message)
|
|
937
|
+
}
|
|
938
|
+
if (interfaceSet === 'notifications') {
|
|
939
|
+
i_notifications(actionSet, message)
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
</script>
|
|
944
|
+
<style>
|
|
945
|
+
.v-dialog:not(.v-dialog--fullscreen) {
|
|
946
|
+
max-height: unset !important;
|
|
947
|
+
}
|
|
948
|
+
.v-expansion-panel-content__wrap {
|
|
949
|
+
padding: 0 8px 4px;
|
|
950
|
+
flex: 1 1 auto;
|
|
951
|
+
max-width: 100%;
|
|
952
|
+
}
|
|
953
|
+
.bordered-event-window {
|
|
954
|
+
position: absolute;
|
|
955
|
+
border: 1px rgba(128, 128, 128, 0.56) solid;
|
|
956
|
+
pointer-events: auto;
|
|
957
|
+
}
|
|
958
|
+
.bordered-event-window:hover {
|
|
959
|
+
position: absolute;
|
|
960
|
+
border: 2px dashed rgb(0, 152, 253);
|
|
961
|
+
//pointer-events: none;
|
|
962
|
+
}
|
|
963
|
+
.v-expansion-panel--active > .v-expansion-panel-header {
|
|
964
|
+
min-height: 48px;
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
.btn-close-dense {
|
|
968
|
+
position: absolute;
|
|
969
|
+
right: 0;
|
|
970
|
+
top: 0;
|
|
971
|
+
}
|
|
972
|
+
.v-slider.v-slider--vertical {
|
|
973
|
+
height: 90%;
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
.slider-in-view {
|
|
977
|
+
position: absolute;
|
|
978
|
+
right: 16px;
|
|
979
|
+
top: 0;
|
|
980
|
+
height: 100%;
|
|
981
|
+
}
|
|
982
|
+
.buttons-in-view {
|
|
983
|
+
position: absolute;
|
|
984
|
+
left: 12px;
|
|
985
|
+
bottom: 12px;
|
|
986
|
+
}
|
|
987
|
+
.top-lhc {
|
|
988
|
+
position: absolute;
|
|
989
|
+
left: 16px;
|
|
990
|
+
top: 12px;
|
|
991
|
+
}
|
|
992
|
+
.buttons-in-view > div {
|
|
993
|
+
display: inline-block;
|
|
994
|
+
}
|
|
995
|
+
</style>
|