@3cr/viewer-browser 0.0.162 → 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 (188) hide show
  1. package/.circleci/config.yml +8 -6
  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 +22731 -17742
  6. package/dist/Viewer3CR.umd.js +44 -27
  7. package/index.html +5 -8
  8. package/index.ts +7 -2
  9. package/package.json +4 -2
  10. package/playground/index.html +6 -4
  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/src/dataLayer/__tests__/payload-handler.spec.ts +0 -214
  187. package/src/dataLayer/payloadHandler.ts +0 -138
  188. /package/src/dataLayer/{iconData.ts → getIconForPreset.ts} +0 -0
@@ -0,0 +1,51 @@
1
+ import { describe, expect, it, vi } from "vitest";
2
+ import { mountVuetify, openAllModals } from "~/helper";
3
+ import DemoPatientShareToMobileModal from "@/components/demo/patient/DemoPatientShareToMobileModal.vue";
4
+ import { PRICING_URL } from "../../options";
5
+
6
+ describe('DemoPatientShareToMobileModal tests', () => {
7
+ it('should mount', () => {
8
+ const props = { modal: true };
9
+ const wrapper = mountVuetify(DemoPatientShareToMobileModal, props);
10
+ expect(wrapper).toBeTruthy();
11
+ });
12
+
13
+ it('should close modal', async () => {
14
+ const props = { modal: true };
15
+ const wrapper = mountVuetify(DemoPatientShareToMobileModal, props);
16
+ const close = wrapper.findComponent('[data-testid="close-1"]');
17
+ await close.trigger('click');
18
+ expect(wrapper.emitted()['update:modal']).toBeTruthy();
19
+ });
20
+
21
+ it('should go to next step and open url', async () => {
22
+ vi.stubGlobal('open', vi.fn());
23
+ const spy = vi.spyOn(window, 'open');
24
+ const props = { modal: true };
25
+ const wrapper = mountVuetify(DemoPatientShareToMobileModal, props);
26
+ const confirm1 = wrapper.findComponent('[data-testid="confirm-1"]');
27
+ await confirm1.trigger('click');
28
+ expect(wrapper.emitted()['update:modal']).toBeTruthy();
29
+ const confirm2 = wrapper.findComponent('[data-testid="confirm-2"]');
30
+ await confirm2.trigger('click');
31
+ expect(spy).toHaveBeenCalledWith(PRICING_URL, '_self');
32
+ vi.restoreAllMocks();
33
+ });
34
+
35
+ it('should go to next step and close', async () => {
36
+ const props = { modal: true };
37
+ const wrapper = mountVuetify(DemoPatientShareToMobileModal, props);
38
+ const confirm1 = wrapper.findComponent('[data-testid="confirm-1"]');
39
+ await confirm1.trigger('click');
40
+ expect(wrapper.emitted()['update:modal']).toBeTruthy();
41
+ const close2 = wrapper.findComponent('[data-testid="close-2"]');
42
+ await close2.trigger('click');
43
+ expect(wrapper.text()).toBe('');
44
+ });
45
+
46
+ it('should open via input', async () => {
47
+ const props = { modal: false };
48
+ const wrapper = mountVuetify(DemoPatientShareToMobileModal, props);
49
+ await openAllModals(wrapper);
50
+ });
51
+ });
@@ -1,6 +1,6 @@
1
1
  <script setup lang="ts">
2
2
  export interface Props {
3
- text: string;
3
+ text?: string;
4
4
  }
5
5
 
6
6
  const props = withDefaults(defineProps<Props>(), {
@@ -0,0 +1,96 @@
1
+ <template>
2
+ <div ref="rail" class="action-rail d-flex">
3
+ <template v-for="(action, index) of visible">
4
+ <div class="visible-item">
5
+ <slot name="visible" :action="action" :index="index"></slot>
6
+ </div>
7
+ </template>
8
+ <v-menu v-if="showMenu" v-bind="menuProps" @update:model-value="emit('menuActive', $event)">
9
+ <template #activator="{ props }">
10
+ <v-btn v-bind="mergeProps(props, menuActivatorProps)" />
11
+ </template>
12
+ <v-list>
13
+ <template v-for="(action, index) of hidden" >
14
+ <div class="hidden-item">
15
+ <slot name="hidden" :action="action" :index="index"></slot>
16
+ </div>
17
+ </template>
18
+ </v-list>
19
+ </v-menu>
20
+ </div>
21
+ </template>
22
+
23
+ <script setup lang="ts">
24
+ import { computed, mergeProps, onBeforeUnmount, onMounted, ref } from 'vue';
25
+
26
+ type Action<T = Record<string, unknown>> = {
27
+ width: number
28
+ } & T;
29
+
30
+ interface Props {
31
+ actions?: Action[];
32
+ menuProps?: Record<string, unknown>;
33
+ menuActivatorProps?: Record<string, unknown>;
34
+ }
35
+
36
+ type Emits = {
37
+ menuActive: [boolean];
38
+ };
39
+
40
+ const props = withDefaults(defineProps<Props>(), {
41
+ actions: () => [],
42
+ menuProps: () => ({}),
43
+ menuActivatorProps: () => ({})
44
+ });
45
+
46
+ const emit = defineEmits<Emits>();
47
+
48
+ const rail = ref<HTMLDivElement>();
49
+ const splitIndex = ref<number>(0);
50
+
51
+ const observer = new ResizeObserver(entries => {
52
+ if (entries.length > 0) {
53
+ const railWidth = entries[0].contentRect.width;
54
+ calculateSplitIndex(railWidth);
55
+ }
56
+ });
57
+
58
+ const visible = computed(() => {
59
+ return props.actions.slice(0, splitIndex.value);
60
+ });
61
+
62
+ const hidden = computed(() => {
63
+ return props.actions.slice(splitIndex.value);
64
+ });
65
+
66
+ const showMenu = computed(() => {
67
+ return hidden.value.length > 0;
68
+ });
69
+
70
+ onMounted(() => {
71
+ observer.observe(rail.value!);
72
+ const rect = rail.value!.getBoundingClientRect();
73
+ calculateSplitIndex(rect.width);
74
+ });
75
+
76
+ onBeforeUnmount(() => {
77
+ observer.disconnect();
78
+ });
79
+
80
+ function calculateSplitIndex(railWidth: number): void {
81
+ const widths = props.actions.map(action => action.width);
82
+ let sum = 48;
83
+ for (const [idx, width] of widths.entries()) {
84
+ if (sum + width < railWidth) {
85
+ sum += width;
86
+ splitIndex.value = idx + 1;
87
+ }
88
+ }
89
+ }
90
+ </script>
91
+
92
+ <style lang="scss">
93
+ .action-rail {
94
+ min-width: 0;
95
+ }
96
+ </style>
@@ -0,0 +1,250 @@
1
+ <template>
2
+ <v-dialog v-model:model-value="modalState" max-width="800" theme="light">
3
+ <v-card class="prompt-box pa-2 py-4" rounded="xl">
4
+ <v-card-title class="text-body-1 d-flex align-center text-wrap mx-3 mb-4">
5
+ <span>{{ questionText }}</span>
6
+ </v-card-title>
7
+ <!-- <v-card-subtitle> From ChatGPT: </v-card-subtitle>-->
8
+ <div class="bordered-gradient-box" v-if="smartResponses">
9
+ <div class="prompt" style="font-size: 28px">
10
+ <!-- &#129668;-->
11
+ <v-img
12
+ src="@/assets/magic_wand.svg"
13
+ class="animated"
14
+ max-width="42"
15
+ />
16
+ &nbsp;&nbsp;&nbsp;<span
17
+ style="font-size: 18px"
18
+ v-html="smartResponses?.GptResponse"
19
+ >
20
+ </span>
21
+ </div>
22
+ </div>
23
+
24
+ <!-- <v-card-text v-html="smartResponses?.GptResponse"> </v-card-text>-->
25
+ <div class="prompt bordered-gradient-box" v-else>
26
+ <div class="loading"></div>
27
+ </div>
28
+ <v-card-text class="py-0">
29
+ <span
30
+ class="text-caption"
31
+ style="font-size: 10px !important; line-height: 10px !important"
32
+ >* 3Dicom's Annotiva AI is powered by AI Large Language Models (LLMs)
33
+ which generate general health information for improved patient
34
+ comprehension. <br />It is NOT for diagnostic usage.
35
+ </span>
36
+ </v-card-text>
37
+
38
+ <v-card-title
39
+ v-if="smartResponses"
40
+ class="text-body-1 d-flex align-center text-wrap mx-3 mt-6 mb-0"
41
+ >
42
+ Still Curious?
43
+ </v-card-title>
44
+ <v-card-text class="py-0">
45
+ <v-chip-group column v-model="item">
46
+ <v-chip
47
+ v-for="action in smartResponses?.FollowupQuestions"
48
+ style="
49
+ text-wrap: wrap;
50
+ word-wrap: break-word;
51
+ min-height: 36px;
52
+ background: linear-gradient(
53
+ to right,
54
+ rgba(38, 55, 245, 0.85),
55
+ rgba(145, 78, 245, 0.85)
56
+ );
57
+ "
58
+ class="text-wrap mb-1 px-4 v-chip--selected text-white"
59
+ @click="askFollowUpQuestion(action)"
60
+ >
61
+ {{ action.Question }}
62
+ </v-chip>
63
+ </v-chip-group>
64
+ </v-card-text>
65
+ </v-card>
66
+ </v-dialog>
67
+ </template>
68
+
69
+ <script setup lang="ts">
70
+ import { computed, ref, watch } from "vue";
71
+ import {
72
+ GptQuestion,
73
+ GptResponsePayload,
74
+ GptService,
75
+ } from "@/services/gpt/gpt.service";
76
+ const smartResponses = ref<GptResponsePayload | null>(null);
77
+ interface Props {
78
+ modal?: boolean;
79
+ questionText: string;
80
+ title: string;
81
+ questionKey: number;
82
+ }
83
+
84
+ type Emits = {
85
+ "update:modal": [value: boolean];
86
+ askFollowup: [value: GptQuestion];
87
+ };
88
+
89
+ const props = defineProps<Props>();
90
+
91
+ const emit = defineEmits<Emits>();
92
+
93
+ const item = ref();
94
+ const modalState = computed({
95
+ get(): boolean {
96
+ return props.modal;
97
+ },
98
+ set(value: boolean): void {
99
+ emit("update:modal", value);
100
+ },
101
+ });
102
+
103
+ watch(
104
+ modalState,
105
+ async (val: boolean) => {
106
+ if (val) {
107
+ smartResponses.value = null;
108
+ await getSmartResponses();
109
+ }
110
+ },
111
+ { immediate: true }
112
+ );
113
+
114
+ async function getSmartResponses(): Promise<void> {
115
+ smartResponses.value = await GptService.Instantiate()
116
+ .GenerateAnnotations(props.title, props.questionKey)
117
+ .then((data) => data.data);
118
+ item.value = smartResponses.value?.FollowupQuestions.findIndex(
119
+ (question) => question.ApiPreFilledRequestKey === props.questionKey
120
+ );
121
+ }
122
+
123
+ async function askFollowUpQuestion(question: GptQuestion): Promise<void> {
124
+ emit("askFollowup", question);
125
+ }
126
+ </script>
127
+ <style>
128
+ .loading {
129
+ font-size: 30px;
130
+ }
131
+ .loading:after {
132
+ overflow: hidden;
133
+ display: inline-block;
134
+ vertical-align: bottom;
135
+ -webkit-animation: ellipsis steps(4, end) 900ms infinite;
136
+ animation: ellipsis steps(4, end) 900ms infinite;
137
+ content: "\2026";
138
+ /* ascii code for the ellipsis character */
139
+ width: 0px;
140
+ }
141
+
142
+ @keyframes ellipsis {
143
+ to {
144
+ width: 40px;
145
+ }
146
+ }
147
+
148
+ @-webkit-keyframes ellipsis {
149
+ to {
150
+ width: 40px;
151
+ }
152
+ }
153
+
154
+ .prompt-box p {
155
+ font-weight: bold;
156
+ margin-bottom: 20px;
157
+ }
158
+
159
+ .prompt {
160
+ margin-left: 20px;
161
+ margin-right: 20px;
162
+ padding: 20px;
163
+ text-align: center;
164
+ color: black;
165
+ display: flex;
166
+ justify-content: space-between;
167
+ align-items: center;
168
+ margin-bottom: 15px;
169
+ z-index: 100;
170
+ position: relative;
171
+ }
172
+
173
+ .prompt::before {
174
+ content: "";
175
+ position: absolute;
176
+ inset: 0;
177
+ border-radius: 16px;
178
+ padding: 3px;
179
+ background: linear-gradient(to right, #6fb3f2, #4e51f5);
180
+
181
+ -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
182
+ -webkit-mask-composite: xor;
183
+ mask-composite: exclude;
184
+ }
185
+
186
+ .prompt span {
187
+ flex: 1;
188
+ text-align: left;
189
+ margin-right: 10px;
190
+ }
191
+
192
+ .icon {
193
+ background: #e0e0e0;
194
+ border-radius: 50%;
195
+ padding: 5px;
196
+ }
197
+
198
+ .record {
199
+ margin-top: 20px;
200
+ }
201
+
202
+ .record-button {
203
+ background: #4e51f5;
204
+ color: white;
205
+ padding: 10px 20px;
206
+ border: none;
207
+ border-radius: 20px;
208
+ font-size: 16px;
209
+ cursor: pointer;
210
+ }
211
+
212
+ .record-button:active {
213
+ background: #3b3edf;
214
+ }
215
+ .animated {
216
+ animation: pulse 2s infinite;
217
+ transition: all 2s ease-in-out;
218
+ }
219
+ @keyframes pulse {
220
+ 0% {
221
+ transform: scale(1);
222
+ }
223
+ 70% {
224
+ transform: scale(1.2);
225
+ }
226
+ }
227
+ .btn-grad {
228
+ background-image: linear-gradient(
229
+ to right,
230
+ #2b5876 0%,
231
+ #4e4376 51%,
232
+ #2b5876 100%
233
+ );
234
+ margin: 10px;
235
+ padding: 15px 45px;
236
+ text-align: center;
237
+ text-transform: uppercase;
238
+ transition: 0.5s;
239
+ background-size: 200% auto;
240
+ color: white;
241
+ box-shadow: 0 0 20px #eee;
242
+ border-radius: 10px;
243
+ display: block;
244
+ }
245
+ .btn-grad:hover {
246
+ background-position: right center; /* change the direction of the change here */
247
+ color: #fff;
248
+ text-decoration: none;
249
+ }
250
+ </style>
@@ -0,0 +1,104 @@
1
+ <template>
2
+ <v-dialog data-testid="dialog" v-model:model-value="modalState">
3
+ <v-card
4
+ class="pa-1 ma-auto position-relative motif-background"
5
+ width="600"
6
+ theme="dark"
7
+ >
8
+ <v-card-title class="text-center">Close Viewer?</v-card-title>
9
+ <v-card-text class="text-center mb-2">
10
+ Are you sure you want to close the Online Viewer?
11
+ </v-card-text>
12
+ <v-card-actions>
13
+ <v-btn
14
+ variant="flat"
15
+ color="secondary"
16
+ @click="modalState = false"
17
+ >
18
+ Cancel
19
+ </v-btn>
20
+ <v-spacer />
21
+ <v-btn
22
+ data-testid="close"
23
+ color="red"
24
+ variant="flat"
25
+ @click="closeModal"
26
+ v-if="showOption('OnSaveSession')"
27
+ >
28
+ Close without saving
29
+ </v-btn>
30
+ <v-btn
31
+ data-testid="save"
32
+ color="primary"
33
+ variant="flat"
34
+ @click="closeModalSave"
35
+ v-if="showOption('OnSaveSession')"
36
+ >
37
+ Save Session
38
+ </v-btn>
39
+ <v-btn
40
+ data-testid="close"
41
+ color="flat"
42
+ variant="tonal"
43
+ @click="closeModal" v-else>
44
+ Close Viewer
45
+ </v-btn>
46
+ </v-card-actions>
47
+ </v-card>
48
+ </v-dialog>
49
+ </template>
50
+
51
+ <script setup lang="ts">
52
+ import {computed} from "vue";
53
+ import {LoadViewerOptions} from "@/models/LoadViewerOptions";
54
+ import {dataOverlayState, scanState} from "@/dataLayer/scanState";
55
+
56
+ interface Props {
57
+ options: LoadViewerOptions,
58
+ modal?: boolean;
59
+ }
60
+
61
+ type Emits = {
62
+ 'update:modal': [boolean];
63
+ 'close': [void];
64
+ };
65
+
66
+ const props = withDefaults(defineProps<Props>(), {
67
+ modal: false
68
+ });
69
+
70
+ const emit = defineEmits<Emits>();
71
+
72
+ const modalState = computed({
73
+ get(): boolean {
74
+ return props.modal;
75
+ },
76
+ set(value: boolean): void {
77
+ emit('update:modal', value);
78
+ }
79
+ });
80
+
81
+ async function closeModal(): Promise<void> {
82
+ await executeOption('OnExitViewer');
83
+ modalState.value = false;
84
+ emit('close');
85
+ }
86
+
87
+ async function closeModalSave(): Promise<void> {
88
+ const scnState = scanState.value;
89
+ const dtaOverlayState = dataOverlayState.value.DataOverlay;
90
+ await executeOption('OnSaveSession', scnState, dtaOverlayState, null);
91
+ await closeModal();
92
+ }
93
+
94
+ function showOption(key: keyof LoadViewerOptions): boolean {
95
+ return props.options[key] !== undefined;
96
+ }
97
+
98
+ async function executeOption(key: keyof LoadViewerOptions, ...args: any[]): Promise<void> {
99
+ const functionToExecute = props.options[key];
100
+ if (functionToExecute !== undefined) {
101
+ await functionToExecute(...args);
102
+ }
103
+ }
104
+ </script>