@3cr/viewer-browser 0.0.220 → 0.0.246

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 (57) hide show
  1. package/components.d.ts +6 -2
  2. package/dist/Viewer3CR.js +32 -32
  3. package/dist/Viewer3CR.mjs +17808 -14315
  4. package/dist/Viewer3CR.umd.js +32 -32
  5. package/index.html +1 -1
  6. package/package.json +4 -3
  7. package/playground/index.html +4 -12
  8. package/src/App.vue +29 -14
  9. package/src/__tests__/main.spec.ts +4 -4
  10. package/src/assets/styles.scss +6 -2
  11. package/src/components/demo/DemoPatientModal.vue +12 -22
  12. package/src/components/demo/licence/DemoLicenceInfoModal.vue +11 -29
  13. package/src/components/demo/options.ts +42 -39
  14. package/src/components/demo/patient/DemoPatientInfoModal.vue +11 -29
  15. package/src/components/modal/CloseViewerModal.vue +1 -1
  16. package/src/components/modal/MftpWebGL3DRModal.vue +133 -120
  17. package/src/components/modal/ViewerActionRail.vue +0 -6
  18. package/src/components/modal/ViewerNavigationDrawerContent.vue +25 -16
  19. package/src/components/modal/ViewerNavigationDrawerFooter.vue +32 -9
  20. package/src/components/modal/ViewerNavigationDrawerHeader.vue +6 -1
  21. package/src/components/modal/WebGL3DR.vue +7 -0
  22. package/src/components/modal/__tests__/ViewerNavigationDrawerHeader.spec.ts +3 -2
  23. package/src/components/modal/buttons/AutoAnnotateBtn.vue +181 -13
  24. package/src/components/modal/menus/FileMenu.vue +2 -9
  25. package/src/components/modal/menus/SettingsMenu.vue +2 -7
  26. package/src/components/navigation/mcad/McadGlobalActions.vue +20 -0
  27. package/src/components/navigation/mcad/McadGlobalOpacitySlider.vue +103 -0
  28. package/src/components/navigation/mcad/McadGlobalScanViewBtn.vue +28 -0
  29. package/src/components/navigation/mcad/McadGlobalVisibilityBtn.vue +38 -0
  30. package/src/components/shared/LoadingSpinner.vue +27 -36
  31. package/src/components/views/AnnotationTreeView.vue +3 -1
  32. package/src/components/views/MarkupTreeView.vue +3 -1
  33. package/src/components/views/McadObjectTreeView.vue +46 -36
  34. package/src/components/views/modals/DataOverlayGeneralModal.vue +71 -0
  35. package/src/components/views/modals/DataOverlayMarkupModal.vue +60 -0
  36. package/src/components/views/modals/DataOverlayModal.vue +54 -55
  37. package/src/components/views/shared/Opacity.vue +0 -1
  38. package/src/composables/useDebounce.ts +11 -0
  39. package/src/composables/useIntroJs.ts +23 -6
  40. package/src/composables/useViewerOptions.ts +1 -0
  41. package/src/functions/guards/isDataOverlayAngle.ts +6 -0
  42. package/src/functions/guards/isDataOverlayLength.ts +6 -0
  43. package/src/functions/guards/isDataOverlayPolygon.ts +6 -0
  44. package/src/functions/modelHelper.ts +2 -0
  45. package/src/functions/notification.ts +63 -29
  46. package/src/main.ts +22 -13
  47. package/src/models/loadViewerOptions.ts +39 -0
  48. package/src/models/loadViewerPayload.ts +0 -7
  49. package/src/services/viewer-3cr.service.ts +13 -1
  50. package/src/tools/data-overlay.tool.ts +71 -0
  51. package/src/types/data-overlay-event.ts +5 -0
  52. package/src/types/data-overlay-info.ts +4 -0
  53. package/src/types/demo-type.ts +4 -0
  54. package/src/components/modal/actions/HideViewAction.vue +0 -38
  55. package/src/components/views/modals/DataOverlayModalManager.vue +0 -104
  56. package/src/components/views/modals/__tests__/DataOverlayModal.spec.ts +0 -33
  57. package/src/components/views/modals/__tests__/DataOverlayModalManager.spec.ts +0 -93
@@ -1,15 +1,44 @@
1
1
  <template>
2
- <v-btn
3
- v-if="opts.OnAutoAnnotate"
4
- @click="autoAnnotate()"
5
- :loading="loading"
6
- :icon="true"
7
- variant="text"
8
- :disabled="done"
9
- >
10
- <v-icon>flash_auto</v-icon>
11
- <v-tooltip activator="parent" location="bottom">Auto Annotate</v-tooltip>
12
- </v-btn>
2
+ <div>
3
+ <v-menu :close-on-content-click="false" v-if="opts.OnAutoAnnotate">
4
+ <template #activator="{ props }">
5
+ <v-btn v-bind="props" data-testid="menu" class="mx-2" color="primary" prepend-icon="psychology" variant="flat">
6
+ AI
7
+ </v-btn>
8
+ </template>
9
+ <v-list>
10
+ <v-list-item
11
+ data-testid="auto_annotate"
12
+ v-if="opts.OnAutoAnnotate"
13
+ @click="autoAnnotate()"
14
+ :disabled="done"
15
+ prepend-icon="flash_auto"
16
+ :append-icon="done ? 'check' : undefined"
17
+ >
18
+ <v-list-item-title>Auto Annotate</v-list-item-title>
19
+ </v-list-item>
20
+ </v-list>
21
+ </v-menu>
22
+ <v-dialog v-model:model-value="loading">
23
+ <template #default>
24
+ <v-card :title="`Annotiva AI: ${type}`" max-width="450" class="mx-auto">
25
+ <div class="prompt">
26
+ <div style="font-size: 6px; top: 30px; left: 40px">
27
+ <div class="cog" style="--cog-colour: #5a93ed"></div>
28
+ <div
29
+ class="cog anti-clockwise"
30
+ style="--cog-colour: #2955da; margin-top: 2.25em; margin-left: -3.375em"
31
+ ></div>
32
+ <div class="cog" style="--cog-colour: #bad3f8; margin-top: 5.625em; margin-left: -1.125em"></div>
33
+ </div>
34
+ <v-card-text class="ml-10" style="font-size: 16px">
35
+ {{ text }}
36
+ </v-card-text>
37
+ </div>
38
+ </v-card>
39
+ </template>
40
+ </v-dialog>
41
+ </div>
13
42
  </template>
14
43
 
15
44
  <script setup lang="ts">
@@ -19,11 +48,150 @@ import { ref } from 'vue';
19
48
  const opts = useViewerOptions();
20
49
  const loading = ref<boolean>(false);
21
50
  const done = ref<boolean>(false);
51
+ const text = ref<string>('');
52
+ const type = ref<string>('');
22
53
 
23
54
  async function autoAnnotate() {
55
+ type.value = 'Auto Annotate';
56
+ text.value = getRandomItemFromList(loaderTexts);
24
57
  loading.value = true;
25
58
  await opts.value!.OnAutoAnnotate!();
26
- loading.value = false;
27
- done.value = true;
59
+ setTimeout(() => {
60
+ loading.value = false;
61
+ done.value = true;
62
+ }, 4000);
28
63
  }
64
+
65
+ function getRandomItemFromList(list: Array<string>) {
66
+ const randomIndex = Math.floor(Math.random() * list.length);
67
+ return list[randomIndex];
68
+ }
69
+
70
+ const loaderTexts = [
71
+ "AI: Analyzing X-rays faster than you can say 'supercalifragilisticexpialidocious'!",
72
+ 'Just a moment, our AI is putting on its lab coat.',
73
+ 'Scanning pixels like Sherlock scans clues.',
74
+ 'Our AI is channeling its inner doctor.',
75
+ 'Hang tight, our AI is donning its stethoscope.',
76
+ "Analyzing with AI precision – because guessing isn't an option!",
77
+ 'Our AI is checking the scan like a detective at a crime scene.',
78
+ 'Crunching data like a pro! Results on the way.',
79
+ 'AI is on it, decoding your scan like a mystery novel.',
80
+ 'Our AI is in deep thought... about your health!',
81
+ 'Letting our AI take a close look at those pixels.',
82
+ 'Your scan is getting the AI treatment.',
83
+ 'Analyzing like a champ! Our AI is almost there.',
84
+ 'Hold tight! Our AI is putting on its thinking cap.',
85
+ 'AI is hard at work, diagnosing like a pro!',
86
+ 'Our AI is busy solving the puzzle of your scan.',
87
+ 'AI is processing... and sipping virtual coffee.',
88
+ "Your scan is in good hands – AI's hands.",
89
+ 'Just a sec, our AI is perfecting the diagnosis.',
90
+ 'Scanning... AI style.',
91
+ 'AI is reading your scan like an open book.',
92
+ 'Give us a moment, our AI is analyzing every detail.',
93
+ 'Our AI is double-checking for good measure.',
94
+ 'AI: Making sense of pixels and patterns.',
95
+ 'AI is decoding your scan, Sherlock style.',
96
+ 'Processing with precision – AI is on it.',
97
+ 'Our AI is diving deep into the data.',
98
+ 'AI is dissecting the details for an accurate diagnosis.',
99
+ 'Hold on, our AI is piecing together the puzzle.',
100
+ 'AI is at the helm, navigating your scan results.',
101
+ 'Analyzing with AI magic – results coming up!',
102
+ 'Our AI is untangling the mysteries of your scan.',
103
+ "Processing... AI's putting its brain to work.",
104
+ 'Your scan + our AI = accurate results.',
105
+ 'AI is fine-tuning the analysis for perfection.',
106
+ 'Letting our AI do the heavy lifting – almost done!',
107
+ 'AI is reading the scan like a pro.',
108
+ 'Our AI is on a mission – accurate results ahead.',
109
+ 'Analyzing with AI expertise – hang tight!',
110
+ 'AI is taking a closer look – results are on the way.'
111
+ ];
29
112
  </script>
113
+ <style>
114
+ .prompt {
115
+ margin-left: 20px;
116
+ margin-right: 20px;
117
+ padding: 20px;
118
+ text-align: center;
119
+ color: black;
120
+ display: flex;
121
+ justify-content: space-between;
122
+ align-items: center;
123
+ margin-bottom: 15px;
124
+ z-index: 100;
125
+ position: relative;
126
+ }
127
+
128
+ .prompt::before {
129
+ content: '';
130
+ position: absolute;
131
+ inset: 0;
132
+ border-radius: 16px;
133
+ padding: 3px;
134
+ background: linear-gradient(to right, #6fb3f2, #4e51f5);
135
+
136
+ -webkit-mask:
137
+ linear-gradient(#fff 0 0) content-box,
138
+ linear-gradient(#fff 0 0);
139
+ -webkit-mask-composite: xor;
140
+ mask-composite: exclude;
141
+ }
142
+
143
+ div.cog {
144
+ nfont-size: 25px;
145
+ border-radius: 0.25em;
146
+ border: solid 0.55em var(--cog-colour, #eee);
147
+ display: inline-block;
148
+ padding: 1.125em;
149
+ position: absolute;
150
+ top: inherit;
151
+ left: inherit;
152
+ -webkit-animation: rotate var(--cog-speed, 3s) linear infinite;
153
+ -moz-animation: rotate var(--cog-speed, 3s) linear infinite;
154
+ animation: rotate var(--cog-speed, 3s) linear infinite;
155
+ }
156
+
157
+ div.cog.anti-clockwise {
158
+ -webkit-animation-direction: reverse;
159
+ -moz-animation-direction: reverse;
160
+ animation-direction: reverse;
161
+ }
162
+
163
+ div.cog::before {
164
+ border: solid 0.55em var(--cog-colour, #eee);
165
+ padding: 1.125em;
166
+ position: absolute;
167
+ content: '';
168
+ transform: translate(-1.675em, -1.675em) rotate(45deg);
169
+ border-radius: 0.25em;
170
+ }
171
+
172
+ div.cog::after {
173
+ content: '';
174
+ position: absolute;
175
+ border: solid 1em var(--cog-colour, #eee);
176
+ padding: 0.375em;
177
+ border-radius: 1.8125em;
178
+ transform: translate(-1.375em, -1.375em);
179
+ filter: brightness(87.5%);
180
+ }
181
+
182
+ @-moz-keyframes rotate {
183
+ 100% {
184
+ -moz-transform: rotate(360deg);
185
+ }
186
+ }
187
+ @-webkit-keyframes rotate {
188
+ 100% {
189
+ -webkit-transform: rotate(360deg);
190
+ }
191
+ }
192
+ @keyframes rotate {
193
+ 100% {
194
+ -webkit-transform: rotate(360deg);
195
+ }
196
+ }
197
+ </style>
@@ -1,14 +1,7 @@
1
1
  <template>
2
2
  <v-menu :close-on-content-click="false">
3
- <template #activator="{ props, isActive }">
4
- <v-btn
5
- v-bind="props"
6
- data-testid="menu"
7
- class="mr-2"
8
- :color="isActive ? 'secondary' : 'primary'"
9
- prepend-icon="description"
10
- variant="flat"
11
- >
3
+ <template #activator="{ props }">
4
+ <v-btn v-bind="props" data-testid="menu" class="mr-2" color="primary" prepend-icon="description" variant="flat">
12
5
  File
13
6
  </v-btn>
14
7
  </template>
@@ -1,12 +1,7 @@
1
1
  <template>
2
2
  <v-menu v-model="menuState" :close-on-content-click="false">
3
- <template #activator="{ props, isActive }">
4
- <v-btn
5
- v-bind="mergeProps(props, activatorProps)"
6
- :color="isActive ? 'secondary' : 'primary'"
7
- prepend-icon="settings"
8
- variant="flat"
9
- >
3
+ <template #activator="{ props }">
4
+ <v-btn v-bind="mergeProps(props, activatorProps)" color="primary" prepend-icon="settings" variant="flat">
10
5
  Settings
11
6
  </v-btn>
12
7
  </template>
@@ -0,0 +1,20 @@
1
+ <template>
2
+ <v-card flat density="compact" color="transparent">
3
+ <v-card-title class="text-overline">Global Actions</v-card-title>
4
+ <v-card-text class="d-flex flex-column ga-1">
5
+ <mcad-global-scan-view-btn />
6
+ <!-- <mcad-global-visibility-btn :mcads="items" />-->
7
+ <!-- <mcad-global-opacity-slider :mcads="items" />-->
8
+ </v-card-text>
9
+ </v-card>
10
+ </template>
11
+
12
+ <script setup lang="ts">
13
+ import { DataOverlayMcad } from '@/types/data-overlay-mcad';
14
+
15
+ interface Props {
16
+ items: DataOverlayMcad[];
17
+ }
18
+
19
+ defineProps<Props>();
20
+ </script>
@@ -0,0 +1,103 @@
1
+ <template>
2
+ <v-row class="flex-column" no-gutters>
3
+ <v-row no-gutters>
4
+ <v-col>
5
+ <span class="text-caption">Global Opacity</span>
6
+ </v-col>
7
+ </v-row>
8
+ <v-row no-gutters>
9
+ <v-col class="my-auto" cols="9">
10
+ <v-slider
11
+ v-model="opacity"
12
+ class="pr-4"
13
+ hide-details
14
+ min="0"
15
+ max="100"
16
+ thumb-color="primary"
17
+ thumb-size="16"
18
+ step="1"
19
+ />
20
+ </v-col>
21
+ <v-col class="my-auto" cols="3">
22
+ <v-text-field
23
+ v-model="opacity"
24
+ density="compact"
25
+ hide-details
26
+ variant="outlined"
27
+ type="number"
28
+ min="0"
29
+ max="100"
30
+ >
31
+ <template #append-inner>
32
+ <v-icon icon="percent" size="16" />
33
+ </template>
34
+ </v-text-field>
35
+ </v-col>
36
+ </v-row>
37
+ </v-row>
38
+ </template>
39
+
40
+ <script setup lang="ts">
41
+ import { DataOverlayMcad } from '@/types/data-overlay-mcad';
42
+ import { useViewer3cr } from '@/composables/useViewer3cr';
43
+ import { computed, ref } from 'vue';
44
+ import { ObjectColour } from '@3cr/types-ts';
45
+ import { clamp } from '@/functions/clamp';
46
+ import { useDebounce } from '@/composables/useDebounce';
47
+
48
+ interface Props {
49
+ mcads: DataOverlayMcad[];
50
+ }
51
+
52
+ const props = defineProps<Props>();
53
+
54
+ const viewer3cr = useViewer3cr();
55
+ const setOpacityDebounce = useDebounce(setOpacity, 500);
56
+
57
+ const _opacity = ref<number>(100);
58
+
59
+ const opacity = computed({
60
+ get(): number {
61
+ return _opacity.value;
62
+ },
63
+ set(value: number): void {
64
+ const next = clamp(value, 0, 100);
65
+ _opacity.value = next;
66
+ setOpacityDebounce(next);
67
+ }
68
+ });
69
+
70
+ async function setOpacity(value: number): Promise<void> {
71
+ const alpha = clamp(value, 0, 100) / 100;
72
+ for (const mcad of props.mcads) {
73
+ const message: ObjectColour = {
74
+ Version: '0.0.0',
75
+ Id: mcad.Id,
76
+ Colour: {
77
+ Version: '0.0.0',
78
+ R: mcad.Colour.R,
79
+ G: mcad.Colour.G,
80
+ B: mcad.Colour.B,
81
+ A: alpha
82
+ }
83
+ };
84
+ await viewer3cr.setMcadObjectColour(message);
85
+ }
86
+ }
87
+ </script>
88
+
89
+ <style scoped lang="scss">
90
+ :deep(.v-field) {
91
+ padding-right: 1px !important;
92
+ }
93
+
94
+ :deep(input) {
95
+ font-size: 11px;
96
+ padding: 4px 0 4px 4px;
97
+ min-height: 0;
98
+ }
99
+
100
+ :deep(.v-field__append-inner) {
101
+ padding-top: 4px;
102
+ }
103
+ </style>
@@ -0,0 +1,28 @@
1
+ <template>
2
+ <v-btn variant="text" size="small" @click="toggle">
3
+ <v-icon start :icon="icon" size="20" />
4
+ <span>{{ visibility ? 'Hide' : 'Show' }} 3D Volume</span>
5
+ </v-btn>
6
+ </template>
7
+
8
+ <script setup lang="ts">
9
+ import { computed } from 'vue';
10
+ import { useViewer3cr } from '@/composables/useViewer3cr';
11
+ import { ScanView, ViewToggleData } from '@3cr/types-ts';
12
+ import { scanState } from '@/models/scanState';
13
+
14
+ const viewer3cr = useViewer3cr();
15
+
16
+ const visibility = computed(() => {
17
+ return scanState.value.Orientations.Volume.Visibility;
18
+ });
19
+
20
+ const icon = computed(() => {
21
+ return visibility.value ? 'visibility' : 'visibility_off';
22
+ });
23
+
24
+ async function toggle(): Promise<void> {
25
+ const message: ViewToggleData = { Version: '0.0.0', View: ScanView.Volume, Visibility: !visibility.value };
26
+ await viewer3cr.viewSelectionToggleView(message);
27
+ }
28
+ </script>
@@ -0,0 +1,38 @@
1
+ <template>
2
+ <v-btn variant="text" size="small" @click="toggle">
3
+ <v-icon start :icon="icon" size="20" />
4
+ <span>{{ visibility ? 'Hide' : 'Show' }} all MCADs</span>
5
+ </v-btn>
6
+ </template>
7
+
8
+ <script setup lang="ts">
9
+ import { computed } from 'vue';
10
+ import { DataOverlayMcad } from '@/types/data-overlay-mcad';
11
+ import { ObjectVisibility } from '@3cr/types-ts';
12
+ import { useViewer3cr } from '@/composables/useViewer3cr';
13
+
14
+ interface Props {
15
+ mcads: DataOverlayMcad[];
16
+ }
17
+
18
+ const props = defineProps<Props>();
19
+
20
+ const viewer3cr = useViewer3cr();
21
+
22
+ const visibility = computed(() => {
23
+ return props.mcads.some((mcad) => mcad.Visibility);
24
+ });
25
+
26
+ const icon = computed(() => {
27
+ return visibility.value ? 'visibility' : 'visibility_off';
28
+ });
29
+
30
+ async function toggle(): Promise<void> {
31
+ for (const mcad of props.mcads) {
32
+ const message: ObjectVisibility = { Version: '0.0.0', Id: mcad.Id, Visibility: !visibility.value };
33
+ await viewer3cr.setMcadObjectVisibility(message);
34
+ }
35
+ }
36
+
37
+ defineOptions({ inheritAttrs: false });
38
+ </script>
@@ -4,12 +4,12 @@ export interface Props {
4
4
  }
5
5
 
6
6
  const props = withDefaults(defineProps<Props>(), {
7
- text: "Loading Online Viewer",
7
+ text: 'Loading Online Viewer'
8
8
  });
9
9
  </script>
10
10
 
11
11
  <template>
12
- <div style="height: 100%">
12
+ <div class="w-100 h-100">
13
13
  <div id="spinner">
14
14
  <div class="content">
15
15
  <div class="circle"></div>
@@ -20,12 +20,7 @@ const props = withDefaults(defineProps<Props>(), {
20
20
  </div>
21
21
  <div
22
22
  class="mx-auto text-center text-white text-h3"
23
- style="
24
- position: absolute;
25
- bottom: 10%;
26
- left: 50%;
27
- transform: translateX(-50%);
28
- "
23
+ style="position: absolute; bottom: 10%; left: 50%; transform: translateX(-50%)"
29
24
  v-html="props.text"
30
25
  ></div>
31
26
  </div>
@@ -62,20 +57,9 @@ const props = withDefaults(defineProps<Props>(), {
62
57
  --in: 80%;
63
58
  --ar: #8799a4;
64
59
  --dt: #ffffff;
65
- --shadow: drop-shadow(0vmin 0vmin 0.5vmin rgba(0, 0, 0, 0.35))
66
- drop-shadow(0vmin 1vmin 0.5vmin rgba(0, 0, 0, 0.09));
67
- --cross: linear-gradient(
68
- 0deg,
69
- #fff0 calc(50% - 2px),
70
- #000 calc(50% - 1px) calc(50% + 1px),
71
- #fff0 calc(50% + 2px)
72
- ),
73
- linear-gradient(
74
- 90deg,
75
- #fff0 calc(50% - 2px),
76
- #000 calc(50% - 1px) calc(50% + 1px),
77
- #fff0 calc(50% + 2px)
78
- );
60
+ --shadow: drop-shadow(0vmin 0vmin 0.5vmin rgba(0, 0, 0, 0.35)) drop-shadow(0vmin 1vmin 0.5vmin rgba(0, 0, 0, 0.09));
61
+ --cross: linear-gradient(0deg, #fff0 calc(50% - 2px), #000 calc(50% - 1px) calc(50% + 1px), #fff0 calc(50% + 2px)),
62
+ linear-gradient(90deg, #fff0 calc(50% - 2px), #000 calc(50% - 1px) calc(50% + 1px), #fff0 calc(50% + 2px));
79
63
  border: 6vmin solid var(--ar);
80
64
  width: var(--in);
81
65
  height: var(--in);
@@ -87,10 +71,12 @@ const props = withDefaults(defineProps<Props>(), {
87
71
  top: 15vmin;
88
72
  right: -10vmin;
89
73
  animation: spin-bot var(--sp) ease 0s infinite;
90
- background-image: var(--cross),
91
- radial-gradient(var(--dt) 5.5vmin, #fff0 calc(5.5vmin + 1px));
74
+ background-image: var(--cross), radial-gradient(var(--dt) 5.5vmin, #fff0 calc(5.5vmin + 1px));
92
75
  background-repeat: no-repeat;
93
- background-size: 3vmin 1vmin, 1vmin 3vmin, 100% 100%;
76
+ background-size:
77
+ 3vmin 1vmin,
78
+ 1vmin 3vmin,
79
+ 100% 100%;
94
80
  background-position: center center;
95
81
  filter: var(--shadow);
96
82
  }
@@ -100,11 +86,13 @@ const props = withDefaults(defineProps<Props>(), {
100
86
  top: -2vmin;
101
87
  animation: spin-top var(--sp) ease 0s infinite;
102
88
  transform: rotate(-45deg);
103
- background-image: var(--cross),
104
- radial-gradient(var(--dt) 1.25vmin, #fff0 calc(1.25vmin + 1px));
89
+ background-image: var(--cross), radial-gradient(var(--dt) 1.25vmin, #fff0 calc(1.25vmin + 1px));
105
90
  right: -4vmin;
106
91
  filter: hue-rotate(10deg) var(--shadow);
107
- background-size: 1.4vmin 1vmin, 1vmin 1.4vmin, 100% 100%;
92
+ background-size:
93
+ 1.4vmin 1vmin,
94
+ 1vmin 1.4vmin,
95
+ 100% 100%;
108
96
  }
109
97
 
110
98
  .circle:nth-child(3) {
@@ -113,10 +101,12 @@ const props = withDefaults(defineProps<Props>(), {
113
101
  left: -13vmin;
114
102
  transform: rotate(175deg);
115
103
  animation: spin-left var(--sp) ease calc(var(--sp) / 4) infinite;
116
- background-image: var(--cross),
117
- radial-gradient(var(--dt) 9vmin, #fff0 calc(9vmin + 1px));
104
+ background-image: var(--cross), radial-gradient(var(--dt) 9vmin, #fff0 calc(9vmin + 1px));
118
105
  filter: hue-rotate(20deg) var(--shadow);
119
- background-size: 5vmin 1vmin, 1vmin 5vmin, 100% 100%;
106
+ background-size:
107
+ 5vmin 1vmin,
108
+ 1vmin 5vmin,
109
+ 100% 100%;
120
110
  }
121
111
 
122
112
  .circle:nth-child(4) {
@@ -124,12 +114,13 @@ const props = withDefaults(defineProps<Props>(), {
124
114
  top: 35vmin;
125
115
  left: -6vmin;
126
116
  transform: rotate(-280deg);
127
- animation: spin-last var(--sp) ease
128
- calc(calc(calc(var(--sp) / 4) + var(--sp)) * -1) infinite;
129
- background-image: var(--cross),
130
- radial-gradient(var(--dt) 2.5vmin, #fff0 calc(2.5vmin + 1px));
117
+ animation: spin-last var(--sp) ease calc(calc(calc(var(--sp) / 4) + var(--sp)) * -1) infinite;
118
+ background-image: var(--cross), radial-gradient(var(--dt) 2.5vmin, #fff0 calc(2.5vmin + 1px));
131
119
  filter: hue-rotate(30deg) var(--shadow);
132
- background-size: 2vmin 1vmin, 1vmin 2vmin, 100% 100%;
120
+ background-size:
121
+ 2vmin 1vmin,
122
+ 1vmin 2vmin,
123
+ 100% 100%;
133
124
  }
134
125
 
135
126
  @keyframes spin-all {
@@ -89,7 +89,9 @@ const questionText = ref<string>('');
89
89
  const title = ref<string>('');
90
90
 
91
91
  function setTreeViewData(annotations: DataOverlayData<DataOverlayAnnotation>[]): void {
92
- data.value = annotations.map((annotation) => mapToTreeViewItem(annotation));
92
+ data.value = annotations
93
+ .sort((a, b) => a.Data.Title.localeCompare(b.Data.Title))
94
+ .map((annotation) => mapToTreeViewItem(annotation));
93
95
  }
94
96
 
95
97
  watch(
@@ -61,7 +61,9 @@ watch(
61
61
  );
62
62
 
63
63
  function setTreeViewData(markups: DataOverlayData<DataOverlayMarkup>[]): void {
64
- data.value = markups.map((markup) => mapToTreeViewItem(markup));
64
+ data.value = markups
65
+ .sort((a, b) => a.Data.Title.localeCompare(b.Data.Title))
66
+ .map((markup) => mapToTreeViewItem(markup));
65
67
  }
66
68
 
67
69
  function getMarkupColour(data: DataOverlayData<DataOverlayMarkup>): string {