@3cr/viewer-browser 0.0.61 → 0.0.86

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 (59) hide show
  1. package/components.d.ts +0 -1
  2. package/coverage/3cr-viewer-browser/index.html +5 -5
  3. package/coverage/3cr-viewer-browser/index.ts.html +14 -26
  4. package/coverage/3cr-viewer-browser/src/App.vue.html +51 -48
  5. package/coverage/3cr-viewer-browser/src/components/WebGL3DR.vue.html +1 -1
  6. package/coverage/3cr-viewer-browser/src/components/icons/index.html +1 -1
  7. package/coverage/3cr-viewer-browser/src/components/icons/liver.vue.html +1 -1
  8. package/coverage/3cr-viewer-browser/src/components/index.html +1 -1
  9. package/coverage/3cr-viewer-browser/src/components/loading/LoadingSpinner.vue.html +82 -16
  10. package/coverage/3cr-viewer-browser/src/components/loading/index.html +5 -5
  11. package/coverage/3cr-viewer-browser/src/components/modal/MftpWebGL3DRModal.vue.html +238 -1123
  12. package/coverage/3cr-viewer-browser/src/components/modal/index.html +19 -19
  13. package/coverage/3cr-viewer-browser/src/components/selectors/ValueSelector.vue.html +47 -20
  14. package/coverage/3cr-viewer-browser/src/components/selectors/index.html +7 -7
  15. package/coverage/3cr-viewer-browser/src/components/sliders/DoubleSliderSelector.vue.html +62 -20
  16. package/coverage/3cr-viewer-browser/src/components/sliders/VerticalSliderSelector.vue.html +16 -7
  17. package/coverage/3cr-viewer-browser/src/components/sliders/index.html +10 -10
  18. package/coverage/3cr-viewer-browser/src/dataLayer/iconData.ts.html +118 -0
  19. package/coverage/3cr-viewer-browser/src/dataLayer/index.html +146 -0
  20. package/coverage/3cr-viewer-browser/src/dataLayer/payloadHandler.ts.html +352 -0
  21. package/coverage/3cr-viewer-browser/src/dataLayer/scanState.ts.html +628 -0
  22. package/coverage/3cr-viewer-browser/src/helpers/index.html +1 -1
  23. package/coverage/3cr-viewer-browser/src/helpers/layoutOverlayStyle.ts.html +1 -1
  24. package/coverage/3cr-viewer-browser/src/helpers/modelHelper.ts.html +105 -105
  25. package/coverage/3cr-viewer-browser/src/helpers/utils.ts.html +3 -3
  26. package/coverage/3cr-viewer-browser/src/index.html +5 -5
  27. package/coverage/3cr-viewer-browser/src/main.ts.html +1 -1
  28. package/coverage/3cr-viewer-browser/src/models/LoadViewerOptions.ts.html +166 -0
  29. package/coverage/3cr-viewer-browser/src/models/index.html +116 -0
  30. package/coverage/3cr-viewer-browser/src/notifications/index.html +116 -0
  31. package/coverage/3cr-viewer-browser/src/notifications/notification.ts.html +229 -0
  32. package/coverage/3cr-viewer-browser/src/plugins/index.html +5 -5
  33. package/coverage/3cr-viewer-browser/src/plugins/index.ts.html +14 -8
  34. package/coverage/3cr-viewer-browser/src/plugins/vuetify.ts.html +1 -1
  35. package/coverage/index.html +78 -33
  36. package/dist/Viewer3CR.js +12 -12
  37. package/dist/Viewer3CR.mjs +10004 -9916
  38. package/dist/Viewer3CR.umd.js +12 -12
  39. package/index.ts +7 -42
  40. package/package.json +2 -1
  41. package/src/App.vue +6 -2
  42. package/src/components/modal/MftpWebGL3DRModal.vue +137 -452
  43. package/src/components/modal/__tests__/mftp-webgl-3dr-modal.spec.ts +114 -83
  44. package/src/components/selectors/ValueSelector.vue +4 -0
  45. package/src/components/selectors/__tests__/value-selector.spec.ts +18 -0
  46. package/src/components/sliders/DoubleSliderSelector.vue +10 -4
  47. package/src/components/sliders/__tests__/double-slider-selector.spec.ts +32 -0
  48. package/src/dataLayer/__tests__/payload-handler.spec.ts +98 -0
  49. package/src/dataLayer/iconData.ts +11 -0
  50. package/src/dataLayer/payloadHandler.ts +89 -0
  51. package/src/dataLayer/scanState.ts +181 -0
  52. package/src/demo/DemoPatientModal.vue +59 -0
  53. package/src/demo/options.ts +93 -0
  54. package/src/models/Callbacks.ts +2 -0
  55. package/src/models/LoadViewerOptions.ts +27 -0
  56. package/src/models/LoadViewerPayload.ts +8 -0
  57. package/src/notifications/notification.ts +48 -0
  58. package/vitest.config.mts +1 -0
  59. /package/src/{components/modal → demo}/DemoModal.vue +0 -0
@@ -0,0 +1,181 @@
1
+ import { computed, nextTick, ref, unref, watch, WatchSource } from "vue";
2
+ import {
3
+ AnchorPoint,
4
+ ColourPresetData,
5
+ CurrentScanState,
6
+ InitialScanState,
7
+ ScanMovementActions,
8
+ } from "@3cr/types-ts";
9
+ import {
10
+ inflateInitialScanState,
11
+ inflateScanState,
12
+ } from "@/helpers/modelHelper";
13
+ import { PayloadHandler } from "@/dataLayer/payloadHandler";
14
+
15
+ export const currentColourPreset = ref<ColourPresetData | undefined>(undefined);
16
+
17
+ export const transactionStarted = ref<boolean>(false);
18
+ export const scanStateIncoming = ref<string>("");
19
+ export const scanState = ref<CurrentScanState>(inflateScanState());
20
+ export const initialScanState = ref<InitialScanState>(
21
+ inflateInitialScanState()
22
+ );
23
+
24
+ export const huMinMax = ref({
25
+ min: -999999,
26
+ max: 999999,
27
+ });
28
+
29
+ export const tMinMax = ref({
30
+ min: 0,
31
+ max: 999999,
32
+ });
33
+ export const sMinMax = ref({
34
+ min: 0,
35
+ max: 999999,
36
+ });
37
+ export const cMinMax = ref({
38
+ min: 0,
39
+ max: 999999,
40
+ });
41
+
42
+ export const windowSlider = computed({
43
+ get() {
44
+ return [
45
+ unref(scanState).Display.WindowLower,
46
+ unref(scanState).Display.WindowUpper,
47
+ ];
48
+ },
49
+ set(value: Array<number>) {
50
+ scanState.value.Display.WindowLower = value[0];
51
+ scanState.value.Display.WindowUpper = value[1];
52
+ },
53
+ });
54
+ export const thresholdSlider = computed({
55
+ get() {
56
+ return [
57
+ Math.trunc(unref(scanState).Display.ThresholdLower),
58
+ Math.trunc(unref(scanState).Display.ThresholdUpper),
59
+ ];
60
+ },
61
+ set(value: Array<number>) {
62
+ scanState.value.Display.ThresholdLower = value[0];
63
+ scanState.value.Display.ThresholdUpper = value[1];
64
+ },
65
+ });
66
+ export const tSlider = computed({
67
+ get() {
68
+ return [
69
+ Math.trunc(unref(scanState).Slice.TransverseLower),
70
+ Math.trunc(unref(scanState).Slice.TransverseUpper),
71
+ ];
72
+ },
73
+ set(value: Array<number>) {
74
+ scanState.value.Slice.TransverseLower = value[0];
75
+ scanState.value.Slice.TransverseLower = value[1];
76
+ },
77
+ });
78
+ export const sSlider = computed({
79
+ get() {
80
+ return [
81
+ Math.trunc(unref(scanState).Slice.SagittalLower),
82
+ Math.trunc(unref(scanState).Slice.SagittalUpper),
83
+ ];
84
+ },
85
+ set(value: Array<number>) {
86
+ scanState.value.Slice.SagittalLower = value[0];
87
+ scanState.value.Slice.SagittalUpper = value[1];
88
+ },
89
+ });
90
+
91
+ export const cSlider = computed({
92
+ get() {
93
+ return [
94
+ Math.trunc(unref(scanState).Slice.CoronalLower),
95
+ Math.trunc(unref(scanState).Slice.CoronalUpper),
96
+ ];
97
+ },
98
+ set(value: Array<number>) {
99
+ scanState.value.Slice.CoronalLower = value[0];
100
+ scanState.value.Slice.CoronalUpper = value[1];
101
+ },
102
+ });
103
+
104
+ export const isLayout2x2 = computed(() => {
105
+ return (
106
+ unref(scanState).Layout.PositionData.length > 1 &&
107
+ unref(scanState).Layout.PositionData[0].Anchor === AnchorPoint.TOP_LEFT &&
108
+ unref(scanState).Layout.PositionData[1].Anchor === AnchorPoint.TOP_RIGHT &&
109
+ unref(scanState).Layout.PositionData[2].Anchor ===
110
+ AnchorPoint.BOTTOM_LEFT &&
111
+ unref(scanState).Layout.PositionData[3].Anchor === AnchorPoint.BOTTOM_RIGHT
112
+ );
113
+ });
114
+ export const isLayout1x3 = computed(() => {
115
+ return (
116
+ unref(scanState).Layout.PositionData.length > 1 &&
117
+ unref(scanState).Layout.PositionData[0].Anchor === AnchorPoint.CENTER &&
118
+ unref(scanState).Layout.PositionData[1].Anchor === AnchorPoint.TOP_RIGHT &&
119
+ unref(scanState).Layout.PositionData[2].Anchor === AnchorPoint.RIGHT &&
120
+ unref(scanState).Layout.PositionData[3].Anchor === AnchorPoint.BOTTOM_RIGHT
121
+ );
122
+ });
123
+
124
+ export const currentGreyscalePreset = computed(() => {
125
+ for (const preset of unref(initialScanState).GreyscalePresets) {
126
+ if (
127
+ (unref(scanState).Display.WindowLower === preset.Lower ||
128
+ (preset.Lower < unref(initialScanState).HuLower &&
129
+ unref(scanState).Display.WindowLower ===
130
+ unref(initialScanState).HuLower)) &&
131
+ (unref(scanState).Display.WindowUpper === preset.Upper ||
132
+ (preset.Upper > unref(initialScanState).HuUpper &&
133
+ unref(scanState).Display.WindowUpper ===
134
+ unref(initialScanState).HuUpper))
135
+ ) {
136
+ return preset.Name;
137
+ }
138
+ }
139
+ return undefined;
140
+ });
141
+
142
+ export async function setScanStateFromPayload(_: string, message: string) {
143
+ transactionStarted.value = true;
144
+ scanStateIncoming.value = JSON.stringify(JSON.parse(message), null, 2);
145
+
146
+ setScanState(message);
147
+ await nextTick();
148
+ transactionStarted.value = false;
149
+ }
150
+ export async function setInitialScanStateFromPayload(
151
+ _: string,
152
+ message: string
153
+ ) {
154
+ transactionStarted.value = true;
155
+
156
+ scanStateIncoming.value = JSON.stringify(JSON.parse(message), null, 2);
157
+ const obj = JSON.parse(unref(scanStateIncoming)) as InitialScanState;
158
+
159
+ setScanState(JSON.stringify(obj.DefaultDisplaySettings, null, 2));
160
+ initialScanState.value = obj;
161
+ huMinMax.value.max = obj.HuUpper;
162
+ huMinMax.value.min = obj.HuLower;
163
+ sMinMax.value.max = obj.XSlices;
164
+ cMinMax.value.max = obj.YSlices;
165
+ tMinMax.value.max = obj.ZSlices;
166
+ currentColourPreset.value = obj.ColourPresets[0];
167
+ }
168
+
169
+ export function setScanState(message: string) {
170
+ scanState.value = JSON.parse(message) as CurrentScanState;
171
+ }
172
+
173
+ export function generateWatcherForScanMovement(
174
+ payloadHandler: PayloadHandler,
175
+ sliderAction: ScanMovementActions,
176
+ source: WatchSource<number>
177
+ ) {
178
+ watch(source, async (value: number) => {
179
+ await payloadHandler.scanMovementHandler(sliderAction, value);
180
+ });
181
+ }
@@ -0,0 +1,59 @@
1
+ <script setup lang="ts">
2
+ import { computed } from "vue";
3
+
4
+ export interface Props {
5
+ modal: boolean;
6
+ isModalOpen: boolean;
7
+ }
8
+
9
+ const emit = defineEmits<{
10
+ "update:modal": [value: boolean];
11
+ }>();
12
+
13
+ const props = withDefaults(defineProps<Props>(), {
14
+ modal: false,
15
+ isModalOpen: true,
16
+ });
17
+
18
+ const modalState = computed({
19
+ get() {
20
+ return props.modal;
21
+ },
22
+ set(value) {
23
+ emit("update:modal", value);
24
+ },
25
+ });
26
+ function openUrl(url: string) {
27
+ window.open(url, "_self");
28
+ }
29
+ </script>
30
+
31
+ <template>
32
+ <v-dialog v-model:model-value="modalState">
33
+ <v-card class="pa-1 ma-auto position-relative" max-width="450">
34
+ <v-card-title>Purchase 3Dicom Viewer</v-card-title>
35
+ <v-card-text> Thank you for trying 3Dicom Online Viewer! </v-card-text>
36
+ <v-card-text>
37
+ This instance of the Online Viewer is a Demo instance. If you would like
38
+ to see your scans in 3D, please choose a plan for 3DICOM
39
+ </v-card-text>
40
+ <v-card-actions>
41
+ <v-btn
42
+ color="error"
43
+ @click="modalState = false"
44
+ :disabled="!isModalOpen"
45
+ >
46
+ Continue with Viewer
47
+ </v-btn>
48
+ <v-spacer />
49
+ <v-btn
50
+ variant="tonal"
51
+ color="success"
52
+ @click="openUrl('https://3dicomviewer.com/pricing')"
53
+ >
54
+ Select a Plan
55
+ </v-btn>
56
+ </v-card-actions>
57
+ </v-card>
58
+ </v-dialog>
59
+ </template>
@@ -0,0 +1,93 @@
1
+ import { computed, ref, unref } from "vue";
2
+ import { LoadViewerOptions } from "@/models/LoadViewerOptions";
3
+ import { LoadViewerPayload } from "@/models/LoadViewerPayload";
4
+
5
+ export function checkIsDemo(payload: LoadViewerPayload) {
6
+ isDemo.value =
7
+ payload.Url ===
8
+ "https://webgl-3dr.singular.health/test_scans/01440d4e-8b04-4b90-bb2c-698535ce16d6/CHEST.3vxl";
9
+ }
10
+
11
+ export const isDemo = ref<boolean>(false);
12
+ export const m_demo = ref<boolean>(false);
13
+ export const m_demoPatient = ref<boolean>(false);
14
+ export const demoLicenceOptions: LoadViewerOptions = {
15
+ OnClosePopup: () => Promise.resolve(),
16
+ OnExitViewer: () => {
17
+ m_demo.value = true;
18
+ },
19
+ OnLoadNewDicomSeries: () => {
20
+ m_demo.value = true;
21
+ },
22
+ OnDownloadDicomSeries: () => {
23
+ m_demo.value = true;
24
+ },
25
+ OnLoadSavedSession: () => {
26
+ m_demo.value = true;
27
+ },
28
+ OnSaveSession: () => {
29
+ m_demo.value = true;
30
+ },
31
+ OnSendTo3rdParty: () => {
32
+ m_demo.value = true;
33
+ },
34
+ OnShareToMobile: () => {
35
+ m_demo.value = true;
36
+ },
37
+ OnShare: () => {
38
+ m_demo.value = true;
39
+ },
40
+ OnScreenshot: () => {
41
+ m_demo.value = true;
42
+ },
43
+ };
44
+
45
+ export const demoPatientOptions: LoadViewerOptions = {
46
+ OnClosePopup: () => Promise.resolve(),
47
+ OnExitViewer: () => {
48
+ m_demoPatient.value = true;
49
+ },
50
+ OnLoadNewDicomSeries: () => {
51
+ m_demoPatient.value = true;
52
+ },
53
+ OnDownloadDicomSeries: () => {
54
+ m_demoPatient.value = true;
55
+ },
56
+ OnLoadSavedSession: () => {
57
+ m_demoPatient.value = true;
58
+ },
59
+ OnSaveSession: () => {
60
+ m_demoPatient.value = true;
61
+ },
62
+ OnSendTo3rdParty: () => {
63
+ m_demoPatient.value = true;
64
+ },
65
+ OnShareToMobile: () => {
66
+ m_demoPatient.value = true;
67
+ },
68
+ OnShare: () => {
69
+ m_demoPatient.value = true;
70
+ },
71
+ OnScreenshot: () => {
72
+ m_demoPatient.value = true;
73
+ },
74
+ };
75
+
76
+ export const demoType = computed(() => {
77
+ const urlParams = new URLSearchParams(window.location.search);
78
+ return (urlParams.get("demoType") || "licence").toLowerCase();
79
+ });
80
+
81
+ export function executeDemoOption(key: keyof LoadViewerOptions) {
82
+ if (unref(demoType) === "licence") {
83
+ const functionToExecute = demoLicenceOptions[key];
84
+ if (functionToExecute !== undefined) {
85
+ functionToExecute();
86
+ }
87
+ } else if (unref(demoType) === "patient") {
88
+ const functionToExecute = demoPatientOptions[key];
89
+ if (functionToExecute !== undefined) {
90
+ functionToExecute();
91
+ }
92
+ }
93
+ }
@@ -0,0 +1,2 @@
1
+ export type ViewerCallback = () => void;
2
+ export type ViewerAsyncCallback = () => Promise<void>;
@@ -0,0 +1,27 @@
1
+ import { ViewerAsyncCallback, ViewerCallback } from "@/models/Callbacks";
2
+
3
+ export interface LoadViewerOptions {
4
+ OnShare?: ViewerCallback | ViewerAsyncCallback | undefined;
5
+ OnScreenshot?: ViewerCallback | ViewerAsyncCallback | undefined;
6
+ OnLoadNewDicomSeries?: ViewerCallback | ViewerAsyncCallback | undefined;
7
+ OnDownloadDicomSeries?: ViewerCallback | ViewerAsyncCallback | undefined;
8
+ OnLoadSavedSession?: ViewerCallback | ViewerAsyncCallback | undefined;
9
+ OnShareToMobile?: ViewerCallback | ViewerAsyncCallback | undefined;
10
+ OnSendTo3rdParty?: ViewerCallback | ViewerAsyncCallback | undefined;
11
+ OnSaveSession?: ViewerCallback | ViewerAsyncCallback | undefined;
12
+ OnClosePopup?: ViewerCallback | ViewerAsyncCallback | undefined;
13
+ OnExitViewer?: ViewerCallback | ViewerAsyncCallback | undefined;
14
+ }
15
+
16
+ export const defaultLoadViewerOptions: LoadViewerOptions = {
17
+ OnClosePopup: () => Promise.resolve(),
18
+ OnExitViewer: () => Promise.resolve(),
19
+ OnLoadNewDicomSeries: () => Promise.resolve(),
20
+ OnDownloadDicomSeries: () => Promise.resolve(),
21
+ OnLoadSavedSession: () => Promise.resolve(),
22
+ OnSaveSession: () => Promise.resolve(),
23
+ OnSendTo3rdParty: () => Promise.resolve(),
24
+ OnShareToMobile: () => Promise.resolve(),
25
+ OnShare: () => Promise.resolve(),
26
+ OnScreenshot: () => Promise.resolve(),
27
+ };
@@ -0,0 +1,8 @@
1
+ export interface LoadViewerPayload {
2
+ Url: string;
3
+ DecryptionKey: MftpDecryptionKey;
4
+ }
5
+ export interface MftpDecryptionKey {
6
+ Key: string;
7
+ Iv: string;
8
+ }
@@ -0,0 +1,48 @@
1
+ import {
2
+ InteractivityActions,
3
+ NotificationPayload,
4
+ NotificationsActions,
5
+ } from "@3cr/types-ts";
6
+ import { useNotification } from "@kyvg/vue3-notification";
7
+ const { notify } = useNotification();
8
+
9
+ export async function handleNotification(action: string, message: string) {
10
+ const notification = JSON.parse(message) as NotificationPayload;
11
+
12
+ switch (notification.Action) {
13
+ case InteractivityActions.in01:
14
+ case InteractivityActions.in02:
15
+ case InteractivityActions.in03:
16
+ case InteractivityActions.in04:
17
+ return;
18
+ default:
19
+ }
20
+ if (notification.Action.startsWith("sl")) return;
21
+
22
+ let type = "success";
23
+ switch (action) {
24
+ // Muting no01
25
+ case NotificationsActions.no01:
26
+ type = "success";
27
+ return;
28
+
29
+ case NotificationsActions.no02:
30
+ type = "warn";
31
+ break;
32
+
33
+ case NotificationsActions.no03:
34
+ type = "error";
35
+ break;
36
+
37
+ // Muting no04
38
+ case NotificationsActions.no04:
39
+ type = "info";
40
+ return;
41
+ }
42
+
43
+ notify({
44
+ title: notification.Code,
45
+ text: notification.Action,
46
+ type,
47
+ });
48
+ }
package/vitest.config.mts CHANGED
@@ -21,6 +21,7 @@ export default mergeConfig(viteConfig, defineConfig({
21
21
  reporter: ['html'],
22
22
  include: ['src/**/*', 'index.ts'],
23
23
  exclude: [
24
+ 'src/demo/**',
24
25
  'coverage/**',
25
26
  'dist/**',
26
27
  'scripts/**',
File without changes