@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
@@ -0,0 +1,123 @@
1
+ <template>
2
+ <ActionRail
3
+ :actions="viewActions"
4
+ :menu-props="menuProps"
5
+ :menu-activator-props="menuActivatorProps"
6
+ @menu-active="onActionModal($event, view)">
7
+ <template #visible="{ action }">
8
+ <Component
9
+ :is="action.component"
10
+ :view="view"
11
+ :element="element"
12
+ variant="button"
13
+ @update:modal="onActionModal($event, view)"
14
+ />
15
+ </template>
16
+ <template #hidden="{ action }">
17
+ <Component
18
+ :is="action.component"
19
+ :view="view"
20
+ :element="element"
21
+ variant="menu-item"
22
+ @update:modal="onActionModal($event, view)"
23
+ />
24
+ </template>
25
+ </ActionRail>
26
+ </template>
27
+
28
+ <script setup lang="ts">
29
+ import { computed, shallowRef } from "vue";
30
+ import { ScanView } from "@3cr/types-ts";
31
+ import FlipVerticalAction from "@/components/modal/actions/FlipVerticalAction.vue";
32
+ import FlipHorizontalAction from "@/components/modal/actions/FlipHorizontalAction.vue";
33
+ import FullscreenAction from "@/components/modal/actions/FullscreenAction.vue";
34
+ import PanAction from "@/components/modal/actions/PanAction.vue";
35
+ import ResetViewAction from "@/components/modal/actions/ResetViewAction.vue";
36
+ import Rotate2dAction from "@/components/modal/actions/Rotate2dAction.vue";
37
+ import Slice3dAction from "@/components/modal/actions/Slice3dAction.vue";
38
+ import ZoomAction from "@/components/modal/actions/ZoomAction.vue";
39
+ import { useViewer3cr } from "@/dataLayer/useViewer3cr";
40
+
41
+ interface Props {
42
+ view: ScanView;
43
+ element: any;
44
+ }
45
+
46
+ type Emits = {
47
+ modal: [boolean, number];
48
+ };
49
+
50
+ type ActionItem = {
51
+ component: any;
52
+ width: number;
53
+ condition: () => boolean;
54
+ }
55
+
56
+ const menuProps = {
57
+ closeOnContentClick: false
58
+ };
59
+
60
+ const menuActivatorProps = {
61
+ icon: 'more_vert',
62
+ variant: 'text',
63
+ color: 'white'
64
+ };
65
+
66
+ const props = defineProps<Props>();
67
+
68
+ const emit = defineEmits<Emits>();
69
+
70
+ const viewer3cr = useViewer3cr();
71
+
72
+ const viewActions = computed(() => {
73
+ return actions.value.filter(action => action.condition());
74
+ });
75
+
76
+ const actions = shallowRef<ActionItem[]>([
77
+ {
78
+ component: FullscreenAction,
79
+ width: 48,
80
+ condition: () => true,
81
+ },
82
+ {
83
+ component: ResetViewAction,
84
+ width: 48,
85
+ condition: () => true,
86
+ },
87
+ {
88
+ component: PanAction,
89
+ width: 48,
90
+ condition: () => true,
91
+ },
92
+ {
93
+ component: ZoomAction,
94
+ width: 48,
95
+ condition: () => true,
96
+ },
97
+ {
98
+ component: Slice3dAction,
99
+ width: 48,
100
+ condition: () => props.view === ScanView.Volume,
101
+ },
102
+ {
103
+ component: Rotate2dAction,
104
+ width: 48,
105
+ condition: () => props.view !== ScanView.Volume,
106
+ },
107
+ {
108
+ component: FlipHorizontalAction,
109
+ width: 48,
110
+ condition: () => props.view !== ScanView.Volume,
111
+ },
112
+ {
113
+ component: FlipVerticalAction,
114
+ width: 48,
115
+ condition: () => props.view !== ScanView.Volume,
116
+ }
117
+ ]);
118
+
119
+ async function onActionModal(value: boolean, view: ScanView): Promise<void> {
120
+ await viewer3cr.hoverOverCanvas(!value); // disable unity inputs when interacting with modals
121
+ emit('modal', value, view);
122
+ }
123
+ </script>
@@ -0,0 +1,115 @@
1
+ <template>
2
+ <v-menu
3
+ v-model="menuState"
4
+ min-width="0"
5
+ max-width="300"
6
+ persistent
7
+ :no-click-animation="true"
8
+ :target="target"
9
+ :offset="[15, 0]"
10
+ >
11
+ <v-card>
12
+ <v-card-title class="text-body-1 d-flex align-center">
13
+ <span>{{ title }}</span>
14
+ </v-card-title>
15
+ <v-card-text>
16
+ <v-row no-gutters>
17
+ <v-col cols="4">
18
+ <b style="font-size: 12px">Description</b>
19
+ </v-col>
20
+ <v-col>
21
+ <span style="font-size: 12px">{{ description }}</span>
22
+ </v-col>
23
+ </v-row>
24
+ <v-row no-gutters v-if="smartResponses">
25
+ <v-col cols="4">
26
+ <b style="font-size: 12px">Smart Description</b>
27
+ </v-col>
28
+ <v-col>
29
+ <span style="font-size: 12px">{{
30
+ smartResponses.GptResponse
31
+ }}</span>
32
+ </v-col>
33
+ </v-row>
34
+ <v-row no-gutters>
35
+ <v-col cols="4">
36
+ <b style="font-size: 12px">Resources</b>
37
+ </v-col>
38
+ <v-col>
39
+ <template v-for="action in actions">
40
+ <a style="font-size: 12px" :href="action.ActionData.Url">{{
41
+ action.ActionData.Description
42
+ }}</a>
43
+ <br />
44
+ </template>
45
+ </v-col>
46
+ </v-row>
47
+ </v-card-text>
48
+ </v-card>
49
+ </v-menu>
50
+ </template>
51
+
52
+ <script setup lang="ts">
53
+ import { computed, ref, watch } from 'vue';
54
+ import { GptResponsePayload, GptService } from '@/services/gpt/gpt.service';
55
+ import { DataOverlay, DataOverlayEvent, InteractionType } from '@3cr/types-ts';
56
+ import { dataOverlayEvent, scanState } from '@/dataLayer/scanState';
57
+ import { useEventListener } from "@/components/modal/composables/useEventListener";
58
+
59
+ useEventListener(document, 'click', onClick);
60
+ const annotation = ref<DataOverlay | null>(null);
61
+ const target = ref<[number, number]>([0, 0]);
62
+ const smartResponses = ref<GptResponsePayload | null>(null);
63
+
64
+ const menuState = computed({
65
+ get(): boolean {
66
+ return !!annotation.value;
67
+ },
68
+ set(value: boolean): void {
69
+ if (!value) {
70
+ annotation.value = null;
71
+ }
72
+ },
73
+ });
74
+
75
+ const title = computed(() => {
76
+ return annotation.value?.Title ?? '';
77
+ });
78
+
79
+ const description = computed(() => {
80
+ return annotation.value?.Description ?? '';
81
+ });
82
+
83
+ const actions = computed(() => {
84
+ return annotation.value?.CallToAction?.Actions || [];
85
+ });
86
+
87
+ watch([
88
+ () => scanState.value.Orientations.Transverse.Slice,
89
+ () => scanState.value.Orientations.Sagittal.Slice,
90
+ () => scanState.value.Orientations.Coronal.Slice,
91
+ ], () => {
92
+ menuState.value = false;
93
+ });
94
+
95
+ watch(dataOverlayEvent, async (event: DataOverlayEvent | null) => {
96
+ if (event && event.Interaction === InteractionType.PointerUp) {
97
+ annotation.value = event.Annotation;
98
+ await getSmartResponses();
99
+ }
100
+ });
101
+
102
+ async function getSmartResponses(): Promise<void> {
103
+ const response = await GptService.Instantiate().GenerateAnnotations(title.value);
104
+ smartResponses.value = response.data;
105
+ }
106
+
107
+ function onClick(event: Event): void {
108
+ const e = event as MouseEvent;
109
+ if (dataOverlayEvent.value?.Interaction === InteractionType.PointerDown) {
110
+ target.value = [e.x, e.y];
111
+ } else {
112
+ menuState.value = false;
113
+ }
114
+ }
115
+ </script>
@@ -0,0 +1,283 @@
1
+ <template>
2
+ <v-treeview
3
+ class="annotation-treeview"
4
+ :items="data"
5
+ density="compact"
6
+ item-value="id"
7
+ open-strategy="single"
8
+ @update:opened="focusSlices($event)"
9
+ >
10
+ <template #prepend="{ item }">
11
+ <div v-if="hasChildren(item)">
12
+ <v-btn
13
+ size="xx-small"
14
+ :icon="getVisibilityIcon(item)"
15
+ variant="text"
16
+ @click.stop="toggleVisibility(item)"
17
+ />
18
+ </div>
19
+ </template>
20
+ <template #title="{ item }">
21
+ <div class="d-flex align-center ml-1">
22
+ <div :style="getMaskStyles(item)">
23
+ <div :style="getBackgroundStyles(item)"></div>
24
+ </div>
25
+ <b class="mx-2">{{ item.id }}</b>
26
+ <span class="text-subtitle-2">{{ item.title }}</span>
27
+ </div>
28
+ </template>
29
+ <template #item="{ props }">
30
+ <v-row no-gutters class="px-6 py-2">
31
+ <v-col cols="4">
32
+ <b>{{ findNode(props.value)?.title }}</b>
33
+ </v-col>
34
+ <v-col>
35
+ <span v-html="findNode(props.value)?.description"></span>
36
+ <template v-for="action in findNode(props.value)?.actions">
37
+ <v-chip
38
+ v-if="action.isBtn"
39
+ size="small"
40
+ label
41
+ variant="tonal"
42
+ class="action text-wrap mb-2"
43
+ @click="selectAction(action)"
44
+ >
45
+ {{ action.description }}
46
+ </v-chip>
47
+ <a v-else :href="action.url">{{ action.description }}</a>
48
+ <br />
49
+ </template>
50
+ </v-col>
51
+ </v-row>
52
+ </template>
53
+ </v-treeview>
54
+ <AskAI
55
+ v-model:modal="m_askAi"
56
+ :title="title"
57
+ :question-key="questionKey"
58
+ :question-text="questionText"
59
+ @askFollowup="askFollowUpQuestion"
60
+ >
61
+ </AskAI>
62
+ <span
63
+ class="text-caption"
64
+ style="font-size: 8px !important; line-height: 8px !important"
65
+ >* Powered by ChatGPT, Smart Items are NOT for diagnostic use. Please
66
+ consult a medical professional for a diagnosis or treatment plan
67
+ </span>
68
+ </template>
69
+
70
+ <script setup lang="ts">
71
+ import { dataOverlayState, initialScanState } from "@/dataLayer/scanState";
72
+ import { nextTick, onMounted, ref } from "vue";
73
+ import { ColourData, SlidersActions, Vector3Data } from "@3cr/types-ts";
74
+ import { GptQuestion, GptService } from "@/services/gpt/gpt.service";
75
+ import {useViewer3cr} from "@/dataLayer/useViewer3cr";
76
+
77
+ interface Annotation {
78
+ id: string;
79
+ index?: number | undefined;
80
+ title: string;
81
+ description?: string;
82
+ icon?: string;
83
+ position?: Vector3Data;
84
+ colour?: ColourData;
85
+ children?: Annotation[];
86
+ actions?: AnnotationAction[];
87
+ }
88
+
89
+ interface AnnotationAction {
90
+ description: string;
91
+ url: string;
92
+ title?: string | undefined;
93
+ isBtn: boolean;
94
+ }
95
+
96
+ const viewer3cr = useViewer3cr();
97
+ const data = ref<Annotation[]>([]);
98
+ const m_askAi = ref<boolean>(false);
99
+ const questionKey = ref<number>(0);
100
+ const questionText = ref<string>("");
101
+ const title = ref<string>("");
102
+
103
+ onMounted(async () => {
104
+ data.value = await generateTreeViewData();
105
+ });
106
+
107
+ async function selectAction(action: AnnotationAction): Promise<void> {
108
+ questionKey.value = Number(action.url);
109
+ questionText.value = action.description || "";
110
+ title.value = action.title || "";
111
+ m_askAi.value = true;
112
+ }
113
+
114
+ async function askFollowUpQuestion(question: GptQuestion): Promise<void> {
115
+ m_askAi.value = false;
116
+ await nextTick();
117
+ questionKey.value = question.ApiPreFilledRequestKey;
118
+ questionText.value = question.Question;
119
+ m_askAi.value = true;
120
+ }
121
+
122
+ async function toggleVisibility(item: any): Promise<void> {
123
+ const visibility = getVisibility(item);
124
+ await viewer3cr.toggle2dAnnotation(item.id, !visibility);
125
+ await viewer3cr.toggle3dAnnotation(item.id, !visibility);
126
+ }
127
+
128
+ function getVisibilityIcon(item: Annotation): string {
129
+ return getVisibility(item) ? "visibility" : "visibility_off";
130
+ }
131
+
132
+ function getVisibility(item: Annotation): boolean {
133
+ const overlay = dataOverlayState.value.DataOverlay.DataOverlay.find(
134
+ (overlay) => item.id === overlay.Id
135
+ );
136
+ return overlay ? overlay.Data.Visibility2d : false;
137
+ }
138
+
139
+ function hasChildren(item: Annotation): boolean {
140
+ return "children" in item;
141
+ }
142
+
143
+ function findNode(
144
+ id: string,
145
+ items: Annotation | Annotation[] = data.value
146
+ ): Annotation | null {
147
+ if (Array.isArray(items)) {
148
+ const values = items
149
+ .map((item) => findNode(id, item))
150
+ .filter((item) => item);
151
+ return values.length > 0 ? values[0] : null;
152
+ } else {
153
+ return items.id === id
154
+ ? items
155
+ : hasChildren(items)
156
+ ? findNode(id, items.children)
157
+ : null;
158
+ }
159
+ }
160
+
161
+ async function generateTreeViewData(): Promise<Annotation[]> {
162
+ return await Promise.all(
163
+ dataOverlayState.value.DataOverlay.DataOverlay.map(async (overlay) => {
164
+ const gpt = await GptService.Instantiate()
165
+ .GenerateAnnotations(overlay.Data.Title)
166
+ .then((data) => data.data)
167
+ .catch((err) => ({
168
+ GptResponse: "failed to execute",
169
+ FollowupQuestions: [],
170
+ }));
171
+ return {
172
+ id: overlay.Id,
173
+ title: overlay.Data.Title,
174
+ icon: overlay.Data.Icon2d,
175
+ colour: overlay.Data.Colour2d,
176
+ position: overlay.Data.Position,
177
+ children: [
178
+ overlay.Data.Description
179
+ ? {
180
+ id: `${overlay.Id}-0`,
181
+ title: "Description",
182
+ description: overlay.Data.Description,
183
+ }
184
+ : {
185
+ id: `${overlay.Id}-2`,
186
+ title: "Smart Description",
187
+ description: gpt.GptResponse,
188
+ },
189
+ {
190
+ id: `${overlay.Id}-1`,
191
+ title: "Resources",
192
+ actions: overlay.Data.CallToAction?.Actions.map((action) => ({
193
+ description: action.ActionData.Description,
194
+ url: action.ActionData.Url,
195
+ isBtn: false,
196
+ })),
197
+ },
198
+ {
199
+ id: `${overlay.Id}-3`,
200
+ title: "Annotiva AI",
201
+ actions: gpt.FollowupQuestions.map((question) => ({
202
+ description: question.Question,
203
+ url: question.ApiPreFilledRequestKey.toString(),
204
+ title: overlay.Data.Title,
205
+ isBtn: true,
206
+ })),
207
+ },
208
+ ],
209
+ };
210
+ })
211
+ );
212
+ }
213
+
214
+ function getMaskStyles(item: Annotation): Record<string, any> {
215
+ const src = `data:image/png;base64, ${item.icon}`;
216
+ return {
217
+ "mask-size": "12px 12px",
218
+ "mask-image": `url('${src}')`,
219
+ "min-width": "12px",
220
+ "min-height": "12px",
221
+ };
222
+ }
223
+
224
+ function getBackgroundStyles(item: Annotation): Record<string, any> {
225
+ const { R, G, B, A } = item.colour!;
226
+ const colour = `rgb(${R * 255}, ${G * 255}, ${B * 255}, ${A * 255})`;
227
+ return {
228
+ "background-color": colour,
229
+ "min-width": "12px",
230
+ "min-height": "12px",
231
+ };
232
+ }
233
+
234
+ async function focusSlices(ids: unknown[]): Promise<void> {
235
+ const id = ids.length > 0 ? ids[0] : null;
236
+ if (!id) { return; }
237
+
238
+ const item = findNode(id as string);
239
+ if (!item) { return; }
240
+
241
+ const x = item.position?.X ?? 0;
242
+ const y = item.position?.Y ?? 0;
243
+ const z = item.position?.Z ?? 0;
244
+
245
+ const xSpace = initialScanState.value.XSpacing || 1;
246
+ const ySpace = initialScanState.value.YSpacing || 1;
247
+ const zSpace = initialScanState.value.ZSpacing || 1;
248
+
249
+ const sagittal = Math.round(x / xSpace);
250
+ const transverse = Math.round(y / ySpace);
251
+ const coronal = Math.round(z / zSpace);
252
+
253
+ await viewer3cr.sliderHandler(SlidersActions.sl12, sagittal);
254
+ await viewer3cr.sliderHandler(SlidersActions.sl09, coronal);
255
+ await viewer3cr.sliderHandler(SlidersActions.sl15, transverse);
256
+ }
257
+ </script>
258
+
259
+ <style lang="scss">
260
+ .annotation-treeview {
261
+ font-size: 12px;
262
+ padding-top: 0;
263
+
264
+ .v-list-group {
265
+ .v-list-item {
266
+ padding: 0 2px;
267
+ }
268
+ }
269
+
270
+ .v-list-group ~ .v-list-group {
271
+ border-top: thin solid rgb(100%, 100%, 100%, var(--v-border-opacity));
272
+ }
273
+
274
+ .v-chip.action {
275
+ text-wrap: wrap;
276
+ word-wrap: break-word;
277
+ min-height: 42px;
278
+ line-height: 14px !important;
279
+ height: unset;
280
+ padding: 4px 8px;
281
+ }
282
+ }
283
+ </style>
@@ -0,0 +1,102 @@
1
+ <template>
2
+ <v-row class="flex-column" no-gutters>
3
+ <v-row no-gutters>
4
+ <v-col>
5
+ <DoubleSliderSelector
6
+ id="skin-to-bone"
7
+ v-model:value="windowSlider"
8
+ v-bind="huMinMax"
9
+ label="Skin to Bone"
10
+ />
11
+ </v-col>
12
+ </v-row>
13
+ <v-row no-gutters>
14
+ <v-col>
15
+ <DoubleSliderSelector
16
+ v-model:value="thresholdSlider"
17
+ v-bind="huMinMax"
18
+ label="Fine Adjustment"
19
+ />
20
+ </v-col>
21
+ </v-row>
22
+ <v-row no-gutters>
23
+ <v-col>
24
+ <v-select
25
+ class="pt-4"
26
+ data-testid="greyscale"
27
+ :model-value="currentGreyscalePreset"
28
+ :items="initialScanState.GreyscalePresets"
29
+ label="Greyscale Preset"
30
+ item-text="Name"
31
+ density="compact"
32
+ variant="outlined"
33
+ persistent-placeholder
34
+ hide-details
35
+ return-object
36
+ placeholder="None"
37
+ @update:model-value="onGreyscalePresetChange"
38
+ >
39
+ <template #item="{ props, item }">
40
+ <v-list-item
41
+ v-bind="props"
42
+ :title="item.raw.Name"
43
+ lines="three"
44
+ >
45
+ <template v-slot:subtitle>
46
+ Skin Density:
47
+ <span class="text-mono">{{ item.raw.Lower }}</span>
48
+ <v-spacer />
49
+ Bone Density:
50
+ <span class="text-mono">{{ item.raw.Upper }}</span>
51
+ </template>
52
+ </v-list-item>
53
+ </template>
54
+ <template v-slot:selection="{ item }">
55
+ <span>{{ item.raw.Name }}</span>
56
+ </template>
57
+ </v-select>
58
+ </v-col>
59
+ </v-row>
60
+ <v-row no-gutters>
61
+ <v-col>
62
+ <v-select
63
+ class="pt-4"
64
+ data-testid="colour"
65
+ :model-value="currentColourPreset"
66
+ :items="initialScanState.ColourPresets"
67
+ label="Colour Preset"
68
+ variant="outlined"
69
+ density="compact"
70
+ item-title="Name"
71
+ hide-details
72
+ placeholder="Select a Colour Preset"
73
+ return-object
74
+ @update:model-value="onColourPresetChange"
75
+ ></v-select>
76
+ </v-col>
77
+ </v-row>
78
+ </v-row>
79
+ </template>
80
+
81
+ <script setup lang="ts">
82
+ import {
83
+ currentColourPreset,
84
+ currentGreyscalePreset,
85
+ huMinMax,
86
+ initialScanState,
87
+ thresholdSlider,
88
+ windowSlider
89
+ } from "@/dataLayer/scanState";
90
+ import { ColourPresetData, GreyscalePresetData, PresetsActions } from "@3cr/types-ts";
91
+ import { useViewer3cr } from "@/dataLayer/useViewer3cr";
92
+
93
+ const viewer3cr = useViewer3cr();
94
+
95
+ async function onGreyscalePresetChange(preset: GreyscalePresetData): Promise<void> {
96
+ await viewer3cr.setPreset(PresetsActions.pr01, preset);
97
+ }
98
+
99
+ async function onColourPresetChange(preset: ColourPresetData): Promise<void> {
100
+ await viewer3cr.setPreset(PresetsActions.pr02, preset);
101
+ }
102
+ </script>
@@ -0,0 +1,90 @@
1
+ <template>
2
+ <v-navigation-drawer
3
+ class="motif-background"
4
+ :model-value="true"
5
+ :rail="!drawer"
6
+ rail-width="72"
7
+ width="350"
8
+ permanent
9
+ touchless
10
+ >
11
+ <template #prepend>
12
+ <ViewerNavigationDrawerHeader :drawer="drawer" />
13
+ </template>
14
+ <template #default>
15
+ <ViewerNavigationDrawerContent v-model:drawer="drawer" :options="options" @update:expanded="emit('update:expanded', $event)" />
16
+ </template>
17
+ <template #append>
18
+ <v-divider />
19
+ <ViewerNavigationDrawerFooter :drawer="drawer" :options="options" />
20
+ </template>
21
+ </v-navigation-drawer>
22
+ <v-btn
23
+ data-testid="toggle"
24
+ height="84"
25
+ color="grey"
26
+ variant="flat"
27
+ width="20"
28
+ min-width="20"
29
+ class="toggle"
30
+ :style="!drawer ? 'left: 72px' : 'left: 350px'"
31
+ @click="drawer = !drawer"
32
+ >
33
+ <v-icon color="black">
34
+ {{ !drawer ? "chevron_right" : "chevron_left" }}
35
+ </v-icon>
36
+ </v-btn>
37
+ </template>
38
+
39
+ <script setup lang="ts">
40
+ import { computed, ref, watch } from 'vue';
41
+ import { LoadViewerOptions } from "@/models/LoadViewerOptions";
42
+
43
+ interface Props {
44
+ options: LoadViewerOptions;
45
+ drawer?: boolean;
46
+ }
47
+
48
+ type Emits = {
49
+ 'update:drawer': [boolean];
50
+ 'update:expanded': [number | undefined];
51
+ };
52
+
53
+ const props = withDefaults(defineProps<Props>(), {
54
+ drawer: false
55
+ });
56
+
57
+ const emit = defineEmits<Emits>();
58
+
59
+ const _drawer = ref<boolean>(props.drawer);
60
+
61
+ const drawer = computed({
62
+ get(): boolean {
63
+ return _drawer.value;
64
+ },
65
+ set(value: boolean): void {
66
+ _drawer.value = value;
67
+ emit('update:drawer', value);
68
+ }
69
+ });
70
+
71
+ watch(() => props.drawer, (value: boolean) => {
72
+ drawer.value = value;
73
+ });
74
+
75
+ defineExpose({ drawer });
76
+ </script>
77
+
78
+ <style scoped>
79
+ .toggle {
80
+ border-bottom-left-radius: 0 !important;
81
+ border-top-left-radius: 0 !important;
82
+ padding: 0;
83
+ position: absolute;
84
+ top: 50%;
85
+ opacity: 0.7;
86
+ z-index: 1;
87
+ transform: translateY(-50%);
88
+ transition: 0.25s left;
89
+ }
90
+ </style>