@3cr/viewer-browser 0.0.162 → 0.0.195
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/.circleci/config.yml +8 -6
- package/__tests__/index.spec.ts +31 -24
- package/components.d.ts +32 -0
- package/dist/Viewer3CR.js +44 -27
- package/dist/Viewer3CR.mjs +22731 -17742
- package/dist/Viewer3CR.umd.js +44 -27
- package/index.html +5 -8
- package/index.ts +7 -2
- package/package.json +4 -2
- package/playground/index.html +6 -4
- package/src/App.vue +12 -4
- package/src/__tests__/app.spec.ts +2 -13
- package/src/assets/magic_wand.svg +24 -0
- package/src/components/WebGL3DR.vue +53 -92
- package/src/components/__tests__/webgl3dr.spec.ts +29 -48
- package/src/{demo → components/demo}/DemoModal.vue +1 -1
- package/src/{demo → components/demo}/DemoPatientModal.vue +4 -1
- package/src/components/demo/__tests__/DemoModal.spec.ts +25 -0
- package/src/components/demo/__tests__/DemoPatientModal.spec.ts +37 -0
- package/src/components/demo/__tests__/options.spec.ts +25 -0
- package/src/components/demo/licence/DemoLicenceEnableCloudStorageModal.vue +64 -0
- package/src/{demo → components/demo}/licence/DemoLicenceInfoModal.vue +5 -4
- package/src/{demo → components/demo}/licence/DemoLicenceSendToPartyModal.vue +6 -4
- package/src/{demo → components/demo}/licence/DemoLicenceShareToMobileModal.vue +8 -6
- package/src/components/demo/licence/__tests__/DemoLicenceEnableCloudStorageModal.spec.ts +37 -0
- package/src/components/demo/licence/__tests__/DemoLicenceInfoModal.spec.ts +37 -0
- package/src/components/demo/licence/__tests__/DemoLicenceSendToPartyModal.spec.ts +36 -0
- package/src/components/demo/licence/__tests__/DemoLicenceShareToMobileModal.spec.ts +51 -0
- package/src/{demo → components/demo}/options.ts +18 -20
- package/src/components/demo/patient/DemoPatientEnableCloudStorageModal.vue +64 -0
- package/src/{demo → components/demo}/patient/DemoPatientInfoModal.vue +5 -4
- package/src/{demo → components/demo}/patient/DemoPatientSendToPartyModal.vue +4 -3
- package/src/{demo → components/demo}/patient/DemoPatientShareToMobileModal.vue +8 -6
- package/src/components/demo/patient/__tests__/DemoPatientEnableCloudStorageModal.spec.ts +37 -0
- package/src/components/demo/patient/__tests__/DemoPatientInfoModal.spec.ts +37 -0
- package/src/components/demo/patient/__tests__/DemoPatientSendToPartyModal.spec.ts +36 -0
- package/src/components/demo/patient/__tests__/DemoPatientShareToMobileModal.spec.ts +51 -0
- package/src/components/loading/LoadingSpinner.vue +1 -1
- package/src/components/modal/ActionRail.vue +96 -0
- package/src/components/modal/AskAI.vue +250 -0
- package/src/components/modal/CloseViewerModal.vue +104 -0
- package/src/components/modal/MftpWebGL3DRModal.vue +415 -834
- package/src/components/modal/ViewerActionRail.vue +123 -0
- package/src/components/modal/ViewerAnnotationModal.vue +115 -0
- package/src/components/modal/ViewerAnnotations.vue +283 -0
- package/src/components/modal/ViewerDisplaySettings.vue +102 -0
- package/src/components/modal/ViewerNavigationDrawer.vue +90 -0
- package/src/components/modal/ViewerNavigationDrawerContent.vue +126 -0
- package/src/components/modal/ViewerNavigationDrawerFooter.vue +111 -0
- package/src/components/modal/ViewerNavigationDrawerHeader.vue +63 -0
- package/src/components/modal/__tests__/CloseViewerModal.spec.ts +60 -0
- package/src/components/modal/__tests__/{mftp-webgl-3dr-modal.spec.ts → MftpWebGL3DRModal.spec.ts} +47 -298
- package/src/components/modal/__tests__/ViewerAnnotationModal.spec.ts +79 -0
- package/src/components/modal/__tests__/ViewerDisplaySettings.spec.ts +61 -0
- package/src/components/modal/__tests__/ViewerNavigationDrawer.spec.ts +32 -0
- package/src/components/modal/__tests__/ViewerNavigationDrawerContent.spec.ts +29 -0
- package/src/components/modal/__tests__/ViewerNavigationDrawerFooter.spec.ts +43 -0
- package/src/components/modal/__tests__/ViewerNavigationDrawerHeader.spec.ts +37 -0
- package/src/components/modal/actions/Action.vue +40 -0
- package/src/components/modal/actions/Flip3dAction.vue +79 -0
- package/src/components/modal/actions/FlipHorizontalAction.vue +36 -0
- package/src/components/modal/actions/FlipVerticalAction.vue +36 -0
- package/src/components/modal/actions/FullscreenAction.vue +47 -0
- package/src/components/modal/actions/NavigationCubeAction.vue +33 -0
- package/src/components/modal/actions/PanAction.vue +78 -0
- package/src/components/modal/actions/ResetViewAction.vue +29 -0
- package/src/components/modal/actions/Rotate2dAction.vue +78 -0
- package/src/components/modal/actions/Slice3dAction.vue +71 -0
- package/src/components/modal/actions/ZoomAction.vue +70 -0
- package/src/components/modal/actions/__tests__/Action.spec.ts +29 -0
- package/src/components/modal/actions/__tests__/Flip3dAction.spec.ts +48 -0
- package/src/components/modal/actions/__tests__/FlipHorizontalAction.spec.ts +17 -0
- package/src/components/modal/actions/__tests__/FlipVerticalAction.spec.ts +17 -0
- package/src/components/modal/actions/__tests__/FullscreenAction.spec.ts +28 -0
- package/src/components/modal/actions/__tests__/NavigationCubeAction.spec.ts +25 -0
- package/src/components/modal/actions/__tests__/PanAction.spec.ts +46 -0
- package/src/components/modal/actions/__tests__/ResetViewAction.spec.ts +17 -0
- package/src/components/modal/actions/__tests__/Rotate2dAction.spec.ts +23 -0
- package/src/components/modal/actions/__tests__/Slice3dAction.spec.ts +14 -0
- package/src/components/modal/actions/__tests__/ZoomAction.spec.ts +34 -0
- package/src/components/modal/composables/__tests__/useNavigationCubeObserver.spec.ts +56 -0
- package/src/components/modal/composables/useEventListener.ts +22 -0
- package/src/components/modal/composables/useNavigationCubeObserver.ts +104 -0
- package/src/components/selectors/ValueSelector.vue +30 -33
- package/src/components/selectors/__tests__/value-selector.spec.ts +1 -1
- package/src/components/sliders/DoubleSliderSelector.vue +79 -71
- package/src/components/sliders/VerticalSliderSelector.vue +12 -17
- package/src/components/sliders/__tests__/double-slider-selector.spec.ts +1 -1
- package/src/components/sliders/__tests__/vertical-slider-selector.spec.ts +1 -1
- package/src/dataLayer/__tests__/clamp.spec.ts +16 -0
- package/src/dataLayer/__tests__/eventHandlers.spec.ts +38 -0
- package/src/dataLayer/__tests__/getIconForPreset.spec.ts +40 -0
- package/src/dataLayer/__tests__/patchDataOverlay.spec.ts +88 -0
- package/src/dataLayer/__tests__/scanState.spec.ts +93 -0
- package/src/dataLayer/__tests__/useViewer3cr.spec.ts +10 -0
- package/src/dataLayer/__tests__/viewer3cr.spec.ts +331 -0
- package/src/dataLayer/clamp.ts +9 -0
- package/src/dataLayer/eventHandlers.ts +26 -0
- package/src/dataLayer/patchDataOverlay.ts +101 -0
- package/src/dataLayer/scanState.ts +105 -26
- package/src/dataLayer/useViewer3cr.ts +7 -0
- package/src/dataLayer/viewer3cr.ts +410 -0
- package/src/helpers/__tests__/layout-overlay-style.spec.ts +24 -22
- package/src/helpers/__tests__/model-helper.spec.ts +44 -13
- package/src/helpers/layoutOverlayStyle.ts +16 -27
- package/src/helpers/modelHelper.ts +62 -10
- package/src/models/Callbacks.ts +2 -2
- package/src/models/LoadViewerOptions.ts +2 -0
- package/src/models/LoadViewerPayload.ts +1 -0
- package/src/notifications/notification.ts +3 -4
- package/src/plugins/vuetify.ts +5 -0
- package/src/services/gpt/__tests__/gpt.service.spec.ts +27 -0
- package/src/services/gpt/gpt.service.ts +27 -0
- package/static/3cr-types-browser/index.ts +74 -0
- package/static/3cr-types-browser/types/Action.ts +6 -0
- package/static/3cr-types-browser/types/ActionData.ts +4 -0
- package/static/3cr-types-browser/types/AlphaKeys.ts +5 -0
- package/static/3cr-types-browser/types/AnchorPoint.ts +12 -0
- package/static/3cr-types-browser/types/CallToAction.ts +5 -0
- package/static/3cr-types-browser/types/ColourData.ts +7 -0
- package/static/3cr-types-browser/types/ColourPresetData.ts +9 -0
- package/static/3cr-types-browser/types/CurrentDataOverlayState.ts +6 -0
- package/static/3cr-types-browser/types/CurrentScanState.ts +22 -0
- package/static/3cr-types-browser/types/DataOverlay.ts +22 -0
- package/static/3cr-types-browser/types/DataOverlayActions.ts +14 -0
- package/static/3cr-types-browser/types/DataOverlayData.ts +8 -0
- package/static/3cr-types-browser/types/DataOverlayEvent.ts +8 -0
- package/static/3cr-types-browser/types/DecryptionKey.ts +4 -0
- package/static/3cr-types-browser/types/DisplaySettings.ts +10 -0
- package/static/3cr-types-browser/types/EmptyPayload.ts +3 -0
- package/static/3cr-types-browser/types/EnumPayload.ts +4 -0
- package/static/3cr-types-browser/types/FileManagementActions.ts +11 -0
- package/static/3cr-types-browser/types/FlipValue.ts +7 -0
- package/static/3cr-types-browser/types/FrontEndInterfaces.ts +14 -0
- package/static/3cr-types-browser/types/GradientKeys.ts +7 -0
- package/static/3cr-types-browser/types/GreyscalePresetData.ts +6 -0
- package/static/3cr-types-browser/types/InitialDataOverlayState.ts +6 -0
- package/static/3cr-types-browser/types/InitialScanState.ts +19 -0
- package/static/3cr-types-browser/types/InteractionType.ts +8 -0
- package/static/3cr-types-browser/types/InteractivityActions.ts +6 -0
- package/static/3cr-types-browser/types/InteractivityState.ts +4 -0
- package/static/3cr-types-browser/types/InvertTransformData.ts +6 -0
- package/static/3cr-types-browser/types/LayoutActions.ts +6 -0
- package/static/3cr-types-browser/types/LayoutData.ts +7 -0
- package/static/3cr-types-browser/types/LoadDataSet.ts +6 -0
- package/static/3cr-types-browser/types/LoadSessionState.ts +4 -0
- package/static/3cr-types-browser/types/LocalLoadDataset.ts +3 -0
- package/static/3cr-types-browser/types/MovementData.ts +7 -0
- package/static/3cr-types-browser/types/NavigationCubeActions.ts +8 -0
- package/static/3cr-types-browser/types/NavigationCubeData.ts +12 -0
- package/static/3cr-types-browser/types/NavigationCubeTransform.ts +9 -0
- package/static/3cr-types-browser/types/NotificationPayload.ts +7 -0
- package/static/3cr-types-browser/types/NotificationsActions.ts +6 -0
- package/static/3cr-types-browser/types/Object.ts +1 -0
- package/static/3cr-types-browser/types/ObjectColour.ts +7 -0
- package/static/3cr-types-browser/types/ObjectIcon.ts +5 -0
- package/static/3cr-types-browser/types/ObjectInvert.ts +7 -0
- package/static/3cr-types-browser/types/ObjectSize.ts +7 -0
- package/static/3cr-types-browser/types/ObjectSize2D.ts +7 -0
- package/static/3cr-types-browser/types/ObjectVisible.ts +5 -0
- package/static/3cr-types-browser/types/PositionData.ts +14 -0
- package/static/3cr-types-browser/types/PresetsActions.ts +4 -0
- package/static/3cr-types-browser/types/RotationValue.ts +7 -0
- package/static/3cr-types-browser/types/ScanMovementActions.ts +27 -0
- package/static/3cr-types-browser/types/ScanMovementData.ts +3 -0
- package/static/3cr-types-browser/types/ScanOrientationActions.ts +6 -0
- package/static/3cr-types-browser/types/ScanStateActions.ts +4 -0
- package/static/3cr-types-browser/types/ScanView.ts +6 -0
- package/static/3cr-types-browser/types/SettingsData.ts +12 -0
- package/static/3cr-types-browser/types/SlicerData.ts +9 -0
- package/static/3cr-types-browser/types/SliderValue.ts +4 -0
- package/static/3cr-types-browser/types/SlidersActions.ts +18 -0
- package/static/3cr-types-browser/types/Vector2Data.ts +5 -0
- package/static/3cr-types-browser/types/Vector3Data.ts +6 -0
- package/static/3cr-types-browser/types/VectorMovementData.ts +8 -0
- package/static/3cr-types-browser/types/ViewInteractiveMode.ts +5 -0
- package/static/3cr-types-browser/types/ViewOrientation.ts +8 -0
- package/static/3cr-types-browser/types/ViewOrientations.ts +10 -0
- package/static/3cr-types-browser/types/ViewSelectionActions.ts +9 -0
- package/static/3cr-types-browser/types/ViewToggleData.ts +7 -0
- package/static/3cr-types-browser/types/VolumeOrientation.ts +7 -0
- package/test/helper.ts +10 -1
- package/test/setup.ts +13 -0
- package/tsconfig.json +1 -0
- package/vite.config.mts +1 -0
- package/src/dataLayer/__tests__/payload-handler.spec.ts +0 -214
- package/src/dataLayer/payloadHandler.ts +0 -138
- /package/src/dataLayer/{iconData.ts → getIconForPreset.ts} +0 -0
|
@@ -1,212 +1,142 @@
|
|
|
1
|
-
<!-- /* c8 ignore start */ -->
|
|
2
1
|
<template>
|
|
3
|
-
<!-- <DemoModal data-vuetify v-model:modal="m_demo" />-->
|
|
4
2
|
<DemoLicenceInfoModal v-model:modal="m_demo" :is-modal-open="value" />
|
|
5
3
|
<DemoLicenceShareToMobileModal v-model:modal="m_demoLicenceShareToMobile" />
|
|
6
4
|
<DemoLicenceSendToPartyModal v-model:modal="m_demoLicenceSendToParty" />
|
|
5
|
+
<DemoLicenceEnableCloudStorageModal v-model:modal="m_demoLicenseEnableCloudStorage" />
|
|
7
6
|
<DemoPatientInfoModal v-model:modal="m_demoPatient" :is-modal-open="value" />
|
|
8
7
|
<DemoPatientShareToMobileModal v-model:modal="m_demoPatientShareToMobile" />
|
|
9
8
|
<DemoPatientSendToPartyModal v-model:modal="m_demoPatientSendToParty" />
|
|
9
|
+
<DemoPatientEnableCloudStorageModal v-model:modal="m_demoPatientEnableCloudStorage" />
|
|
10
|
+
<CloseViewerModal v-model:modal="m_closeDialog" :options="opts" @close="value = false"/>
|
|
10
11
|
<v-dialog
|
|
11
12
|
data-vuetify
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
:modelValue="value"
|
|
15
|
-
style="max-height: unset"
|
|
16
|
-
:scrollable="false"
|
|
13
|
+
:model-value="value"
|
|
14
|
+
:no-click-animation="true"
|
|
17
15
|
fullscreen
|
|
18
16
|
persistent
|
|
19
|
-
@input="alterValue"
|
|
20
17
|
>
|
|
21
|
-
<v-dialog
|
|
22
|
-
v-model:model-value="m_closeDialog"
|
|
23
|
-
id="close-dialog-prompt"
|
|
24
|
-
data-test="closemodal"
|
|
25
|
-
>
|
|
26
|
-
<v-card
|
|
27
|
-
class="pa-1 ma-auto position-relative motif-background"
|
|
28
|
-
width="600"
|
|
29
|
-
theme="dark"
|
|
30
|
-
>
|
|
31
|
-
<v-card-title class="text-center">Close Viewer?</v-card-title>
|
|
32
|
-
<v-card-text class="text-center mb-2"
|
|
33
|
-
>Are you sure you want to close the Online Viewer?</v-card-text
|
|
34
|
-
>
|
|
35
|
-
<v-card-actions>
|
|
36
|
-
<v-btn
|
|
37
|
-
variant="flat"
|
|
38
|
-
color="secondary"
|
|
39
|
-
@click="m_closeDialog = false"
|
|
40
|
-
>
|
|
41
|
-
Cancel
|
|
42
|
-
</v-btn>
|
|
43
|
-
<v-spacer />
|
|
44
|
-
<v-btn
|
|
45
|
-
color="red"
|
|
46
|
-
variant="flat"
|
|
47
|
-
@click="closeModal"
|
|
48
|
-
v-if="showOption('OnSaveSession')"
|
|
49
|
-
>
|
|
50
|
-
Close without saving
|
|
51
|
-
</v-btn>
|
|
52
|
-
<v-btn
|
|
53
|
-
color="primary"
|
|
54
|
-
variant="flat"
|
|
55
|
-
@click="closeModalSave"
|
|
56
|
-
v-if="showOption('OnSaveSession')"
|
|
57
|
-
>
|
|
58
|
-
Save Session
|
|
59
|
-
</v-btn>
|
|
60
|
-
<v-btn color="flat" variant="tonal" @click="closeModal" v-else>
|
|
61
|
-
Close Viewer
|
|
62
|
-
</v-btn>
|
|
63
|
-
</v-card-actions>
|
|
64
|
-
</v-card>
|
|
65
|
-
</v-dialog>
|
|
66
18
|
<v-card
|
|
67
19
|
id="invoice-table-modal"
|
|
68
20
|
class="pa-0 ma-0 position-relative motif-background overflow-y-hidden rounded-0"
|
|
69
21
|
height="100vh"
|
|
70
22
|
>
|
|
71
|
-
<v-
|
|
72
|
-
<v-menu
|
|
73
|
-
:close-on-content-click="false"
|
|
74
|
-
:close-on-click="true"
|
|
75
|
-
open-on-hover
|
|
76
|
-
offset-y
|
|
77
|
-
>
|
|
23
|
+
<v-app-bar density="compact">
|
|
24
|
+
<v-menu :close-on-content-click="false">
|
|
78
25
|
<template #activator="{ props, isActive }">
|
|
79
26
|
<v-btn
|
|
80
27
|
v-bind="props"
|
|
81
|
-
variant="flat"
|
|
82
|
-
:color="isActive ? 'secondary' : 'primary'"
|
|
83
28
|
class="mr-2"
|
|
29
|
+
:color="isActive ? 'secondary' : 'primary'"
|
|
30
|
+
prepend-icon="description"
|
|
31
|
+
variant="flat"
|
|
84
32
|
>
|
|
85
|
-
<template #prepend>
|
|
86
|
-
<v-icon>description</v-icon>
|
|
87
|
-
</template>
|
|
88
33
|
File
|
|
89
34
|
</v-btn>
|
|
90
35
|
</template>
|
|
91
|
-
<v-
|
|
92
|
-
<v-list
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
<v-list-item
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
<v-list-item-title>Send to 3rd Party</v-list-item-title>
|
|
128
|
-
</v-list-item>
|
|
129
|
-
<v-list-item
|
|
130
|
-
v-if="showOption('OnShareToMobile')"
|
|
131
|
-
@click="executeOption('OnShareToMobile')"
|
|
132
|
-
>
|
|
133
|
-
<template v-slot:prepend>
|
|
134
|
-
<v-icon> share </v-icon>
|
|
135
|
-
</template>
|
|
136
|
-
<v-list-item-title>Share to Mobile / VR</v-list-item-title>
|
|
137
|
-
</v-list-item>
|
|
36
|
+
<v-list>
|
|
37
|
+
<v-list-item
|
|
38
|
+
v-if="showOption('OnLoadNewDicomSeries')"
|
|
39
|
+
@click="executeOption('OnLoadNewDicomSeries')"
|
|
40
|
+
prepend-icon="upload"
|
|
41
|
+
>
|
|
42
|
+
<v-list-item-title>Load New DICOM Series</v-list-item-title>
|
|
43
|
+
</v-list-item>
|
|
44
|
+
<v-list-item
|
|
45
|
+
v-if="showOption('OnDownloadDicomSeries')"
|
|
46
|
+
@click="executeOption('OnDownloadDicomSeries')"
|
|
47
|
+
prepend-icon="download"
|
|
48
|
+
>
|
|
49
|
+
<v-list-item-title>Download DICOM Series</v-list-item-title>
|
|
50
|
+
</v-list-item>
|
|
51
|
+
<v-list-item
|
|
52
|
+
v-if="showOption('OnLoadSavedSession')"
|
|
53
|
+
@click="executeOption('OnLoadSavedSession')"
|
|
54
|
+
prepend-icon="sync"
|
|
55
|
+
>
|
|
56
|
+
<v-list-item-title> Load Saved Session </v-list-item-title>
|
|
57
|
+
</v-list-item>
|
|
58
|
+
<v-list-item
|
|
59
|
+
v-if="showOption('OnSendTo3rdParty')"
|
|
60
|
+
@click="executeOption('OnSendTo3rdParty')"
|
|
61
|
+
prepend-icon="send"
|
|
62
|
+
>
|
|
63
|
+
<v-list-item-title>Send to 3rd Party</v-list-item-title>
|
|
64
|
+
</v-list-item>
|
|
65
|
+
<v-list-item
|
|
66
|
+
v-if="showOption('OnShareToMobile')"
|
|
67
|
+
@click="executeOption('OnShareToMobile')"
|
|
68
|
+
prepend-icon="share"
|
|
69
|
+
>
|
|
70
|
+
<v-list-item-title>Share to Mobile / VR</v-list-item-title>
|
|
71
|
+
</v-list-item>
|
|
138
72
|
|
|
139
|
-
|
|
140
|
-
|
|
73
|
+
<v-list-item
|
|
74
|
+
@click="
|
|
141
75
|
executeOption('OnClosePopup');
|
|
142
76
|
alterValue(false);
|
|
143
77
|
"
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
</v-list-item>
|
|
150
|
-
</v-list>
|
|
151
|
-
</v-card>
|
|
78
|
+
prepend-icon="close"
|
|
79
|
+
>
|
|
80
|
+
<v-list-item-title>Close Viewer</v-list-item-title>
|
|
81
|
+
</v-list-item>
|
|
82
|
+
</v-list>
|
|
152
83
|
</v-menu>
|
|
153
84
|
<v-menu
|
|
85
|
+
v-model:model-value="introJsOpenMenu"
|
|
86
|
+
:persistent="introJsDisableMenu"
|
|
87
|
+
:no-click-animation="introJsDisableMenu"
|
|
154
88
|
:close-on-content-click="false"
|
|
155
|
-
:close-on-click="true"
|
|
156
|
-
open-on-hover
|
|
157
|
-
offset-y
|
|
158
89
|
>
|
|
159
90
|
<template #activator="{ props, isActive }">
|
|
160
91
|
<v-btn
|
|
161
|
-
variant="flat"
|
|
162
92
|
v-bind="props"
|
|
163
93
|
:color="isActive ? 'secondary' : 'primary'"
|
|
94
|
+
:disabled="introJsDisableMenu"
|
|
95
|
+
prepend-icon="settings"
|
|
96
|
+
variant="flat"
|
|
164
97
|
>
|
|
165
|
-
<template #prepend>
|
|
166
|
-
<v-icon>settings</v-icon>
|
|
167
|
-
</template>
|
|
168
98
|
Settings
|
|
169
99
|
</v-btn>
|
|
170
100
|
</template>
|
|
171
|
-
<v-card width="350" class="pa-4">
|
|
172
|
-
<
|
|
101
|
+
<v-card id="settings-card" width="350" class="pa-4">
|
|
102
|
+
<ValueSelector
|
|
173
103
|
v-model:value="scanState.Display.Brightness"
|
|
174
104
|
label="Adjust Brightness"
|
|
175
105
|
prepend="percent"
|
|
176
106
|
/>
|
|
177
|
-
<
|
|
107
|
+
<ValueSelector
|
|
178
108
|
v-model:value="scanState.Display.Contrast"
|
|
179
109
|
label="Adjust Contrast"
|
|
180
110
|
prepend="percent"
|
|
181
111
|
/>
|
|
182
|
-
<
|
|
112
|
+
<ValueSelector
|
|
183
113
|
v-model:value="scanState.Display.Opacity"
|
|
184
114
|
label="Adjust Opacity"
|
|
185
115
|
prepend="percent"
|
|
186
116
|
/>
|
|
187
117
|
<v-divider class="my-4" />
|
|
188
|
-
<
|
|
118
|
+
<ValueSelector
|
|
189
119
|
v-model:value="scanState.InteractionSettings.PanSensivitity"
|
|
190
120
|
:min="0"
|
|
191
121
|
:max="100"
|
|
192
122
|
label="Pan Sensitivity"
|
|
193
123
|
prepend="percent"
|
|
194
124
|
/>
|
|
195
|
-
<
|
|
125
|
+
<ValueSelector
|
|
196
126
|
v-model:value="scanState.InteractionSettings.ZoomSensitivity"
|
|
197
127
|
:min="0"
|
|
198
128
|
:max="100"
|
|
199
129
|
label="Zoom Sensitivity"
|
|
200
130
|
prepend="percent"
|
|
201
131
|
/>
|
|
202
|
-
<
|
|
132
|
+
<ValueSelector
|
|
203
133
|
v-model:value="scanState.InteractionSettings.RotateSensitivity"
|
|
204
134
|
:min="0"
|
|
205
135
|
:max="100"
|
|
206
136
|
label="Rotate Sensitivity"
|
|
207
137
|
prepend="percent"
|
|
208
138
|
/>
|
|
209
|
-
<
|
|
139
|
+
<ValueSelector
|
|
210
140
|
v-model:value="
|
|
211
141
|
scanState.InteractionSettings.CameraRotateSensitivity
|
|
212
142
|
"
|
|
@@ -215,9 +145,10 @@
|
|
|
215
145
|
label="Camera Rotate Sensitivity"
|
|
216
146
|
prepend="percent"
|
|
217
147
|
/>
|
|
148
|
+
<v-divider class="my-4" />
|
|
149
|
+
<NavigationCubeAction />
|
|
218
150
|
</v-card>
|
|
219
151
|
</v-menu>
|
|
220
|
-
|
|
221
152
|
<v-spacer />
|
|
222
153
|
<div class="font-weight-bold">
|
|
223
154
|
<span v-if="isDemo" class="text-capitalize">
|
|
@@ -234,7 +165,7 @@
|
|
|
234
165
|
height="36"
|
|
235
166
|
style="min-width: 36px !important"
|
|
236
167
|
:color="isLayout2x2 ? 'secondary' : 'primary'"
|
|
237
|
-
@click="
|
|
168
|
+
@click="viewer3cr.layouts(LayoutActions.lo02)"
|
|
238
169
|
><v-icon>grid_view</v-icon>
|
|
239
170
|
<v-tooltip location="bottom" activator="parent">
|
|
240
171
|
Change Layout to 2:2
|
|
@@ -245,612 +176,178 @@
|
|
|
245
176
|
height="36"
|
|
246
177
|
style="min-width: 36px !important"
|
|
247
178
|
:color="isLayout1x3 ? 'secondary' : 'primary'"
|
|
248
|
-
@click="
|
|
179
|
+
@click="viewer3cr.layouts(LayoutActions.lo03)"
|
|
249
180
|
><v-icon style="rotate: -90deg">view_comfy</v-icon>
|
|
250
181
|
<v-tooltip location="bottom" activator="parent">
|
|
251
182
|
Change Layout to 1:3
|
|
252
183
|
</v-tooltip></v-btn
|
|
253
184
|
>
|
|
254
|
-
<v-btn
|
|
255
|
-
|
|
185
|
+
<v-btn variant="flat" color="red" @click="alterValue(false)">
|
|
186
|
+
Close Viewer
|
|
256
187
|
</v-btn>
|
|
257
|
-
</v-
|
|
258
|
-
<v-
|
|
259
|
-
|
|
260
|
-
permanent
|
|
261
|
-
:rail="drawerCollapsed"
|
|
262
|
-
rail-width="68"
|
|
263
|
-
style="opacity: 0.95; margin-top: 48px; height: calc(100vh - 40px)"
|
|
264
|
-
absolute
|
|
265
|
-
dark
|
|
266
|
-
class="rounded-0 motif-background"
|
|
267
|
-
width="300"
|
|
268
|
-
>
|
|
269
|
-
<template v-slot:prepend>
|
|
270
|
-
<div
|
|
271
|
-
v-if="!isDemo || demoType === 'patient'"
|
|
272
|
-
class="d-flex align-center pb-1"
|
|
273
|
-
:class="drawerCollapsed ? 'py-2' : 'pa-2'"
|
|
274
|
-
>
|
|
275
|
-
<img
|
|
276
|
-
v-if="!drawerCollapsed"
|
|
277
|
-
src="../../assets/images/dark/3dicom-logo.svg"
|
|
278
|
-
height="48"
|
|
279
|
-
alt="logo"
|
|
280
|
-
class="ma-auto"
|
|
281
|
-
style="opacity: 0.9"
|
|
282
|
-
/>
|
|
283
|
-
<img
|
|
284
|
-
v-if="drawerCollapsed"
|
|
285
|
-
src="../../assets/images/dark/3DICOM.png"
|
|
286
|
-
height="64"
|
|
287
|
-
alt="logo"
|
|
288
|
-
class="ma-auto"
|
|
289
|
-
style="opacity: 0.9"
|
|
290
|
-
/>
|
|
291
|
-
</div>
|
|
292
|
-
<div
|
|
293
|
-
v-else
|
|
294
|
-
class="d-flex align-center pb-1"
|
|
295
|
-
:class="drawerCollapsed ? 'py-2' : 'pa-2 my-n12'"
|
|
296
|
-
>
|
|
297
|
-
<img
|
|
298
|
-
v-if="!drawerCollapsed"
|
|
299
|
-
src="../../assets/images/dark/demo-logo.svg"
|
|
300
|
-
width="100%"
|
|
301
|
-
alt="logo"
|
|
302
|
-
class="ma-auto my-n16"
|
|
303
|
-
style="opacity: 0.9"
|
|
304
|
-
/>
|
|
305
|
-
<img
|
|
306
|
-
v-if="drawerCollapsed"
|
|
307
|
-
src="../../assets/images/dark/demo-icon-small.png"
|
|
308
|
-
height="48"
|
|
309
|
-
alt="logo"
|
|
310
|
-
class="ma-auto"
|
|
311
|
-
style="opacity: 0.9"
|
|
312
|
-
/>
|
|
313
|
-
</div>
|
|
314
|
-
<div
|
|
315
|
-
class="text-center mx-auto pa-0 text-white sub-type"
|
|
316
|
-
:style="drawerCollapsed ? 'font-size: 100%' : 'font-size: 140%'"
|
|
317
|
-
>
|
|
318
|
-
Online Viewer
|
|
319
|
-
</div>
|
|
320
|
-
</template>
|
|
321
|
-
<template v-slot:append>
|
|
322
|
-
<v-divider></v-divider>
|
|
323
|
-
<v-list>
|
|
324
|
-
<v-tooltip
|
|
325
|
-
right
|
|
326
|
-
v-for="(item, index) in footerItems.filter((x) =>
|
|
327
|
-
x.conditional()
|
|
328
|
-
)"
|
|
329
|
-
:key="index"
|
|
330
|
-
>
|
|
331
|
-
<template #activator="{ props }">
|
|
332
|
-
<v-list-item
|
|
333
|
-
class="px-2"
|
|
334
|
-
:class="[drawerCollapsed && 'py-1']"
|
|
335
|
-
target="_blank"
|
|
336
|
-
v-bind="props"
|
|
337
|
-
:disabled="!(instanceLoaded && !scanLoading)"
|
|
338
|
-
@click="item.click()"
|
|
339
|
-
>
|
|
340
|
-
<template v-slot:prepend>
|
|
341
|
-
<div
|
|
342
|
-
:class="[drawerCollapsed && 'mx-auto']"
|
|
343
|
-
class="mx-2"
|
|
344
|
-
:style="
|
|
345
|
-
drawerCollapsed &&
|
|
346
|
-
'margin-left: 10px !important; margin-right: 10px !important;'
|
|
347
|
-
"
|
|
348
|
-
>
|
|
349
|
-
<v-icon
|
|
350
|
-
:large="drawerCollapsed"
|
|
351
|
-
:size="drawerCollapsed ? 32 : 24"
|
|
352
|
-
:color="item.color"
|
|
353
|
-
>
|
|
354
|
-
{{ item.icon || "radio_button_checked" }}
|
|
355
|
-
</v-icon>
|
|
356
|
-
</div>
|
|
357
|
-
</template>
|
|
358
|
-
<v-list-item-title
|
|
359
|
-
v-if="!drawerCollapsed"
|
|
360
|
-
class="text-white font-weight-medium ml-3"
|
|
361
|
-
>
|
|
362
|
-
{{ item.text }}
|
|
363
|
-
</v-list-item-title>
|
|
364
|
-
</v-list-item>
|
|
365
|
-
</template>
|
|
366
|
-
{{ item.tooltip }}
|
|
367
|
-
</v-tooltip>
|
|
368
|
-
</v-list>
|
|
369
|
-
</template>
|
|
370
|
-
<v-list v-if="drawerCollapsed">
|
|
371
|
-
<v-tooltip right v-for="(item, index) in miniMenu" :key="item.text">
|
|
372
|
-
<template #activator="{ props }">
|
|
373
|
-
<v-list-item
|
|
374
|
-
:disabled="!(instanceLoaded && !scanLoading)"
|
|
375
|
-
class="px-2 py-1"
|
|
376
|
-
v-bind="props"
|
|
377
|
-
@click="
|
|
378
|
-
openPanels = index;
|
|
379
|
-
drawerCollapsed = false;
|
|
380
|
-
"
|
|
381
|
-
>
|
|
382
|
-
<v-icon
|
|
383
|
-
size="36"
|
|
384
|
-
color="white"
|
|
385
|
-
style="
|
|
386
|
-
margin-left: 8px !important;
|
|
387
|
-
margin-right: 10px !important;
|
|
388
|
-
"
|
|
389
|
-
>
|
|
390
|
-
{{ item.icon || "radio_button_checked" }}
|
|
391
|
-
</v-icon>
|
|
392
|
-
</v-list-item>
|
|
393
|
-
</template>
|
|
394
|
-
{{ item.tooltip }}
|
|
395
|
-
</v-tooltip>
|
|
396
|
-
</v-list>
|
|
397
|
-
<v-expansion-panels
|
|
398
|
-
v-else
|
|
399
|
-
v-model="openPanels"
|
|
400
|
-
style="max-height: 95vh"
|
|
401
|
-
theme="dark"
|
|
402
|
-
accordion
|
|
403
|
-
class="mt-1 transparent pr-0 mr-0"
|
|
404
|
-
>
|
|
405
|
-
<v-expansion-panel class="transparent px-0">
|
|
406
|
-
<v-expansion-panel-title class="font-weight-bold transparent">
|
|
407
|
-
<span
|
|
408
|
-
><v-icon small>{{ miniMenu[0].icon }}</v-icon
|
|
409
|
-
> {{ miniMenu[0].text }}</span
|
|
410
|
-
>
|
|
411
|
-
<v-spacer />
|
|
412
|
-
<v-tooltip location="right" activator="parent">
|
|
413
|
-
{{ miniMenu[0].tooltip }}
|
|
414
|
-
</v-tooltip>
|
|
415
|
-
</v-expansion-panel-title>
|
|
416
|
-
<v-expansion-panel-text class="px-0">
|
|
417
|
-
<DoubleSliderSelector
|
|
418
|
-
v-model:value="windowSlider"
|
|
419
|
-
label="Skin to Bone"
|
|
420
|
-
:min="huMinMax.min"
|
|
421
|
-
:max="huMinMax.max"
|
|
422
|
-
/>
|
|
423
|
-
<DoubleSliderSelector
|
|
424
|
-
v-model:value="thresholdSlider"
|
|
425
|
-
label="Fine Adjustment"
|
|
426
|
-
v-bind="huMinMax"
|
|
427
|
-
/>
|
|
428
|
-
<v-card-actions class="py-3">
|
|
429
|
-
<v-select
|
|
430
|
-
:value="currentGreyscalePreset"
|
|
431
|
-
:items="initialScanState.GreyscalePresets"
|
|
432
|
-
label="Greyscale Preset"
|
|
433
|
-
item-text="Name"
|
|
434
|
-
theme="light"
|
|
435
|
-
density="compact"
|
|
436
|
-
variant="outlined"
|
|
437
|
-
persistent-placeholder
|
|
438
|
-
hide-details
|
|
439
|
-
return-object
|
|
440
|
-
:menu-props="{
|
|
441
|
-
closeOnContentClick: true,
|
|
442
|
-
}"
|
|
443
|
-
placeholder="Select a Density Preset"
|
|
444
|
-
@change="
|
|
445
|
-
payloadHandler.setPreset(PresetsActions.pr01, $event)
|
|
446
|
-
"
|
|
447
|
-
>
|
|
448
|
-
<template #item="{ props, item }">
|
|
449
|
-
<v-list-item
|
|
450
|
-
v-bind="props"
|
|
451
|
-
ripple
|
|
452
|
-
:title="item.raw.Name"
|
|
453
|
-
lines="three"
|
|
454
|
-
@mousedown.prevent
|
|
455
|
-
@click="
|
|
456
|
-
payloadHandler.setPreset(PresetsActions.pr01, item.raw)
|
|
457
|
-
"
|
|
458
|
-
>
|
|
459
|
-
<!-- <template v-slot:prepend>-->
|
|
460
|
-
<!-- <v-icon-->
|
|
461
|
-
<!-- :icon="getIconForPreset(item.raw.Name)"-->
|
|
462
|
-
<!-- ></v-icon>-->
|
|
463
|
-
<!-- </template>-->
|
|
464
|
-
<template v-slot:subtitle>
|
|
465
|
-
Skin Density:
|
|
466
|
-
<span class="text-mono">{{ item.raw.Lower }}</span>
|
|
467
|
-
<v-spacer />
|
|
468
|
-
Bone Density:
|
|
469
|
-
<span class="text-mono">{{ item.raw.Upper }}</span>
|
|
470
|
-
</template>
|
|
471
|
-
</v-list-item>
|
|
472
|
-
</template>
|
|
473
|
-
<template v-slot:selection="{ item }">
|
|
474
|
-
<span v-if="item.raw === undefined || !item.raw.Name"
|
|
475
|
-
>None</span
|
|
476
|
-
>
|
|
477
|
-
<span v-else>{{ item.raw.Name }}</span>
|
|
478
|
-
</template>
|
|
479
|
-
</v-select>
|
|
480
|
-
</v-card-actions>
|
|
481
|
-
<v-card-actions class="py-3">
|
|
482
|
-
<v-select
|
|
483
|
-
:model-value="currentColourPreset"
|
|
484
|
-
label="Colour Preset"
|
|
485
|
-
variant="outlined"
|
|
486
|
-
density="compact"
|
|
487
|
-
:items="initialScanState.ColourPresets"
|
|
488
|
-
item-title="Name"
|
|
489
|
-
hide-details
|
|
490
|
-
:menu-props="{
|
|
491
|
-
closeOnContentClick: true,
|
|
492
|
-
}"
|
|
493
|
-
placeholder="Select a Colour Preset"
|
|
494
|
-
return-object
|
|
495
|
-
@update:modelValue="
|
|
496
|
-
payloadHandler.setPreset(PresetsActions.pr02, $event)
|
|
497
|
-
"
|
|
498
|
-
></v-select>
|
|
499
|
-
</v-card-actions>
|
|
500
|
-
</v-expansion-panel-text>
|
|
501
|
-
</v-expansion-panel>
|
|
502
|
-
</v-expansion-panels>
|
|
503
|
-
</v-navigation-drawer>
|
|
504
|
-
|
|
505
|
-
<v-btn
|
|
506
|
-
height="84"
|
|
507
|
-
color="grey"
|
|
508
|
-
variant="flat"
|
|
509
|
-
width="20"
|
|
510
|
-
min-width="20"
|
|
511
|
-
class="my-auto rounded-bl-0 rounded-tl-0 pa-0"
|
|
512
|
-
style="
|
|
513
|
-
border-bottom-left-radius: 0 !important;
|
|
514
|
-
border-top-left-radius: 0 !important;
|
|
515
|
-
position: absolute;
|
|
516
|
-
top: 50%;
|
|
517
|
-
opacity: 0.7;
|
|
518
|
-
z-index: 100000;
|
|
519
|
-
transform: translateY(-50%);
|
|
520
|
-
"
|
|
521
|
-
:style="drawerCollapsed ? 'left: 68px' : 'left: 300px'"
|
|
522
|
-
@click="drawerCollapsed = !drawerCollapsed"
|
|
523
|
-
>
|
|
524
|
-
<v-icon color="black">{{
|
|
525
|
-
drawerCollapsed ? "chevron_right" : "chevron_left"
|
|
526
|
-
}}</v-icon>
|
|
527
|
-
</v-btn>
|
|
528
|
-
|
|
529
|
-
<div
|
|
530
|
-
class="position-relative pa-0"
|
|
531
|
-
:style="drawerCollapsed ? 'margin-left: 68px' : 'margin-left: 300px'"
|
|
532
|
-
>
|
|
188
|
+
</v-app-bar>
|
|
189
|
+
<ViewerNavigationDrawer v-model:drawer="drawer" :options="opts" @update:expanded="expanded = $event" />
|
|
190
|
+
<v-main>
|
|
533
191
|
<WebGL3DR
|
|
534
|
-
v-show="instanceLoaded && !scanLoading"
|
|
535
|
-
|
|
536
|
-
v-if="value"
|
|
537
|
-
ref="web_gl"
|
|
538
|
-
id="webgl-container"
|
|
539
|
-
@on_payload="handleOnPayload"
|
|
192
|
+
v-show="value && instanceLoaded && !scanLoading"
|
|
193
|
+
ref="webGl3dr"
|
|
540
194
|
@instance_loaded="load"
|
|
541
|
-
@
|
|
195
|
+
@dblclick="viewer3cr.viewSelection(ViewSelectionActions.vs05)"
|
|
196
|
+
@mouseenter="onMouseEnter"
|
|
197
|
+
@mouseleave="onMouseLeave"
|
|
542
198
|
>
|
|
543
199
|
<div
|
|
544
|
-
class="bordered-event-window"
|
|
545
200
|
v-for="(layout, index) in scanState.Layout.PositionData"
|
|
546
|
-
:key="layout
|
|
547
|
-
:
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
? 'grab !important'
|
|
552
|
-
: 'default',
|
|
553
|
-
}"
|
|
554
|
-
:data-box-internal="layout.DefaultView"
|
|
201
|
+
:key="getCurrentView(layout)"
|
|
202
|
+
:id="`view-${getCurrentView(layout)}`"
|
|
203
|
+
:ref="(el) => (elements[index] = el)"
|
|
204
|
+
class="bordered-event-window"
|
|
205
|
+
:style="generateDivStyleForLayout(layout, webGl3dr!.canvas)"
|
|
555
206
|
>
|
|
556
207
|
<v-hover>
|
|
557
|
-
<template
|
|
558
|
-
<
|
|
559
|
-
<
|
|
560
|
-
class="
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
</v-btn>
|
|
573
|
-
<v-tooltip
|
|
574
|
-
target="cursor"
|
|
575
|
-
location="top"
|
|
576
|
-
activator="parent"
|
|
577
|
-
>
|
|
578
|
-
Make
|
|
579
|
-
{{ getViewName(getCurrentActiveView(layout)) }}
|
|
580
|
-
fullscreen
|
|
581
|
-
</v-tooltip>
|
|
582
|
-
</div>
|
|
583
|
-
<div v-if="scanState.Layout.PositionData.length === 1">
|
|
584
|
-
<v-btn
|
|
585
|
-
color="transparent"
|
|
586
|
-
@click="payloadHandler.layouts(previousLayout)"
|
|
587
|
-
:icon="true"
|
|
588
|
-
>
|
|
589
|
-
<v-icon color="white">fullscreen_exit</v-icon>
|
|
590
|
-
</v-btn>
|
|
591
|
-
<v-tooltip
|
|
592
|
-
target="cursor"
|
|
593
|
-
location="top"
|
|
594
|
-
activator="parent"
|
|
595
|
-
>
|
|
596
|
-
Exit Fullscreen View
|
|
597
|
-
</v-tooltip>
|
|
598
|
-
</div>
|
|
599
|
-
<div>
|
|
600
|
-
<v-btn
|
|
601
|
-
color="transparent"
|
|
602
|
-
:icon="true"
|
|
603
|
-
@click="
|
|
604
|
-
payloadHandler.viewSelection(
|
|
605
|
-
ViewSelectionActions.vs06
|
|
606
|
-
);
|
|
607
|
-
payloadHandler.viewSelection(
|
|
608
|
-
ViewSelectionActions.vs05
|
|
609
|
-
);
|
|
610
|
-
"
|
|
611
|
-
><v-icon color="white">home</v-icon></v-btn
|
|
612
|
-
>
|
|
613
|
-
<v-tooltip
|
|
614
|
-
target="cursor"
|
|
615
|
-
location="top"
|
|
616
|
-
activator="parent"
|
|
617
|
-
>
|
|
618
|
-
Reset volume to default view
|
|
619
|
-
</v-tooltip>
|
|
620
|
-
</div>
|
|
621
|
-
<div
|
|
622
|
-
v-if="getCurrentActiveView(layout) === ScanView.Volume"
|
|
623
|
-
>
|
|
624
|
-
<v-menu
|
|
625
|
-
v-model="menu"
|
|
626
|
-
:close-on-content-click="false"
|
|
627
|
-
location="end"
|
|
628
|
-
>
|
|
629
|
-
<template v-slot:activator="{ props }">
|
|
630
|
-
<v-tooltip location="top">
|
|
631
|
-
<template v-slot:activator="{ props: tooltip }">
|
|
632
|
-
<v-btn
|
|
633
|
-
v-bind="mergeProps(props, tooltip)"
|
|
634
|
-
color="transparent"
|
|
635
|
-
:icon="true"
|
|
636
|
-
>
|
|
637
|
-
<v-icon color="white">cut</v-icon>
|
|
638
|
-
</v-btn>
|
|
208
|
+
<template #default="{ isHovering, props }">
|
|
209
|
+
<v-row v-bind="props" class="flex-nowrap h-100" no-gutters>
|
|
210
|
+
<v-col style="min-width: 0">
|
|
211
|
+
<v-row class="flex-column h-100" no-gutters>
|
|
212
|
+
<v-col class="flex-grow-0">
|
|
213
|
+
<div class="d-flex pa-2 pl-3 align-center">
|
|
214
|
+
<span style="color: white">{{ getViewName(getCurrentView(layout)) }}</span>
|
|
215
|
+
<v-tooltip v-if="isHorizontalFlip(getCurrentView(layout))" location="bottom">
|
|
216
|
+
<template #activator="{ props }">
|
|
217
|
+
<v-icon
|
|
218
|
+
v-bind="props"
|
|
219
|
+
icon="swap_horiz"
|
|
220
|
+
color="blue-darken-1"
|
|
221
|
+
class="ml-2"
|
|
222
|
+
/>
|
|
639
223
|
</template>
|
|
640
|
-
|
|
224
|
+
<span>This view has been flipped horizontally</span>
|
|
641
225
|
</v-tooltip>
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
<DoubleSliderSelector
|
|
651
|
-
v-model:value="sSlider"
|
|
652
|
-
label="Sagittal"
|
|
653
|
-
v-bind="sMinMax"
|
|
654
|
-
/>
|
|
655
|
-
<DoubleSliderSelector
|
|
656
|
-
v-model:value="cSlider"
|
|
657
|
-
label="Coronal"
|
|
658
|
-
v-bind="cMinMax"
|
|
659
|
-
/>
|
|
660
|
-
</v-card>
|
|
661
|
-
</v-menu>
|
|
662
|
-
</div>
|
|
663
|
-
<div
|
|
664
|
-
v-if="getCurrentActiveView(layout) !== ScanView.Volume"
|
|
665
|
-
>
|
|
666
|
-
<v-menu
|
|
667
|
-
v-model="menus[index]"
|
|
668
|
-
:close-on-content-click="false"
|
|
669
|
-
:close-on-click="true"
|
|
670
|
-
offset-overflow
|
|
671
|
-
top
|
|
672
|
-
>
|
|
673
|
-
<template v-slot:activator="{ props }">
|
|
674
|
-
<v-tooltip top>
|
|
675
|
-
<template #activator="{ props: ttprops }">
|
|
676
|
-
<v-btn
|
|
677
|
-
v-bind="{ ...props, ...ttprops }"
|
|
678
|
-
:icon="true"
|
|
679
|
-
>
|
|
680
|
-
<v-icon color="white">360</v-icon>
|
|
681
|
-
</v-btn>
|
|
226
|
+
<v-tooltip v-if="isVerticalFlip(getCurrentView(layout))" location="bottom">
|
|
227
|
+
<template #activator="{ props }">
|
|
228
|
+
<v-icon
|
|
229
|
+
v-bind="props"
|
|
230
|
+
icon="swap_vert"
|
|
231
|
+
color="blue-darken-1"
|
|
232
|
+
class="ml-2"
|
|
233
|
+
/>
|
|
682
234
|
</template>
|
|
683
|
-
|
|
684
|
-
{{ getViewName(getCurrentActiveView(layout)) }} by
|
|
685
|
-
an angle
|
|
235
|
+
<span>This view has been flipped vertically</span>
|
|
686
236
|
</v-tooltip>
|
|
687
|
-
</
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
<v-spacer />
|
|
705
|
-
<v-btn
|
|
706
|
-
color="primary"
|
|
707
|
-
@click="
|
|
708
|
-
payloadHandler.rotateByDeg(
|
|
709
|
-
getCurrentActiveView(layout),
|
|
710
|
-
rotationDeg
|
|
711
|
-
)
|
|
712
|
-
"
|
|
713
|
-
>Rotate</v-btn
|
|
714
|
-
>
|
|
715
|
-
</v-card-actions>
|
|
716
|
-
</v-card>
|
|
717
|
-
</v-menu>
|
|
718
|
-
</div>
|
|
719
|
-
</div>
|
|
720
|
-
<div class="slider-in-view" v-if="isHovering || true">
|
|
237
|
+
</div>
|
|
238
|
+
</v-col>
|
|
239
|
+
<v-spacer />
|
|
240
|
+
<v-col class="flex-grow-0">
|
|
241
|
+
<Transition>
|
|
242
|
+
<div v-show="isHovering || modals[getCurrentView(layout)]">
|
|
243
|
+
<ViewerActionRail
|
|
244
|
+
:view="getCurrentView(layout)"
|
|
245
|
+
:element="elements[index]"
|
|
246
|
+
@modal="onActionModal"
|
|
247
|
+
/>
|
|
248
|
+
</div>
|
|
249
|
+
</Transition>
|
|
250
|
+
</v-col>
|
|
251
|
+
</v-row>
|
|
252
|
+
</v-col>
|
|
253
|
+
<v-col class="flex-grow-0">
|
|
721
254
|
<VerticalSliderSelector
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
"
|
|
255
|
+
class="h-100 my-0 pa-2 pr-3"
|
|
256
|
+
v-if="getCurrentView(layout) === ScanView.Transverse"
|
|
725
257
|
v-model:value="scanState.Orientations.Transverse.Slice"
|
|
726
258
|
v-bind="tMinMax"
|
|
727
259
|
/>
|
|
728
260
|
<VerticalSliderSelector
|
|
729
|
-
|
|
261
|
+
class="h-100 my-0 pa-2 pr-3"
|
|
262
|
+
v-if="getCurrentView(layout) === ScanView.Coronal"
|
|
730
263
|
v-model:value="scanState.Orientations.Coronal.Slice"
|
|
731
264
|
v-bind="cMinMax"
|
|
732
265
|
/>
|
|
733
266
|
<VerticalSliderSelector
|
|
734
|
-
|
|
267
|
+
class="h-100 my-0 pa-2 pr-3"
|
|
268
|
+
v-if="getCurrentView(layout) === ScanView.Sagittal"
|
|
735
269
|
v-model:value="scanState.Orientations.Sagittal.Slice"
|
|
736
270
|
v-bind="sMinMax"
|
|
737
271
|
/>
|
|
738
|
-
</
|
|
739
|
-
|
|
740
|
-
<div class="top-lhc" v-if="isHovering">
|
|
741
|
-
<div class="white--text">
|
|
742
|
-
{{ getViewName(getCurrentActiveView(layout)) }}
|
|
743
|
-
</div>
|
|
744
|
-
</div>
|
|
745
|
-
</div>
|
|
272
|
+
</v-col>
|
|
273
|
+
</v-row>
|
|
746
274
|
</template>
|
|
747
275
|
</v-hover>
|
|
748
276
|
</div>
|
|
749
277
|
</WebGL3DR>
|
|
750
|
-
|
|
751
|
-
|
|
278
|
+
<ViewerAnnotationModal />
|
|
279
|
+
</v-main>
|
|
752
280
|
<LoadingSpinner v-if="!instanceLoaded" />
|
|
753
281
|
<LoadingSpinner v-if="scanLoading" text="Rendering your scan in 3D" />
|
|
754
|
-
<v-textarea
|
|
755
|
-
v-if="stateOverlay"
|
|
756
|
-
style="
|
|
757
|
-
position: absolute;
|
|
758
|
-
top: 0px;
|
|
759
|
-
right: -0px;
|
|
760
|
-
width: 240px;
|
|
761
|
-
z-index: 10000;
|
|
762
|
-
"
|
|
763
|
-
class="text--white"
|
|
764
|
-
color="white"
|
|
765
|
-
dark
|
|
766
|
-
outlined
|
|
767
|
-
height="800"
|
|
768
|
-
:value="JSON.stringify(scanState, null, 2)"
|
|
769
|
-
disabled
|
|
770
|
-
/>
|
|
771
282
|
</v-card>
|
|
772
283
|
</v-dialog>
|
|
773
284
|
</template>
|
|
285
|
+
|
|
774
286
|
<!-- /* c8 ignore stop */ -->
|
|
775
287
|
<script setup lang="ts">
|
|
776
|
-
import LoadingSpinner from "@/components/loading/LoadingSpinner.vue";
|
|
777
|
-
import WebGL3DR from "@/components/WebGL3DR.vue";
|
|
778
|
-
import DoubleSliderSelector from "@/components/sliders/DoubleSliderSelector.vue";
|
|
779
|
-
import SliderSelector from "@/components/selectors/ValueSelector.vue";
|
|
780
|
-
import VerticalSliderSelector from "@/components/sliders/VerticalSliderSelector.vue";
|
|
781
|
-
|
|
782
288
|
import { generateDivStyleForLayout } from "@/helpers/layoutOverlayStyle";
|
|
783
|
-
|
|
784
289
|
import {
|
|
290
|
+
DataOverlayActions,
|
|
785
291
|
FileManagementActions,
|
|
786
292
|
FrontEndInterfaces,
|
|
787
293
|
LayoutActions,
|
|
788
294
|
NotificationsActions,
|
|
789
|
-
PositionData,
|
|
790
|
-
PresetsActions,
|
|
791
295
|
ScanMovementActions,
|
|
296
|
+
ScanStateActions,
|
|
792
297
|
ScanView,
|
|
793
298
|
SlidersActions,
|
|
794
299
|
ViewSelectionActions,
|
|
795
300
|
} from "@3cr/types-ts";
|
|
796
|
-
import {
|
|
797
|
-
defineEmits,
|
|
798
|
-
mergeProps,
|
|
799
|
-
nextTick,
|
|
800
|
-
ref,
|
|
801
|
-
unref,
|
|
802
|
-
watch,
|
|
803
|
-
WatchSource,
|
|
804
|
-
} from "vue";
|
|
805
|
-
|
|
301
|
+
import {ComponentInstance, computed, nextTick, onBeforeUnmount, onMounted, ref, unref, watch, WatchSource} from "vue";
|
|
806
302
|
import {
|
|
807
303
|
checkIsDemo,
|
|
304
|
+
demoOptions,
|
|
808
305
|
demoType,
|
|
809
|
-
getDemoOption,
|
|
810
306
|
isDemo,
|
|
811
307
|
m_demo,
|
|
812
308
|
m_demoLicenceSendToParty,
|
|
813
309
|
m_demoLicenceShareToMobile,
|
|
310
|
+
m_demoLicenseEnableCloudStorage,
|
|
814
311
|
m_demoPatient,
|
|
312
|
+
m_demoPatientEnableCloudStorage,
|
|
815
313
|
m_demoPatientSendToParty,
|
|
816
314
|
m_demoPatientShareToMobile,
|
|
817
|
-
} from "@/demo/options";
|
|
315
|
+
} from "@/components/demo/options";
|
|
818
316
|
import { handleNotification } from "@/notifications/notification";
|
|
819
317
|
import { LoadViewerPayload } from "@/models/LoadViewerPayload";
|
|
820
318
|
import {
|
|
821
319
|
defaultLoadViewerOptions,
|
|
822
320
|
LoadViewerOptions,
|
|
823
321
|
} from "@/models/LoadViewerOptions";
|
|
824
|
-
import { PayloadHandler, sendPayload } from "@/dataLayer/payloadHandler";
|
|
825
322
|
import {
|
|
826
323
|
cMinMax,
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
currentGreyscalePreset,
|
|
830
|
-
huMinMax,
|
|
324
|
+
getCurrentView,
|
|
325
|
+
getViewName,
|
|
831
326
|
initialScanState,
|
|
327
|
+
isFullscreen,
|
|
328
|
+
isHorizontalFlip,
|
|
832
329
|
isLayout1x3,
|
|
833
330
|
isLayout2x2,
|
|
331
|
+
isVerticalFlip,
|
|
834
332
|
previousLayout,
|
|
835
333
|
scanState,
|
|
334
|
+
setActiveAnnotation,
|
|
335
|
+
setDataOverlayState,
|
|
336
|
+
setInitialDataOverlayState,
|
|
836
337
|
setInitialScanStateFromPayload,
|
|
837
338
|
setScanStateFromPayload,
|
|
838
339
|
sMinMax,
|
|
839
|
-
sSlider,
|
|
840
340
|
thresholdSlider,
|
|
841
341
|
tMinMax,
|
|
842
342
|
transactionStarted,
|
|
843
|
-
tSlider,
|
|
844
343
|
windowSlider,
|
|
845
344
|
} from "@/dataLayer/scanState";
|
|
846
|
-
import
|
|
847
|
-
import {
|
|
848
|
-
import
|
|
849
|
-
import
|
|
850
|
-
import
|
|
851
|
-
import
|
|
852
|
-
import DemoLicenceShareToMobileModal from "@/demo/licence/DemoLicenceShareToMobileModal.vue";
|
|
853
|
-
import DemoLicenceSendToPartyModal from "@/demo/licence/DemoLicenceSendToPartyModal.vue";
|
|
345
|
+
import introJs from "intro.js";
|
|
346
|
+
import type { IntroJs } from "intro.js/src/intro";
|
|
347
|
+
import "intro.js/introjs.css";
|
|
348
|
+
import WebGL3DR from "@/components/WebGL3DR.vue";
|
|
349
|
+
import { useViewer3cr } from "@/dataLayer/useViewer3cr";
|
|
350
|
+
import { useNavigationCubeObserver } from "@/components/modal/composables/useNavigationCubeObserver";
|
|
854
351
|
|
|
855
352
|
export interface Props {
|
|
856
353
|
payload?: LoadViewerPayload;
|
|
@@ -860,6 +357,7 @@ export interface Props {
|
|
|
860
357
|
const emit = defineEmits<{
|
|
861
358
|
instanceLoaded: [void];
|
|
862
359
|
}>();
|
|
360
|
+
|
|
863
361
|
const props = withDefaults(defineProps<Props>(), {
|
|
864
362
|
payload: () => ({
|
|
865
363
|
Url: "https://webgl-3dr.singular.health/test_scans/8bdddee1-e581-485d-827d-6aa12eef2fc8/Head+Axial+Axial.3vxl",
|
|
@@ -871,86 +369,94 @@ const props = withDefaults(defineProps<Props>(), {
|
|
|
871
369
|
options: () => defaultLoadViewerOptions,
|
|
872
370
|
});
|
|
873
371
|
|
|
874
|
-
const
|
|
875
|
-
const
|
|
372
|
+
const viewer3cr = useViewer3cr();
|
|
373
|
+
const webGl3dr = ref<typeof WebGL3DR>();
|
|
374
|
+
const observer = ref<ResizeObserver>();
|
|
876
375
|
const value = ref<boolean>(false);
|
|
877
|
-
const
|
|
878
|
-
const
|
|
879
|
-
const drawer = ref<boolean>(true);
|
|
880
|
-
const drawerCollapsed = ref<boolean>(true);
|
|
376
|
+
const drawer = ref<boolean>(false);
|
|
377
|
+
const expanded = ref<number | undefined>();
|
|
881
378
|
const scanLoading = ref<boolean>(true);
|
|
882
379
|
const instanceLoaded = ref<boolean>(true);
|
|
883
|
-
const
|
|
884
|
-
const footerItems = ref([
|
|
885
|
-
{
|
|
886
|
-
text: "Reset Scan",
|
|
887
|
-
tooltip: "Resets your scan to original position and settings in all views",
|
|
888
|
-
icon: "refresh",
|
|
889
|
-
color: "red",
|
|
890
|
-
click: async () => {
|
|
891
|
-
await payloadHandler.viewSelection(ViewSelectionActions.vs05);
|
|
892
|
-
await payloadHandler.viewSelection(ViewSelectionActions.vs06);
|
|
893
|
-
},
|
|
894
|
-
conditional: () => true,
|
|
895
|
-
},
|
|
896
|
-
{
|
|
897
|
-
text: "Send to 3rd Party",
|
|
898
|
-
tooltip:
|
|
899
|
-
"Securely share your loaded scan with 3rd parties via email with option to anonymise",
|
|
900
|
-
icon: "send",
|
|
901
|
-
color: "blue",
|
|
902
|
-
click: async () => executeOption("OnSendTo3rdParty"),
|
|
903
|
-
conditional: () => showOption("OnSendTo3rdParty"),
|
|
904
|
-
},
|
|
905
|
-
{
|
|
906
|
-
text: "Share to Mobile / VR",
|
|
907
|
-
tooltip:
|
|
908
|
-
"Share your loaded scan with the 3Dicom Mobile and VR applications to download within 7 days",
|
|
909
|
-
icon: "share",
|
|
910
|
-
color: "yellow",
|
|
911
|
-
click: async () => executeOption("OnShareToMobile"),
|
|
912
|
-
conditional: () => showOption("OnShareToMobile"),
|
|
913
|
-
},
|
|
914
|
-
{
|
|
915
|
-
text: "Screenshot View",
|
|
916
|
-
icon: "screenshot_region",
|
|
917
|
-
color: "green",
|
|
918
|
-
click: async () => executeOption("OnScreenshot"),
|
|
919
|
-
conditional: () => showOption("OnScreenshot"),
|
|
920
|
-
},
|
|
921
|
-
]);
|
|
922
|
-
const miniMenu = ref([
|
|
923
|
-
{
|
|
924
|
-
icon: "display_settings",
|
|
925
|
-
text: "Tissue Density",
|
|
926
|
-
tooltip:
|
|
927
|
-
"Change the range of visible anatomy in the scan by only showing areas with certain density of tissue",
|
|
928
|
-
},
|
|
929
|
-
]);
|
|
930
|
-
const rotationDeg = ref<number>(0);
|
|
931
|
-
const stateOverlay = ref<boolean>(false);
|
|
380
|
+
const elements = ref<ComponentInstance<any>[]>([]);
|
|
932
381
|
const m_closeDialog = ref<boolean>(false);
|
|
382
|
+
const introJsOpenMenu = ref<boolean>(false);
|
|
383
|
+
const introJsDisableMenu = ref<boolean>(false);
|
|
384
|
+
const modals = ref<Record<ScanView, boolean>>({
|
|
385
|
+
[ScanView.Volume]: false,
|
|
386
|
+
[ScanView.Sagittal]: false,
|
|
387
|
+
[ScanView.Coronal]: false,
|
|
388
|
+
[ScanView.Transverse]: false,
|
|
389
|
+
});
|
|
933
390
|
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
391
|
+
const tier = computed(() => {
|
|
392
|
+
const value = demoType.value;
|
|
393
|
+
return value.charAt(0).toUpperCase() + value.substring(1);
|
|
394
|
+
});
|
|
937
395
|
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
)
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
396
|
+
const opts = computed(() => {
|
|
397
|
+
return unref(isDemo) ? demoOptions.value : props.options;
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
onMounted(() => {
|
|
401
|
+
viewer3cr.addInterfaceHandler(FrontEndInterfaces.notifications, async (message: string, action: string) => {
|
|
402
|
+
await handleNotification(action as NotificationsActions, message);
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
viewer3cr.addActionHandler(FileManagementActions.fm02, async (message: string) => {
|
|
406
|
+
setInitialScanStateFromPayload(message);
|
|
407
|
+
await nextTick();
|
|
408
|
+
transactionStarted.value = false;
|
|
409
|
+
scanLoading.value = false;
|
|
410
|
+
drawer.value = true;
|
|
411
|
+
if (unref(isDemo)) {
|
|
412
|
+
await viewer3cr.loadDataOverlay("https://webgl-3dr.singular.health/test_scans/sample_annotations.json");
|
|
413
|
+
}
|
|
414
|
+
observer.value = useNavigationCubeObserver(webGl3dr.value!.canvas);
|
|
415
|
+
await startIntro();
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
viewer3cr.addActionHandler(ScanStateActions.ss01, async (message: string) => {
|
|
419
|
+
await setScanStateFromPayload(message);
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
viewer3cr.addActionHandler(ScanStateActions.ss02, (message: string) => {
|
|
423
|
+
setDataOverlayState(message);
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
viewer3cr.addActionHandler(DataOverlayActions.do01, (message: string) => {
|
|
427
|
+
setInitialDataOverlayState(message);
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
viewer3cr.addActionHandler(DataOverlayActions.do02, (message: string) => {
|
|
431
|
+
setActiveAnnotation(message);
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
viewer3cr.addActionHandler(DataOverlayActions.do07, (message: string) => {
|
|
435
|
+
setActiveAnnotation(message);
|
|
436
|
+
});
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
onBeforeUnmount(() => {
|
|
440
|
+
if (observer.value) {
|
|
441
|
+
observer.value.disconnect();
|
|
945
442
|
}
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
function showOption(key: keyof LoadViewerOptions): boolean {
|
|
446
|
+
return opts.value[key] !== undefined;
|
|
946
447
|
}
|
|
448
|
+
|
|
947
449
|
async function executeOption(key: keyof LoadViewerOptions) {
|
|
948
|
-
const functionToExecute =
|
|
450
|
+
const functionToExecute = opts.value[key];
|
|
949
451
|
if (functionToExecute !== undefined) {
|
|
950
452
|
await functionToExecute();
|
|
951
453
|
}
|
|
952
454
|
}
|
|
953
455
|
|
|
456
|
+
function onActionModal(value: boolean, view: ScanView): void {
|
|
457
|
+
modals.value[view] = value;
|
|
458
|
+
}
|
|
459
|
+
|
|
954
460
|
type WatchSlidersType = { [key in SlidersActions]: WatchSource<number> };
|
|
955
461
|
const watchSliders: WatchSlidersType = {
|
|
956
462
|
[SlidersActions.sl01]: () => scanState.value.Display.Brightness,
|
|
@@ -975,7 +481,7 @@ for (const slider of Object.keys(watchSliders)) {
|
|
|
975
481
|
watch(
|
|
976
482
|
watchSliders[slider as keyof WatchSlidersType],
|
|
977
483
|
async (value: number) => {
|
|
978
|
-
await
|
|
484
|
+
await viewer3cr.sliderHandler(slider as SlidersActions, value);
|
|
979
485
|
}
|
|
980
486
|
);
|
|
981
487
|
}
|
|
@@ -1000,7 +506,7 @@ for (const scanMovement of Object.keys(watchScanMovement)) {
|
|
|
1000
506
|
watch(
|
|
1001
507
|
watchScanMovement[scanMovement as keyof typeof watchScanMovement],
|
|
1002
508
|
async (value: number) => {
|
|
1003
|
-
await
|
|
509
|
+
await viewer3cr.scanMovementHandler(
|
|
1004
510
|
scanMovement as ScanMovementActions,
|
|
1005
511
|
value
|
|
1006
512
|
);
|
|
@@ -1008,33 +514,6 @@ for (const scanMovement of Object.keys(watchScanMovement)) {
|
|
|
1008
514
|
);
|
|
1009
515
|
}
|
|
1010
516
|
|
|
1011
|
-
function getCurrentActiveView(position: PositionData): ScanView {
|
|
1012
|
-
return unref(scanState).Layout.PositionData.length !== 1
|
|
1013
|
-
? position.DefaultView
|
|
1014
|
-
: unref(scanState).CurrentView;
|
|
1015
|
-
}
|
|
1016
|
-
function getViewName(index: number) {
|
|
1017
|
-
return Object.values(ScanView)
|
|
1018
|
-
.filter((value) => typeof value === "string")
|
|
1019
|
-
.map((x) => {
|
|
1020
|
-
if (x === "Volume") return "3D Volume";
|
|
1021
|
-
return x;
|
|
1022
|
-
})[index];
|
|
1023
|
-
}
|
|
1024
|
-
async function fullscreenLayout(view: ScanView) {
|
|
1025
|
-
await payloadHandler.layouts(LayoutActions.lo01);
|
|
1026
|
-
await payloadHandler.viewSelection(`vs_0${view + 1}` as ViewSelectionActions);
|
|
1027
|
-
}
|
|
1028
|
-
|
|
1029
|
-
async function closeModal() {
|
|
1030
|
-
await executeOption("OnExitViewer");
|
|
1031
|
-
m_closeDialog.value = false;
|
|
1032
|
-
value.value = false;
|
|
1033
|
-
}
|
|
1034
|
-
async function closeModalSave() {
|
|
1035
|
-
await executeOption("OnSaveSession");
|
|
1036
|
-
await closeModal();
|
|
1037
|
-
}
|
|
1038
517
|
function alterValue(val: boolean) {
|
|
1039
518
|
if (!val) {
|
|
1040
519
|
m_closeDialog.value = true;
|
|
@@ -1042,119 +521,221 @@ function alterValue(val: boolean) {
|
|
|
1042
521
|
} else {
|
|
1043
522
|
checkIsDemo(props.payload);
|
|
1044
523
|
}
|
|
1045
|
-
|
|
1046
524
|
value.value = val;
|
|
1047
525
|
}
|
|
1048
|
-
|
|
526
|
+
|
|
527
|
+
async function loadSession(url: string): Promise<void> {
|
|
528
|
+
if (url.includes('3crds')) {
|
|
529
|
+
await viewer3cr.loadDataOverlay(url);
|
|
530
|
+
} else if (url.includes('3crms')) {
|
|
531
|
+
await viewer3cr.loadMcad(url);
|
|
532
|
+
} else if (url.includes('3crs')) {
|
|
533
|
+
await viewer3cr.loadScanSession(url);
|
|
534
|
+
} else {
|
|
535
|
+
throw new Error('Invalid URL type');
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
async function load(): Promise<void> {
|
|
1049
540
|
instanceLoaded.value = true;
|
|
1050
541
|
scanLoading.value = true;
|
|
1051
|
-
await
|
|
542
|
+
await viewer3cr.loadScan(props.payload);
|
|
1052
543
|
}
|
|
1053
544
|
|
|
1054
|
-
async function
|
|
1055
|
-
|
|
1056
|
-
|
|
545
|
+
async function onMouseEnter(): Promise<void> {
|
|
546
|
+
await viewer3cr.hoverOverCanvas(true);
|
|
547
|
+
}
|
|
1057
548
|
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
scanLoading.value = false;
|
|
1061
|
-
drawerCollapsed.value = false;
|
|
1062
|
-
await payloadHandler.hoverOverCanvas(false);
|
|
1063
|
-
await payloadHandler.setNavCubeVisibility(false);
|
|
1064
|
-
}
|
|
549
|
+
async function onMouseLeave(): Promise<void> {
|
|
550
|
+
await viewer3cr.hoverOverCanvas(false);
|
|
1065
551
|
}
|
|
1066
552
|
|
|
1067
|
-
function
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
)
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
553
|
+
async function startIntro(): Promise<void> {
|
|
554
|
+
let intro: IntroJs | null = null;
|
|
555
|
+
|
|
556
|
+
const sleep = (delay: number) =>
|
|
557
|
+
new Promise((resolve) => setTimeout(resolve, delay));
|
|
558
|
+
|
|
559
|
+
const refreshHandle = watch(scanState, () => {
|
|
560
|
+
intro?.refresh(true);
|
|
561
|
+
});
|
|
562
|
+
|
|
563
|
+
const expandHandle = watch(expanded, async () => {
|
|
564
|
+
// For some reason introjs refresh by itself doesn't work here
|
|
565
|
+
if (intro && intro.currentStep() === 1) {
|
|
566
|
+
await sleep(400);
|
|
567
|
+
const skinToBone = document.getElementById('skin-to-bone');
|
|
568
|
+
const floatingElement = document.querySelector('.introjsFloatingElement') as HTMLElement | null;
|
|
569
|
+
intro._introItems[1].element = skinToBone ?? floatingElement;
|
|
570
|
+
intro.refresh(true);
|
|
571
|
+
}
|
|
572
|
+
});
|
|
573
|
+
|
|
574
|
+
const blurActiveElement = () => {
|
|
575
|
+
(document.activeElement as HTMLElement).blur();
|
|
576
|
+
};
|
|
577
|
+
|
|
578
|
+
intro = await introJs()
|
|
579
|
+
.setOptions({
|
|
580
|
+
exitOnOverlayClick: false,
|
|
581
|
+
keyboardNavigation: false,
|
|
582
|
+
dontShowAgain: true,
|
|
583
|
+
steps: [
|
|
584
|
+
{
|
|
585
|
+
title: "Take the Viewer Tour",
|
|
586
|
+
intro:
|
|
587
|
+
`Welcome to the ${tier.value} Online DICOM Viewer.` +
|
|
588
|
+
"<br/><br/>Take the short interative tour to better visualize your medical images in 2D and 3D.",
|
|
589
|
+
position: "floating",
|
|
590
|
+
},
|
|
591
|
+
{
|
|
592
|
+
title: "Density Slider",
|
|
593
|
+
intro:
|
|
594
|
+
"Move the <em>lower density slider</em> to the right to hide softer tissue like skin and view internal organs." +
|
|
595
|
+
"<br/><br/>For more precise control, change the numbers in the boxes above or use the <em>Fine Adjustment</em> slider below.",
|
|
596
|
+
element: "#skin-to-bone",
|
|
597
|
+
position: "right",
|
|
598
|
+
},
|
|
599
|
+
{
|
|
600
|
+
title: "3D Viewing Controls",
|
|
601
|
+
intro:
|
|
602
|
+
"With your cursor placed in the 3D view, <b>scroll to zoom</b> & <b>click and drag to rotate</b>." +
|
|
603
|
+
"<br/><br/>You can also view the 3D view in full screen, reset the view, and cut into the 3D model from each side using the <em>3D slicing tool</em>.",
|
|
604
|
+
element: `#view-${ScanView.Volume}`,
|
|
605
|
+
},
|
|
606
|
+
{
|
|
607
|
+
title: "2D Viewing Controls",
|
|
608
|
+
intro:
|
|
609
|
+
"With your mouse in the 2D views, <b>scroll to navigate through the 2D images</b>, and use <b>Shift + Arrows to Pan and Zoom</b>." +
|
|
610
|
+
"<br/><br/>Use the icons to <em>Fullscreen</em>, <em>Reset</em>, <em>Rotate</em>, and <em>Flip</em> each individual 2D view.",
|
|
611
|
+
element: `#view-${ScanView.Sagittal}`,
|
|
612
|
+
},
|
|
613
|
+
{
|
|
614
|
+
title: "Global settings",
|
|
615
|
+
intro:
|
|
616
|
+
"Accurately change the <b>brightness</b>, <b>contrast</b>, and <b>opacity</b> to enhance the appearance of your medical images." +
|
|
617
|
+
"<br/><br/>Adjust the sensitivity of mouse and keyboard inputs to suit your preferences.",
|
|
618
|
+
element: "#settings-card",
|
|
619
|
+
position: "right",
|
|
620
|
+
},
|
|
621
|
+
{
|
|
622
|
+
title: "Stay Tuned for Updates",
|
|
623
|
+
intro:
|
|
624
|
+
'<img src="https://c.tenor.com/S4Sz_yvlLn4AAAAC/cats-cat.gif" alt="cat" width="260" height="260">' +
|
|
625
|
+
"<br/>Us working hard on new features",
|
|
626
|
+
position: "floating",
|
|
627
|
+
},
|
|
628
|
+
],
|
|
629
|
+
})
|
|
630
|
+
.onbeforechange(function (
|
|
631
|
+
el: HTMLElement,
|
|
632
|
+
step: number,
|
|
633
|
+
direction: "forward" | "backward"
|
|
634
|
+
) {
|
|
635
|
+
// Focus on volume view if moving to step 2
|
|
636
|
+
modals.value[ScanView.Volume] = step === 2;
|
|
637
|
+
|
|
638
|
+
// Focus on sagittal view if moving to step 3
|
|
639
|
+
modals.value[ScanView.Sagittal] = step === 3;
|
|
640
|
+
|
|
641
|
+
// If the user has enabled full-screen, turn it off for steps 2 and 3
|
|
642
|
+
if (isFullscreen.value && (step === 2 || step === 3)) {
|
|
643
|
+
viewer3cr
|
|
644
|
+
.layouts(previousLayout.value)
|
|
645
|
+
.then(() => nextTick())
|
|
646
|
+
.then(() => this.refresh(true))
|
|
647
|
+
.then(() => direction === "forward" ? this.nextStep() : this.previousStep());
|
|
648
|
+
return false;
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
// Wait for menu animation before focusing if moving to step 4
|
|
652
|
+
if (!introJsOpenMenu.value && step === 4) {
|
|
653
|
+
introJsOpenMenu.value = true;
|
|
654
|
+
introJsDisableMenu.value = true;
|
|
655
|
+
sleep(400)
|
|
656
|
+
.then(() => this.refresh(true))
|
|
657
|
+
.then(() => direction === "forward" ? this.nextStep() : this.previousStep());
|
|
658
|
+
return false;
|
|
659
|
+
} else if (introJsOpenMenu.value && step !== 4) {
|
|
660
|
+
introJsOpenMenu.value = false;
|
|
661
|
+
introJsDisableMenu.value = false;
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
// Only show Don't Show Again on last step
|
|
665
|
+
const introjsDontShowAgain = document.querySelector(".introjs-dontShowAgain") as HTMLElement | null;
|
|
666
|
+
if (introjsDontShowAgain) {
|
|
667
|
+
const lastStep = this._options.steps.length - 1;
|
|
668
|
+
introjsDontShowAgain.style.display = step !== lastStep ? "none" : "block";
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
return true;
|
|
672
|
+
})
|
|
673
|
+
.onchange(() => {
|
|
674
|
+
setTimeout(() => blurActiveElement(), 500);
|
|
675
|
+
})
|
|
676
|
+
.onexit(() => {
|
|
677
|
+
introJsOpenMenu.value = false;
|
|
678
|
+
introJsDisableMenu.value = false;
|
|
679
|
+
setTimeout(() => blurActiveElement(), 500);
|
|
680
|
+
refreshHandle();
|
|
681
|
+
expandHandle();
|
|
682
|
+
})
|
|
683
|
+
.start();
|
|
1081
684
|
}
|
|
1082
685
|
|
|
1083
686
|
defineExpose({
|
|
1084
687
|
alterValue,
|
|
688
|
+
loadSession,
|
|
1085
689
|
load,
|
|
1086
|
-
|
|
1087
|
-
sendPayload,
|
|
1088
|
-
getIconForPreset,
|
|
690
|
+
startIntro,
|
|
1089
691
|
thresholdSlider,
|
|
1090
692
|
windowSlider,
|
|
1091
693
|
m_closeDialog,
|
|
1092
|
-
closeModal,
|
|
1093
694
|
scanState,
|
|
1094
695
|
initialScanState,
|
|
1095
|
-
footerItems,
|
|
1096
|
-
getCurrentActiveView,
|
|
1097
|
-
getViewName,
|
|
1098
|
-
fullscreenLayout,
|
|
1099
696
|
value,
|
|
1100
697
|
transactionStarted,
|
|
1101
698
|
});
|
|
1102
699
|
</script>
|
|
700
|
+
|
|
1103
701
|
<style>
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
}
|
|
1107
|
-
.v-expansion-panel-content__wrap {
|
|
1108
|
-
padding: 0 8px 4px;
|
|
1109
|
-
flex: 1 1 auto;
|
|
1110
|
-
max-width: 100%;
|
|
702
|
+
#view-0 {
|
|
703
|
+
cursor: grab;
|
|
1111
704
|
}
|
|
705
|
+
|
|
1112
706
|
.bordered-event-window {
|
|
1113
707
|
position: absolute;
|
|
1114
708
|
border: 1px rgba(128, 128, 128, 0.56) solid;
|
|
1115
|
-
|
|
709
|
+
transition: border 0.5s;
|
|
1116
710
|
}
|
|
711
|
+
|
|
1117
712
|
.bordered-event-window:hover {
|
|
1118
|
-
|
|
1119
|
-
border: 2px dashed rgb(0, 152, 253);
|
|
1120
|
-
}
|
|
1121
|
-
.v-expansion-panel--active > .v-expansion-panel-header {
|
|
1122
|
-
min-height: 48px;
|
|
713
|
+
border: 1px dashed rgb(0, 152, 253);
|
|
1123
714
|
}
|
|
1124
715
|
|
|
1125
|
-
.
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
top: 0;
|
|
1129
|
-
}
|
|
1130
|
-
.v-slider.v-slider--vertical {
|
|
1131
|
-
height: 90%;
|
|
716
|
+
.motif-background {
|
|
717
|
+
background: url("data:image/svg+xml,%3Csvg width='992' height='560' viewBox='15 7 992 560' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Crect x='15' y='1' width='992' height='567' fill='url(%23paint0_linear_504_579)'/%3E%3Cg filter='url(%23filter0_i_504_579)'%3E%3Cpath d='M567.029 291.5C574.21 421.339 748.15 561 825.931 561H12L12.0001 0C153.713 0 580.363 0 580.363 0C684.258 142 560.918 181 567.029 291.5Z' fill='%231B2E43' fill-opacity='0.9'/%3E%3C/g%3E%3Cg filter='url(%23filter1_i_504_579)'%3E%3Cpath d='M435.911 260.5C462.579 433.5 652.034 561 729.815 561H12L12.0001 0C233.678 0 409.954 92.1126 435.911 260.5Z' fill='%231B2E43' fill-opacity='0.9'/%3E%3C/g%3E%3Cg filter='url(%23filter2_i_504_579)'%3E%3Ccircle cx='46' cy='521' r='93' fill='%231B2E43' fill-opacity='0.3'/%3E%3C/g%3E%3Cdefs%3E%3Cfilter id='filter0_i_504_579' x='12' y='0' width='858.931' height='606' filterUnits='userSpaceOnUse' color-interpolation-filters='sRGB'%3E%3CfeFlood flood-opacity='0' result='BackgroundImageFix'/%3E%3CfeBlend mode='normal' in='SourceGraphic' in2='BackgroundImageFix' result='shape'/%3E%3CfeColorMatrix in='SourceAlpha' type='matrix' values='0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0' result='hardAlpha'/%3E%3CfeOffset dx='45' dy='45'/%3E%3CfeGaussianBlur stdDeviation='50'/%3E%3CfeComposite in2='hardAlpha' operator='arithmetic' k2='-1' k3='1'/%3E%3CfeColorMatrix type='matrix' values='0 0 0 0 0.653125 0 0 0 0 0.761642 0 0 0 0 0.916667 0 0 0 0.15 0'/%3E%3CfeBlend mode='normal' in2='shape' result='effect1_innerShadow_504_579'/%3E%3C/filter%3E%3Cfilter id='filter1_i_504_579' x='12' y='0' width='762.815' height='606' filterUnits='userSpaceOnUse' color-interpolation-filters='sRGB'%3E%3CfeFlood flood-opacity='0' result='BackgroundImageFix'/%3E%3CfeBlend mode='normal' in='SourceGraphic' in2='BackgroundImageFix' result='shape'/%3E%3CfeColorMatrix in='SourceAlpha' type='matrix' values='0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0' result='hardAlpha'/%3E%3CfeOffset dx='45' dy='45'/%3E%3CfeGaussianBlur stdDeviation='50'/%3E%3CfeComposite in2='hardAlpha' operator='arithmetic' k2='-1' k3='1'/%3E%3CfeColorMatrix type='matrix' values='0 0 0 0 0.653125 0 0 0 0 0.761642 0 0 0 0 0.916667 0 0 0 0.25 0'/%3E%3CfeBlend mode='normal' in2='shape' result='effect1_innerShadow_504_579'/%3E%3C/filter%3E%3Cfilter id='filter2_i_504_579' x='-47' y='428' width='231' height='231' filterUnits='userSpaceOnUse' color-interpolation-filters='sRGB'%3E%3CfeFlood flood-opacity='0' result='BackgroundImageFix'/%3E%3CfeBlend mode='normal' in='SourceGraphic' in2='BackgroundImageFix' result='shape'/%3E%3CfeColorMatrix in='SourceAlpha' type='matrix' values='0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0' result='hardAlpha'/%3E%3CfeOffset dx='45' dy='45'/%3E%3CfeGaussianBlur stdDeviation='50'/%3E%3CfeComposite in2='hardAlpha' operator='arithmetic' k2='-1' k3='1'/%3E%3CfeColorMatrix type='matrix' values='0 0 0 0 0.653125 0 0 0 0 0.761642 0 0 0 0 0.916667 0 0 0 0.15 0'/%3E%3CfeBlend mode='normal' in2='shape' result='effect1_innerShadow_504_579'/%3E%3C/filter%3E%3ClinearGradient id='paint0_linear_504_579' x1='1251.04' y1='696.83' x2='21.486' y2='-10.2783' gradientUnits='userSpaceOnUse'%3E%3Cstop stop-color='%230E141D'/%3E%3Cstop offset='1' stop-color='%2323405E'/%3E%3C/linearGradient%3E%3C/defs%3E%3C/svg%3E%0A") !important;
|
|
718
|
+
background-size: cover !important;
|
|
1132
719
|
}
|
|
1133
720
|
|
|
1134
|
-
.
|
|
1135
|
-
|
|
1136
|
-
right: 16px;
|
|
1137
|
-
top: 0;
|
|
1138
|
-
height: 100%;
|
|
1139
|
-
}
|
|
1140
|
-
.buttons-in-view {
|
|
1141
|
-
position: absolute;
|
|
1142
|
-
left: 12px;
|
|
1143
|
-
bottom: 12px;
|
|
721
|
+
.introjs-tooltipReferenceLayer {
|
|
722
|
+
z-index: 2401;
|
|
1144
723
|
}
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
724
|
+
|
|
725
|
+
.introjs-overlay, .introjs-helperLayer {
|
|
726
|
+
pointer-events: none;
|
|
727
|
+
z-index: 2401;
|
|
1149
728
|
}
|
|
1150
|
-
|
|
1151
|
-
|
|
729
|
+
|
|
730
|
+
.introjs-dontShowAgain {
|
|
731
|
+
display: none;
|
|
1152
732
|
}
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
733
|
+
|
|
734
|
+
.v-enter-active, .v-leave-active {
|
|
735
|
+
transition: opacity 0.5s ease;
|
|
1156
736
|
}
|
|
1157
|
-
|
|
1158
|
-
|
|
737
|
+
|
|
738
|
+
.v-enter-from, .v-leave-to {
|
|
739
|
+
opacity: 0;
|
|
1159
740
|
}
|
|
1160
741
|
</style>
|