@3cr/viewer-browser 0.0.161 → 0.0.194

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (229) hide show
  1. package/.circleci/config.yml +53 -0
  2. package/__tests__/index.spec.ts +31 -24
  3. package/components.d.ts +32 -0
  4. package/dist/Viewer3CR.js +44 -27
  5. package/dist/Viewer3CR.mjs +22734 -17747
  6. package/dist/Viewer3CR.umd.js +44 -27
  7. package/index.html +6 -8
  8. package/index.ts +9 -5
  9. package/package.json +5 -2
  10. package/playground/index.html +7 -10
  11. package/src/App.vue +12 -4
  12. package/src/__tests__/app.spec.ts +2 -13
  13. package/src/assets/magic_wand.svg +24 -0
  14. package/src/components/WebGL3DR.vue +53 -92
  15. package/src/components/__tests__/webgl3dr.spec.ts +29 -48
  16. package/src/{demo → components/demo}/DemoModal.vue +1 -1
  17. package/src/{demo → components/demo}/DemoPatientModal.vue +4 -1
  18. package/src/components/demo/__tests__/DemoModal.spec.ts +25 -0
  19. package/src/components/demo/__tests__/DemoPatientModal.spec.ts +37 -0
  20. package/src/components/demo/__tests__/options.spec.ts +25 -0
  21. package/src/components/demo/licence/DemoLicenceEnableCloudStorageModal.vue +64 -0
  22. package/src/{demo → components/demo}/licence/DemoLicenceInfoModal.vue +5 -4
  23. package/src/{demo → components/demo}/licence/DemoLicenceSendToPartyModal.vue +6 -4
  24. package/src/{demo → components/demo}/licence/DemoLicenceShareToMobileModal.vue +8 -6
  25. package/src/components/demo/licence/__tests__/DemoLicenceEnableCloudStorageModal.spec.ts +37 -0
  26. package/src/components/demo/licence/__tests__/DemoLicenceInfoModal.spec.ts +37 -0
  27. package/src/components/demo/licence/__tests__/DemoLicenceSendToPartyModal.spec.ts +36 -0
  28. package/src/components/demo/licence/__tests__/DemoLicenceShareToMobileModal.spec.ts +51 -0
  29. package/src/{demo → components/demo}/options.ts +18 -20
  30. package/src/components/demo/patient/DemoPatientEnableCloudStorageModal.vue +64 -0
  31. package/src/{demo → components/demo}/patient/DemoPatientInfoModal.vue +5 -4
  32. package/src/{demo → components/demo}/patient/DemoPatientSendToPartyModal.vue +4 -3
  33. package/src/{demo → components/demo}/patient/DemoPatientShareToMobileModal.vue +8 -6
  34. package/src/components/demo/patient/__tests__/DemoPatientEnableCloudStorageModal.spec.ts +37 -0
  35. package/src/components/demo/patient/__tests__/DemoPatientInfoModal.spec.ts +37 -0
  36. package/src/components/demo/patient/__tests__/DemoPatientSendToPartyModal.spec.ts +36 -0
  37. package/src/components/demo/patient/__tests__/DemoPatientShareToMobileModal.spec.ts +51 -0
  38. package/src/components/loading/LoadingSpinner.vue +1 -1
  39. package/src/components/modal/ActionRail.vue +96 -0
  40. package/src/components/modal/AskAI.vue +250 -0
  41. package/src/components/modal/CloseViewerModal.vue +104 -0
  42. package/src/components/modal/MftpWebGL3DRModal.vue +415 -834
  43. package/src/components/modal/ViewerActionRail.vue +123 -0
  44. package/src/components/modal/ViewerAnnotationModal.vue +115 -0
  45. package/src/components/modal/ViewerAnnotations.vue +283 -0
  46. package/src/components/modal/ViewerDisplaySettings.vue +102 -0
  47. package/src/components/modal/ViewerNavigationDrawer.vue +90 -0
  48. package/src/components/modal/ViewerNavigationDrawerContent.vue +126 -0
  49. package/src/components/modal/ViewerNavigationDrawerFooter.vue +111 -0
  50. package/src/components/modal/ViewerNavigationDrawerHeader.vue +63 -0
  51. package/src/components/modal/__tests__/CloseViewerModal.spec.ts +60 -0
  52. package/src/components/modal/__tests__/{mftp-webgl-3dr-modal.spec.ts → MftpWebGL3DRModal.spec.ts} +47 -298
  53. package/src/components/modal/__tests__/ViewerAnnotationModal.spec.ts +79 -0
  54. package/src/components/modal/__tests__/ViewerDisplaySettings.spec.ts +61 -0
  55. package/src/components/modal/__tests__/ViewerNavigationDrawer.spec.ts +32 -0
  56. package/src/components/modal/__tests__/ViewerNavigationDrawerContent.spec.ts +29 -0
  57. package/src/components/modal/__tests__/ViewerNavigationDrawerFooter.spec.ts +43 -0
  58. package/src/components/modal/__tests__/ViewerNavigationDrawerHeader.spec.ts +37 -0
  59. package/src/components/modal/actions/Action.vue +40 -0
  60. package/src/components/modal/actions/Flip3dAction.vue +79 -0
  61. package/src/components/modal/actions/FlipHorizontalAction.vue +36 -0
  62. package/src/components/modal/actions/FlipVerticalAction.vue +36 -0
  63. package/src/components/modal/actions/FullscreenAction.vue +47 -0
  64. package/src/components/modal/actions/NavigationCubeAction.vue +33 -0
  65. package/src/components/modal/actions/PanAction.vue +78 -0
  66. package/src/components/modal/actions/ResetViewAction.vue +29 -0
  67. package/src/components/modal/actions/Rotate2dAction.vue +78 -0
  68. package/src/components/modal/actions/Slice3dAction.vue +71 -0
  69. package/src/components/modal/actions/ZoomAction.vue +70 -0
  70. package/src/components/modal/actions/__tests__/Action.spec.ts +29 -0
  71. package/src/components/modal/actions/__tests__/Flip3dAction.spec.ts +48 -0
  72. package/src/components/modal/actions/__tests__/FlipHorizontalAction.spec.ts +17 -0
  73. package/src/components/modal/actions/__tests__/FlipVerticalAction.spec.ts +17 -0
  74. package/src/components/modal/actions/__tests__/FullscreenAction.spec.ts +28 -0
  75. package/src/components/modal/actions/__tests__/NavigationCubeAction.spec.ts +25 -0
  76. package/src/components/modal/actions/__tests__/PanAction.spec.ts +46 -0
  77. package/src/components/modal/actions/__tests__/ResetViewAction.spec.ts +17 -0
  78. package/src/components/modal/actions/__tests__/Rotate2dAction.spec.ts +23 -0
  79. package/src/components/modal/actions/__tests__/Slice3dAction.spec.ts +14 -0
  80. package/src/components/modal/actions/__tests__/ZoomAction.spec.ts +34 -0
  81. package/src/components/modal/composables/__tests__/useNavigationCubeObserver.spec.ts +56 -0
  82. package/src/components/modal/composables/useEventListener.ts +22 -0
  83. package/src/components/modal/composables/useNavigationCubeObserver.ts +104 -0
  84. package/src/components/selectors/ValueSelector.vue +30 -33
  85. package/src/components/selectors/__tests__/value-selector.spec.ts +1 -1
  86. package/src/components/sliders/DoubleSliderSelector.vue +79 -71
  87. package/src/components/sliders/VerticalSliderSelector.vue +12 -17
  88. package/src/components/sliders/__tests__/double-slider-selector.spec.ts +1 -1
  89. package/src/components/sliders/__tests__/vertical-slider-selector.spec.ts +1 -1
  90. package/src/dataLayer/__tests__/clamp.spec.ts +16 -0
  91. package/src/dataLayer/__tests__/eventHandlers.spec.ts +38 -0
  92. package/src/dataLayer/__tests__/getIconForPreset.spec.ts +40 -0
  93. package/src/dataLayer/__tests__/patchDataOverlay.spec.ts +88 -0
  94. package/src/dataLayer/__tests__/scanState.spec.ts +93 -0
  95. package/src/dataLayer/__tests__/useViewer3cr.spec.ts +10 -0
  96. package/src/dataLayer/__tests__/viewer3cr.spec.ts +331 -0
  97. package/src/dataLayer/clamp.ts +9 -0
  98. package/src/dataLayer/eventHandlers.ts +26 -0
  99. package/src/dataLayer/patchDataOverlay.ts +101 -0
  100. package/src/dataLayer/scanState.ts +105 -26
  101. package/src/dataLayer/useViewer3cr.ts +7 -0
  102. package/src/dataLayer/viewer3cr.ts +410 -0
  103. package/src/helpers/__tests__/layout-overlay-style.spec.ts +24 -22
  104. package/src/helpers/__tests__/model-helper.spec.ts +44 -13
  105. package/src/helpers/layoutOverlayStyle.ts +16 -27
  106. package/src/helpers/modelHelper.ts +62 -10
  107. package/src/models/Callbacks.ts +2 -2
  108. package/src/models/LoadViewerOptions.ts +2 -0
  109. package/src/models/LoadViewerPayload.ts +1 -0
  110. package/src/notifications/notification.ts +3 -4
  111. package/src/plugins/vuetify.ts +5 -0
  112. package/src/services/gpt/__tests__/gpt.service.spec.ts +27 -0
  113. package/src/services/gpt/gpt.service.ts +27 -0
  114. package/static/3cr-types-browser/index.ts +74 -0
  115. package/static/3cr-types-browser/types/Action.ts +6 -0
  116. package/static/3cr-types-browser/types/ActionData.ts +4 -0
  117. package/static/3cr-types-browser/types/AlphaKeys.ts +5 -0
  118. package/static/3cr-types-browser/types/AnchorPoint.ts +12 -0
  119. package/static/3cr-types-browser/types/CallToAction.ts +5 -0
  120. package/static/3cr-types-browser/types/ColourData.ts +7 -0
  121. package/static/3cr-types-browser/types/ColourPresetData.ts +9 -0
  122. package/static/3cr-types-browser/types/CurrentDataOverlayState.ts +6 -0
  123. package/static/3cr-types-browser/types/CurrentScanState.ts +22 -0
  124. package/static/3cr-types-browser/types/DataOverlay.ts +22 -0
  125. package/static/3cr-types-browser/types/DataOverlayActions.ts +14 -0
  126. package/static/3cr-types-browser/types/DataOverlayData.ts +8 -0
  127. package/static/3cr-types-browser/types/DataOverlayEvent.ts +8 -0
  128. package/static/3cr-types-browser/types/DecryptionKey.ts +4 -0
  129. package/static/3cr-types-browser/types/DisplaySettings.ts +10 -0
  130. package/static/3cr-types-browser/types/EmptyPayload.ts +3 -0
  131. package/static/3cr-types-browser/types/EnumPayload.ts +4 -0
  132. package/static/3cr-types-browser/types/FileManagementActions.ts +11 -0
  133. package/static/3cr-types-browser/types/FlipValue.ts +7 -0
  134. package/static/3cr-types-browser/types/FrontEndInterfaces.ts +14 -0
  135. package/static/3cr-types-browser/types/GradientKeys.ts +7 -0
  136. package/static/3cr-types-browser/types/GreyscalePresetData.ts +6 -0
  137. package/static/3cr-types-browser/types/InitialDataOverlayState.ts +6 -0
  138. package/static/3cr-types-browser/types/InitialScanState.ts +19 -0
  139. package/static/3cr-types-browser/types/InteractionType.ts +8 -0
  140. package/static/3cr-types-browser/types/InteractivityActions.ts +6 -0
  141. package/static/3cr-types-browser/types/InteractivityState.ts +4 -0
  142. package/static/3cr-types-browser/types/InvertTransformData.ts +6 -0
  143. package/static/3cr-types-browser/types/LayoutActions.ts +6 -0
  144. package/static/3cr-types-browser/types/LayoutData.ts +7 -0
  145. package/static/3cr-types-browser/types/LoadDataSet.ts +6 -0
  146. package/static/3cr-types-browser/types/LoadSessionState.ts +4 -0
  147. package/static/3cr-types-browser/types/LocalLoadDataset.ts +3 -0
  148. package/static/3cr-types-browser/types/MovementData.ts +7 -0
  149. package/static/3cr-types-browser/types/NavigationCubeActions.ts +8 -0
  150. package/static/3cr-types-browser/types/NavigationCubeData.ts +12 -0
  151. package/static/3cr-types-browser/types/NavigationCubeTransform.ts +9 -0
  152. package/static/3cr-types-browser/types/NotificationPayload.ts +7 -0
  153. package/static/3cr-types-browser/types/NotificationsActions.ts +6 -0
  154. package/static/3cr-types-browser/types/Object.ts +1 -0
  155. package/static/3cr-types-browser/types/ObjectColour.ts +7 -0
  156. package/static/3cr-types-browser/types/ObjectIcon.ts +5 -0
  157. package/static/3cr-types-browser/types/ObjectInvert.ts +7 -0
  158. package/static/3cr-types-browser/types/ObjectSize.ts +7 -0
  159. package/static/3cr-types-browser/types/ObjectSize2D.ts +7 -0
  160. package/static/3cr-types-browser/types/ObjectVisible.ts +5 -0
  161. package/static/3cr-types-browser/types/PositionData.ts +14 -0
  162. package/static/3cr-types-browser/types/PresetsActions.ts +4 -0
  163. package/static/3cr-types-browser/types/RotationValue.ts +7 -0
  164. package/static/3cr-types-browser/types/ScanMovementActions.ts +27 -0
  165. package/static/3cr-types-browser/types/ScanMovementData.ts +3 -0
  166. package/static/3cr-types-browser/types/ScanOrientationActions.ts +6 -0
  167. package/static/3cr-types-browser/types/ScanStateActions.ts +4 -0
  168. package/static/3cr-types-browser/types/ScanView.ts +6 -0
  169. package/static/3cr-types-browser/types/SettingsData.ts +12 -0
  170. package/static/3cr-types-browser/types/SlicerData.ts +9 -0
  171. package/static/3cr-types-browser/types/SliderValue.ts +4 -0
  172. package/static/3cr-types-browser/types/SlidersActions.ts +18 -0
  173. package/static/3cr-types-browser/types/Vector2Data.ts +5 -0
  174. package/static/3cr-types-browser/types/Vector3Data.ts +6 -0
  175. package/static/3cr-types-browser/types/VectorMovementData.ts +8 -0
  176. package/static/3cr-types-browser/types/ViewInteractiveMode.ts +5 -0
  177. package/static/3cr-types-browser/types/ViewOrientation.ts +8 -0
  178. package/static/3cr-types-browser/types/ViewOrientations.ts +10 -0
  179. package/static/3cr-types-browser/types/ViewSelectionActions.ts +9 -0
  180. package/static/3cr-types-browser/types/ViewToggleData.ts +7 -0
  181. package/static/3cr-types-browser/types/VolumeOrientation.ts +7 -0
  182. package/test/helper.ts +10 -1
  183. package/test/setup.ts +13 -0
  184. package/tsconfig.json +1 -0
  185. package/vite.config.mts +1 -0
  186. package/coverage/3cr-viewer-browser/index.html +0 -116
  187. package/coverage/3cr-viewer-browser/index.ts.html +0 -199
  188. package/coverage/3cr-viewer-browser/src/App.vue.html +0 -316
  189. package/coverage/3cr-viewer-browser/src/components/WebGL3DR.vue.html +0 -442
  190. package/coverage/3cr-viewer-browser/src/components/icons/index.html +0 -116
  191. package/coverage/3cr-viewer-browser/src/components/icons/liver.vue.html +0 -148
  192. package/coverage/3cr-viewer-browser/src/components/index.html +0 -116
  193. package/coverage/3cr-viewer-browser/src/components/loading/LoadingSpinner.vue.html +0 -622
  194. package/coverage/3cr-viewer-browser/src/components/loading/index.html +0 -116
  195. package/coverage/3cr-viewer-browser/src/components/modal/MftpWebGL3DRModal.vue.html +0 -3118
  196. package/coverage/3cr-viewer-browser/src/components/modal/index.html +0 -116
  197. package/coverage/3cr-viewer-browser/src/components/selectors/ValueSelector.vue.html +0 -358
  198. package/coverage/3cr-viewer-browser/src/components/selectors/index.html +0 -116
  199. package/coverage/3cr-viewer-browser/src/components/sliders/DoubleSliderSelector.vue.html +0 -487
  200. package/coverage/3cr-viewer-browser/src/components/sliders/VerticalSliderSelector.vue.html +0 -358
  201. package/coverage/3cr-viewer-browser/src/components/sliders/index.html +0 -131
  202. package/coverage/3cr-viewer-browser/src/dataLayer/iconData.ts.html +0 -118
  203. package/coverage/3cr-viewer-browser/src/dataLayer/index.html +0 -146
  204. package/coverage/3cr-viewer-browser/src/dataLayer/payloadHandler.ts.html +0 -463
  205. package/coverage/3cr-viewer-browser/src/dataLayer/scanState.ts.html +0 -598
  206. package/coverage/3cr-viewer-browser/src/helpers/index.html +0 -146
  207. package/coverage/3cr-viewer-browser/src/helpers/layoutOverlayStyle.ts.html +0 -406
  208. package/coverage/3cr-viewer-browser/src/helpers/modelHelper.ts.html +0 -412
  209. package/coverage/3cr-viewer-browser/src/helpers/utils.ts.html +0 -133
  210. package/coverage/3cr-viewer-browser/src/index.html +0 -131
  211. package/coverage/3cr-viewer-browser/src/main.ts.html +0 -124
  212. package/coverage/3cr-viewer-browser/src/models/LoadViewerOptions.ts.html +0 -166
  213. package/coverage/3cr-viewer-browser/src/models/index.html +0 -116
  214. package/coverage/3cr-viewer-browser/src/notifications/index.html +0 -116
  215. package/coverage/3cr-viewer-browser/src/notifications/notification.ts.html +0 -238
  216. package/coverage/3cr-viewer-browser/src/plugins/index.html +0 -131
  217. package/coverage/3cr-viewer-browser/src/plugins/index.ts.html +0 -136
  218. package/coverage/3cr-viewer-browser/src/plugins/vuetify.ts.html +0 -220
  219. package/coverage/base.css +0 -224
  220. package/coverage/block-navigation.js +0 -87
  221. package/coverage/favicon.png +0 -0
  222. package/coverage/index.html +0 -296
  223. package/coverage/prettify.css +0 -1
  224. package/coverage/prettify.js +0 -2
  225. package/coverage/sort-arrow-sprite.png +0 -0
  226. package/coverage/sorter.js +0 -196
  227. package/src/dataLayer/__tests__/payload-handler.spec.ts +0 -214
  228. package/src/dataLayer/payloadHandler.ts +0 -138
  229. /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
- id="cr-viewer"
13
- class="pa-0 ma-0 overflow-y-auto overflow-x-hidden"
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-toolbar dense height="48">
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-card class="">
92
- <v-list>
93
- <v-list-item
94
- v-if="showOption('OnLoadNewDicomSeries')"
95
- @click="executeOption('OnLoadNewDicomSeries')"
96
- >
97
- <template v-slot:prepend>
98
- <v-icon> upload </v-icon>
99
- </template>
100
- <v-list-item-title>Load New DICOM Series</v-list-item-title>
101
- </v-list-item>
102
- <v-list-item
103
- v-if="showOption('OnDownloadDicomSeries')"
104
- @click="executeOption('OnDownloadDicomSeries')"
105
- >
106
- <template v-slot:prepend>
107
- <v-icon> download </v-icon>
108
- </template>
109
- <v-list-item-title>Download DICOM Series</v-list-item-title>
110
- </v-list-item>
111
- <v-list-item
112
- v-if="showOption('OnLoadSavedSession')"
113
- @click="executeOption('OnLoadSavedSession')"
114
- >
115
- <template v-slot:prepend>
116
- <v-icon> sync </v-icon>
117
- </template>
118
- <v-list-item-title> Load Saved Session </v-list-item-title>
119
- </v-list-item>
120
- <v-list-item
121
- v-if="showOption('OnSendTo3rdParty')"
122
- @click="executeOption('OnSendTo3rdParty')"
123
- >
124
- <template v-slot:prepend>
125
- <v-icon> send </v-icon>
126
- </template>
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
- <v-list-item
140
- @click="
73
+ <v-list-item
74
+ @click="
141
75
  executeOption('OnClosePopup');
142
76
  alterValue(false);
143
77
  "
144
- >
145
- <template v-slot:prepend>
146
- <v-icon> close </v-icon>
147
- </template>
148
- <v-list-item-title>Close Viewer</v-list-item-title>
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
- <SliderSelector
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
- <SliderSelector
107
+ <ValueSelector
178
108
  v-model:value="scanState.Display.Contrast"
179
109
  label="Adjust Contrast"
180
110
  prepend="percent"
181
111
  />
182
- <SliderSelector
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
- <SliderSelector
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
- <SliderSelector
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
- <SliderSelector
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
- <SliderSelector
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="payloadHandler.layouts(LayoutActions.lo02)"
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="payloadHandler.layouts(LayoutActions.lo03)"
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 class="" variant="flat" color="red" @click="alterValue(false)"
255
- >Close Viewer
185
+ <v-btn variant="flat" color="red" @click="alterValue(false)">
186
+ Close Viewer
256
187
  </v-btn>
257
- </v-toolbar>
258
- <v-navigation-drawer
259
- v-model="drawer"
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
- >&nbsp;&nbsp;{{ 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
- :class="!(instanceLoaded && !scanLoading) && 'no-pointer-events'"
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
- @hover="payloadHandler.hoverOverCanvas($event)"
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.Anchor"
547
- :style="{
548
- ...generateDivStyleForLayout(layout),
549
- cursor:
550
- getCurrentActiveView(layout) === ScanView.Volume
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 v-slot:default="{ isHovering, props }">
558
- <div style="width: 100%; height: 100%" v-bind="props">
559
- <div
560
- class="buttons-in-view"
561
- v-show="
562
- isHovering || menu || menus.filter((x) => x).length > 0
563
- "
564
- >
565
- <div v-if="scanState.Layout.PositionData.length !== 1">
566
- <v-btn
567
- color="transparent"
568
- @click="fullscreenLayout(layout.DefaultView)"
569
- :icon="true"
570
- >
571
- <v-icon color="white">fullscreen</v-icon>
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
- Slice the 3D Volume
224
+ <span>This view has been flipped horizontally</span>
641
225
  </v-tooltip>
642
- </template>
643
- <v-card min-width="400" class="pb-2">
644
- <v-card-title>Slice into the 3D Volume</v-card-title>
645
- <DoubleSliderSelector
646
- v-model:value="tSlider"
647
- label="Transverse"
648
- v-bind="tMinMax"
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
- Rotate
684
- {{ getViewName(getCurrentActiveView(layout)) }} by
685
- an angle
235
+ <span>This view has been flipped vertically</span>
686
236
  </v-tooltip>
687
- </template>
688
- <v-card min-width="200" width="200" class="pb-2">
689
- <v-card-subtitle
690
- >Rotate
691
- {{ getViewName(getCurrentActiveView(layout)) }} by
692
- an angle</v-card-subtitle
693
- >
694
- <v-card-text class="py-0">
695
- <v-text-field
696
- hide-details
697
- outlined
698
- v-model="rotationDeg"
699
- type="number"
700
- suffix="deg"
701
- ></v-text-field>
702
- </v-card-text>
703
- <v-card-actions>
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
- v-if="
723
- getCurrentActiveView(layout) === ScanView.Transverse
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
- v-if="getCurrentActiveView(layout) === ScanView.Coronal"
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
- v-if="getCurrentActiveView(layout) === ScanView.Sagittal"
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
- </div>
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
- </div>
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
- cSlider,
828
- currentColourPreset,
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 { getIconForPreset } from "@/dataLayer/iconData";
847
- import { ViewerAsyncCallback, ViewerCallback } from "@/models/Callbacks";
848
- import DemoPatientInfoModal from "@/demo/patient/DemoPatientInfoModal.vue";
849
- import DemoLicenceInfoModal from "@/demo/licence/DemoLicenceInfoModal.vue";
850
- import DemoPatientShareToMobileModal from "@/demo/patient/DemoPatientShareToMobileModal.vue";
851
- import DemoPatientSendToPartyModal from "@/demo/patient/DemoPatientSendToPartyModal.vue";
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 web_gl = ref<typeof WebGL3DR | null>(null);
875
- const payloadHandler = new PayloadHandler(web_gl);
372
+ const viewer3cr = useViewer3cr();
373
+ const webGl3dr = ref<typeof WebGL3DR>();
374
+ const observer = ref<ResizeObserver>();
876
375
  const value = ref<boolean>(false);
877
- const menu = ref<boolean>(false);
878
- const menus = ref<Array<boolean>>([false, false, false, false]);
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 openPanels = ref<number>(0);
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
- function showOption(key: keyof LoadViewerOptions): boolean {
935
- return getOption(key) !== undefined;
936
- }
391
+ const tier = computed(() => {
392
+ const value = demoType.value;
393
+ return value.charAt(0).toUpperCase() + value.substring(1);
394
+ });
937
395
 
938
- function getOption(
939
- key: keyof LoadViewerOptions
940
- ): ViewerCallback | ViewerAsyncCallback | undefined {
941
- if (unref(isDemo)) {
942
- return getDemoOption(key);
943
- } else {
944
- return unref(props.options)[key];
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 = getOption(key);
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 payloadHandler.sliderHandler(slider as SlidersActions, value);
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 payloadHandler.scanMovementHandler(
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
- async function load() {
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 sendPayload(web_gl, "file_management", "fm_01", props.payload);
542
+ await viewer3cr.loadScan(props.payload);
1052
543
  }
1053
544
 
1054
- async function i_fileManagement(action: string, message: string) {
1055
- if (action === FileManagementActions.fm02) {
1056
- await setInitialScanStateFromPayload(action, message);
545
+ async function onMouseEnter(): Promise<void> {
546
+ await viewer3cr.hoverOverCanvas(true);
547
+ }
1057
548
 
1058
- await nextTick();
1059
- transactionStarted.value = false;
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 handleOnPayload(
1068
- interfaceSet: string | FrontEndInterfaces,
1069
- actionSet: string,
1070
- message: string
1071
- ) {
1072
- if (interfaceSet === FrontEndInterfaces.scan_state) {
1073
- setScanStateFromPayload(actionSet, message);
1074
- }
1075
- if (interfaceSet === FrontEndInterfaces.file_management) {
1076
- i_fileManagement(actionSet, message);
1077
- }
1078
- if (interfaceSet === FrontEndInterfaces.notifications) {
1079
- handleNotification(actionSet as NotificationsActions, message);
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
- handleOnPayload,
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
- .v-dialog:not(.v-dialog--fullscreen) {
1105
- max-height: unset !important;
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
- pointer-events: auto;
709
+ transition: border 0.5s;
1116
710
  }
711
+
1117
712
  .bordered-event-window:hover {
1118
- position: absolute;
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
- .btn-close-dense {
1126
- position: absolute;
1127
- right: 0;
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
- .slider-in-view {
1135
- position: absolute;
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
- .top-lhc {
1146
- position: absolute;
1147
- left: 16px;
1148
- top: 12px;
724
+
725
+ .introjs-overlay, .introjs-helperLayer {
726
+ pointer-events: none;
727
+ z-index: 2401;
1149
728
  }
1150
- .buttons-in-view > div {
1151
- display: inline-block;
729
+
730
+ .introjs-dontShowAgain {
731
+ display: none;
1152
732
  }
1153
- .motif-background {
1154
- 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;
1155
- background-size: cover !important;
733
+
734
+ .v-enter-active, .v-leave-active {
735
+ transition: opacity 0.5s ease;
1156
736
  }
1157
- .v-expansion-panel-text__wrapper {
1158
- padding: 8px 8px 16px;
737
+
738
+ .v-enter-from, .v-leave-to {
739
+ opacity: 0;
1159
740
  }
1160
741
  </style>