@3cr/viewer-browser 0.0.247 → 0.0.292

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 (519) hide show
  1. package/.circleci/config.yml +53 -0
  2. package/README.md +79 -74
  3. package/components.d.ts +94 -36
  4. package/dist/Viewer3CR.js +663 -47
  5. package/dist/Viewer3CR.mjs +108285 -37154
  6. package/dist/Viewer3CR.umd.js +663 -47
  7. package/index.html +35 -33
  8. package/package.json +27 -8
  9. package/playground/img.png +0 -0
  10. package/playground/index.html +6 -2
  11. package/src/App.vue +41 -97
  12. package/src/__tests__/app.spec.ts +2 -1
  13. package/src/__tests__/main.spec.ts +14 -14
  14. package/src/assets/_palette.scss +112 -0
  15. package/src/assets/_settings.scss +64 -0
  16. package/src/assets/_typography.scss +187 -0
  17. package/src/assets/images/cloud-storage.svg +22 -0
  18. package/src/assets/images/courtney.png +0 -0
  19. package/src/assets/images/dark/magic-wand.svg +32 -0
  20. package/src/assets/images/dark-theme.svg +33 -0
  21. package/src/assets/images/dot.svg +5 -0
  22. package/src/assets/images/dr-miles.png +0 -0
  23. package/src/assets/images/illustration.svg +118 -0
  24. package/src/assets/images/intro.svg +30 -0
  25. package/src/assets/{magic_wand.svg → images/light/magic-wand.svg} +22 -14
  26. package/src/assets/images/light-theme.svg +33 -0
  27. package/src/assets/images/system-theme.svg +51 -0
  28. package/src/assets/images/totalSegmentator.png +0 -0
  29. package/src/assets/logos/3dicom/black/black.svg +30 -0
  30. package/src/assets/logos/3dicom/colour/colour.svg +18 -0
  31. package/src/assets/logos/3dicom/colour/colour_single.svg +18 -0
  32. package/src/assets/logos/3dicom/wordmark/patients/patients.svg +29 -0
  33. package/src/assets/logos/3dicom/wordmark/powered-by/powered-by.svg +41 -0
  34. package/src/assets/logos/3dicom/wordmark/practitioners/practitioner.svg +34 -0
  35. package/src/assets/logos/3dicom/wordmark/research/research.svg +29 -0
  36. package/src/assets/logos/license/black/black.svg +22 -0
  37. package/src/assets/logos/license/white/white.svg +22 -0
  38. package/src/assets/styles.scss +435 -12
  39. package/src/components/ShFlatIconBtn.vue +36 -0
  40. package/src/components/ShPlainIconBtn.vue +42 -0
  41. package/src/components/ShSelect.vue +48 -0
  42. package/src/components/ShTextField.vue +39 -0
  43. package/src/components/ShTextarea.vue +39 -0
  44. package/src/components/__tests__/ShFlatIconBtn.spec.ts +32 -0
  45. package/src/components/__tests__/ShPlainIconBtn.spec.ts +37 -0
  46. package/src/components/__tests__/ShSelect.spec.ts +30 -0
  47. package/src/components/__tests__/ShTextField.spec.ts +30 -0
  48. package/src/components/__tests__/ShTextarea.spec.ts +30 -0
  49. package/src/components/demo/CloudDropZone.vue +105 -0
  50. package/src/components/demo/DemoAvatarDropdownMenu.vue +343 -0
  51. package/src/components/demo/DemoInfoModal.vue +60 -0
  52. package/src/components/demo/DemoNotificationNavigationDrawer.vue +118 -0
  53. package/src/components/demo/DemoPurchaseCreditsModal.vue +141 -0
  54. package/src/components/demo/DemoUpgradeCloudStorageModal.vue +74 -0
  55. package/src/components/demo/DemoUpgradePlanModal.vue +17 -0
  56. package/src/components/demo/UploadDicomToCloudModal.vue +225 -0
  57. package/src/components/demo/__tests__/CloudDropZone.spec.ts +132 -0
  58. package/src/components/demo/__tests__/DemoAvatarDropdownMenu.spec.ts +16 -0
  59. package/src/components/demo/{licence/__tests__/DemoLicenceEnableCloudStorageModal.spec.ts → __tests__/DemoEnableCloudStorageModal.spec.ts} +11 -11
  60. package/src/components/demo/{licence/__tests__/DemoLicenceInfoModal.spec.ts → __tests__/DemoInfoModal.spec.ts} +11 -8
  61. package/src/components/demo/__tests__/DemoNotificationNavigationDrawer.spec.ts +56 -0
  62. package/src/components/demo/__tests__/UploadDicomToCloudModal.spec.ts +320 -0
  63. package/src/components/demo/__tests__/demo-payload.spec.ts +10 -0
  64. package/src/components/demo/demo-notifications.ts +45 -0
  65. package/src/components/demo/demo-payload.ts +9 -0
  66. package/src/components/demo/fileReader.ts +47 -0
  67. package/src/{composables/useViewerOptions.ts → components/demo/mockDemoViewerStlDecimator.ts} +8 -12
  68. package/src/components/intro/ViewerIntro.vue +56 -0
  69. package/src/components/intro/ViewerTour.vue +53 -0
  70. package/src/components/intro/tour/Tour.vue +205 -0
  71. package/src/components/intro/tour/step.ts +6 -0
  72. package/src/components/modal/ActionRail.vue +4 -0
  73. package/src/components/modal/Viewer3cr.vue +224 -0
  74. package/src/components/modal/Viewer3crWebGL.vue +65 -0
  75. package/src/components/modal/ViewerActionRail.vue +10 -27
  76. package/src/components/modal/ViewerMain.vue +83 -0
  77. package/src/components/modal/ViewerNavigationDrawer.vue +269 -68
  78. package/src/components/modal/ViewerScanView.vue +223 -67
  79. package/src/components/modal/__tests__/{MftpWebGL3DRModal.spec.ts → Viewer3cr.spec.ts} +154 -53
  80. package/src/components/modal/__tests__/{WebGL3DR.spec.ts → Viewer3crWebGL.spec.ts} +11 -11
  81. package/src/components/modal/__tests__/ViewerNavigationDrawer.spec.ts +271 -11
  82. package/src/components/modal/__tests__/ViewerScanView.spec.ts +15 -7
  83. package/src/components/modal/actions/Action.vue +3 -8
  84. package/src/components/modal/actions/Flip3dAction.vue +31 -16
  85. package/src/components/modal/actions/FlipHorizontalAction.vue +14 -5
  86. package/src/components/modal/actions/FlipVerticalAction.vue +14 -5
  87. package/src/components/modal/actions/FullscreenAction.vue +9 -5
  88. package/src/components/modal/actions/PanAction.vue +11 -51
  89. package/src/components/modal/actions/ResetViewAction.vue +10 -8
  90. package/src/components/modal/actions/Rotate2dAction.vue +25 -15
  91. package/src/components/modal/actions/Slice3dAction.vue +22 -10
  92. package/src/components/modal/actions/ZoomAction.vue +21 -13
  93. package/src/components/modal/actions/__tests__/Action.spec.ts +2 -2
  94. package/src/components/modal/actions/__tests__/Flip3dAction.spec.ts +58 -14
  95. package/src/components/modal/actions/__tests__/FlipHorizontalAction.spec.ts +15 -5
  96. package/src/components/modal/actions/__tests__/FlipVerticalAction.spec.ts +15 -5
  97. package/src/components/modal/actions/__tests__/FullscreenAction.spec.ts +7 -5
  98. package/src/components/modal/actions/__tests__/PanAction.spec.ts +13 -32
  99. package/src/components/modal/actions/__tests__/ResetViewAction.spec.ts +5 -3
  100. package/src/components/modal/actions/__tests__/Rotate2dAction.spec.ts +13 -6
  101. package/src/components/modal/actions/__tests__/Slice3dAction.spec.ts +1 -1
  102. package/src/components/modal/actions/__tests__/ZoomAction.spec.ts +25 -11
  103. package/src/components/modal/app-bars/ViewerActionBar.vue +224 -0
  104. package/src/components/modal/app-bars/ViewerAppBar.vue +126 -0
  105. package/src/components/modal/app-bars/__tests__/ViewerActionBar.spec.ts +176 -0
  106. package/src/components/modal/app-bars/__tests__/ViewerAppBar.spec.ts +99 -0
  107. package/src/components/modal/layouts/Layout1x3ClipPath.vue +35 -0
  108. package/src/components/modal/layouts/Layout2x2ClipPath.vue +35 -0
  109. package/src/components/modal/layouts/LayoutFullscreenClipPath.vue +29 -0
  110. package/src/components/modal/layouts/__tests__/Layout1x3ClipPath.spec.ts +15 -0
  111. package/src/components/modal/layouts/__tests__/Layout2x2ClipPath.spec.ts +15 -0
  112. package/src/components/modal/layouts/__tests__/LayoutFullscreenClipPath.spec.ts +15 -0
  113. package/src/components/modal/layouts/composables/__tests__/useLayout1x3.spec.ts +40 -0
  114. package/src/components/modal/layouts/composables/__tests__/useLayout2x2.spec.ts +40 -0
  115. package/src/components/modal/layouts/composables/__tests__/useLayoutFullscreen.spec.ts +40 -0
  116. package/src/components/modal/layouts/composables/useLayout1x3.ts +46 -0
  117. package/src/components/modal/layouts/composables/useLayout2x2.ts +46 -0
  118. package/src/components/modal/layouts/composables/useLayoutFullscreen.ts +24 -0
  119. package/src/components/modal/menus/AutoAnnotateMenu.vue +67 -0
  120. package/src/components/modal/menus/AvatarDropdownMenu.vue +66 -0
  121. package/src/components/modal/menus/HelpMenu.vue +47 -0
  122. package/src/components/modal/menus/SettingsMenu.vue +242 -92
  123. package/src/components/modal/menus/ShareMenu.vue +58 -0
  124. package/src/components/modal/menus/__tests__/AutoAnnotateMenu.spec.ts +80 -0
  125. package/src/components/modal/menus/__tests__/AvatarDropdownMenu.spec.ts +49 -0
  126. package/src/components/modal/menus/__tests__/HelpMenu.spec.ts +12 -0
  127. package/src/components/modal/menus/__tests__/SettingsMenu.spec.ts +163 -57
  128. package/src/components/modal/modal/AboutModal.vue +61 -0
  129. package/src/components/modal/modal/AiModelList.vue +70 -0
  130. package/src/components/modal/{AskAI.vue → modal/AskAiModal.vue} +26 -34
  131. package/src/components/modal/modal/AutoAnnotateModal.vue +136 -0
  132. package/src/components/modal/modal/CloseViewerModal.vue +108 -0
  133. package/src/components/modal/modal/DataOverlayGeneralModal.vue +122 -0
  134. package/src/components/modal/modal/DataOverlayMeasurementModal.vue +59 -0
  135. package/src/components/modal/modal/DataOverlayModal.vue +121 -0
  136. package/src/components/modal/modal/ShareModal.vue +201 -0
  137. package/src/components/modal/modal/UploadsDrawer.vue +165 -0
  138. package/src/components/modal/modal/WhatsNewModal.vue +45 -0
  139. package/src/components/modal/modal/__tests__/AboutModal.spec.ts +60 -0
  140. package/src/components/modal/modal/__tests__/AiModelList.spec.ts +50 -0
  141. package/src/components/modal/{__tests__ → modal/__tests__}/AskAI.spec.ts +8 -7
  142. package/src/components/modal/modal/__tests__/AutoAnnotateModal.spec.ts +29 -0
  143. package/src/components/modal/modal/__tests__/CloseViewerModal.spec.ts +60 -0
  144. package/src/components/modal/modal/__tests__/DataOverlayGeneralModal.spec.ts +55 -0
  145. package/src/components/modal/modal/__tests__/DataOverlayMeasurementModal.spec.ts +82 -0
  146. package/src/components/modal/modal/__tests__/DataOverlayModal.spec.ts +94 -0
  147. package/src/components/modal/modal/__tests__/ShareModal.spec.ts +262 -0
  148. package/src/components/modal/modal/__tests__/UploadsDrawer.spec.ts +174 -0
  149. package/src/components/modal/modal/__tests__/WhatsNewModal.spec.ts +29 -0
  150. package/src/components/modal/snackbars/AnnotationGeneratedSnackbar.vue +34 -0
  151. package/src/components/modal/snackbars/ImagesSharedSnackbar.vue +26 -0
  152. package/src/components/modal/snackbars/LinkCopiedSnackbar.vue +26 -0
  153. package/src/components/modal/snackbars/__tests__/AnnotationGeneratedSnackbar.spec.ts +43 -0
  154. package/src/components/modal/snackbars/__tests__/ImagesSharedSnackbar.spec.ts +22 -0
  155. package/src/components/modal/snackbars/__tests__/LinkCopiedSnackbar.spec.ts +21 -0
  156. package/src/components/navigation/annotation/AnnotationCreateModal.vue +114 -0
  157. package/src/components/navigation/annotation/AnnotationCreateSnackbar.vue +20 -0
  158. package/src/components/navigation/annotation/AnnotationDataTable.vue +157 -0
  159. package/src/components/navigation/annotation/AnnotationDeleteModal.vue +48 -0
  160. package/src/components/navigation/annotation/AnnotationDeleteSnackbar.vue +25 -0
  161. package/src/components/navigation/annotation/AnnotationForm.vue +277 -0
  162. package/src/components/navigation/annotation/AnnotationIcon.vue +45 -0
  163. package/src/components/navigation/annotation/AnnotationInfo.vue +141 -0
  164. package/src/components/navigation/annotation/AnnotationMenu.vue +99 -0
  165. package/src/components/navigation/annotation/AnnotationPanel.vue +30 -0
  166. package/src/components/navigation/annotation/AnnotationTools.vue +104 -0
  167. package/src/components/navigation/annotation/AnnotationUpdateModal.vue +78 -0
  168. package/src/components/navigation/annotation/AnnotationVisibilityBtn.vue +52 -0
  169. package/src/components/navigation/annotation/DrawerTitle.vue +25 -0
  170. package/src/components/navigation/annotation/__tests__/AnnotationCreateModal.spec.ts +178 -0
  171. package/src/components/navigation/annotation/__tests__/AnnotationCreateSnackbar.spec.ts +31 -0
  172. package/src/components/navigation/annotation/__tests__/AnnotationDataTable.spec.ts +79 -0
  173. package/src/components/navigation/annotation/__tests__/AnnotationDeleteSnackbar.spec.ts +66 -0
  174. package/src/components/navigation/annotation/__tests__/AnnotationForm.spec.ts +261 -0
  175. package/src/components/navigation/annotation/__tests__/AnnotationIcon.spec.ts +13 -0
  176. package/src/components/navigation/annotation/__tests__/AnnotationTools.spec.ts +116 -0
  177. package/src/components/navigation/display/DisplayPanel.vue +156 -0
  178. package/src/components/{views/__tests__/DisplaySettings.spec.ts → navigation/display/__tests__/DisplayPanel.spec.ts} +14 -3
  179. package/src/components/navigation/history/HistoryModal.vue +140 -0
  180. package/src/components/navigation/mcad/McadAddObjectBtn.vue +41 -0
  181. package/src/components/navigation/mcad/McadDataTable.vue +78 -0
  182. package/src/components/navigation/mcad/McadDataTableItems.vue +382 -0
  183. package/src/components/navigation/mcad/McadDeleteModal.vue +44 -0
  184. package/src/components/navigation/mcad/McadDropZone.vue +148 -0
  185. package/src/components/navigation/mcad/McadFloating.vue +193 -0
  186. package/src/components/navigation/mcad/McadIcon.vue +34 -0
  187. package/src/components/navigation/mcad/McadInvert.vue +91 -0
  188. package/src/components/navigation/mcad/McadLockBtn.vue +62 -0
  189. package/src/components/navigation/mcad/McadMenu.vue +248 -0
  190. package/src/components/navigation/mcad/McadOpacity.vue +79 -0
  191. package/src/components/navigation/mcad/McadPanel.vue +31 -0
  192. package/src/components/navigation/mcad/McadToggleSortBtn.vue +18 -0
  193. package/src/components/navigation/mcad/McadToggleVisibilityBtn.vue +46 -0
  194. package/src/components/navigation/mcad/McadUpdateModal.vue +108 -0
  195. package/src/components/navigation/mcad/McadVisibilityBtn.vue +46 -0
  196. package/src/components/navigation/mcad/__tests__/McadAddObjectBtn.spec.ts +73 -0
  197. package/src/components/navigation/mcad/__tests__/McadDataTable.spec.ts +27 -0
  198. package/src/components/navigation/mcad/__tests__/McadDataTableItems.spec.ts +621 -0
  199. package/src/components/navigation/mcad/__tests__/McadDeleteModal.spec.ts +67 -0
  200. package/src/components/navigation/mcad/__tests__/McadDropZone.spec.ts +109 -0
  201. package/src/components/navigation/mcad/__tests__/McadIcon.spec.ts +13 -0
  202. package/src/components/navigation/mcad/__tests__/McadInvert.spec.ts +45 -0
  203. package/src/components/navigation/mcad/__tests__/McadLockBtn.spec.ts +38 -0
  204. package/src/components/navigation/mcad/__tests__/McadMenu.spec.ts +258 -0
  205. package/src/components/navigation/mcad/__tests__/McadOpacity.spec.ts +68 -0
  206. package/src/components/navigation/mcad/__tests__/McadToggleSortBtn.spec.ts +36 -0
  207. package/src/components/navigation/mcad/__tests__/McadToggleVisibilityBtn.spec.ts +53 -0
  208. package/src/components/navigation/mcad/__tests__/McadUpdateModal.spec.ts +122 -0
  209. package/src/components/navigation/mcad/__tests__/McadVisibilityBtn.spec.ts +45 -0
  210. package/src/components/navigation/measurement/MeasurementAngleSnackbar.vue +18 -0
  211. package/src/components/navigation/measurement/MeasurementDataTable.vue +162 -0
  212. package/src/components/navigation/measurement/MeasurementDeleteModal.vue +49 -0
  213. package/src/components/navigation/measurement/MeasurementDeleteSnackbar.vue +40 -0
  214. package/src/components/navigation/measurement/MeasurementIcon.vue +41 -0
  215. package/src/components/navigation/measurement/MeasurementInfo.vue +25 -0
  216. package/src/components/navigation/measurement/MeasurementInfoAngle.vue +36 -0
  217. package/src/components/navigation/measurement/MeasurementInfoLength.vue +37 -0
  218. package/src/components/navigation/measurement/MeasurementInfoPolygon.vue +58 -0
  219. package/src/components/navigation/measurement/MeasurementLengthSnackbar.vue +18 -0
  220. package/src/components/navigation/measurement/MeasurementMenu.vue +97 -0
  221. package/src/components/navigation/measurement/MeasurementPanel.vue +30 -0
  222. package/src/components/navigation/measurement/MeasurementPolygonSnackbar.vue +18 -0
  223. package/src/components/navigation/measurement/MeasurementTools.vue +101 -0
  224. package/src/components/navigation/measurement/MeasurementVisibilityBtn.vue +53 -0
  225. package/src/components/navigation/measurement/__tests__/MeasurementAngleSnackbar.spec.ts +34 -0
  226. package/src/components/navigation/measurement/__tests__/MeasurementDataTable.spec.ts +79 -0
  227. package/src/components/navigation/measurement/__tests__/MeasurementDeleteSnackbar.spec.ts +51 -0
  228. package/src/components/navigation/measurement/__tests__/MeasurementInfo.spec.ts +77 -0
  229. package/src/components/navigation/measurement/__tests__/MeasurementLengthSnackbar.spec.ts +35 -0
  230. package/src/components/navigation/measurement/__tests__/MeasurementPolygonSnackbar.spec.ts +35 -0
  231. package/src/components/navigation/measurement/__tests__/MeasurementTools.spec.ts +87 -0
  232. package/src/components/shared/ColourPalette.vue +72 -0
  233. package/src/components/shared/DoubleSliderSelector.vue +66 -28
  234. package/src/components/shared/HorizontalSliderSelector.vue +80 -0
  235. package/src/components/shared/KeyboardShortcuts.vue +184 -0
  236. package/src/components/shared/LoadingSpinner.vue +508 -144
  237. package/src/components/shared/MultiLoginError.vue +98 -0
  238. package/src/components/shared/NotificationToaster.vue +112 -0
  239. package/src/components/shared/UpdateSnackbar.vue +22 -9
  240. package/src/components/shared/ValueSelector.vue +10 -10
  241. package/src/components/shared/VerticalSliderSelector.vue +45 -44
  242. package/src/components/shared/__tests__/ColourPalette.spec.ts +21 -0
  243. package/src/components/shared/__tests__/DoubleSliderSelector.spec.ts +58 -46
  244. package/src/components/shared/__tests__/HorizontalSliderSelector.spec.ts +35 -0
  245. package/src/components/shared/__tests__/KeyboardShortcuts.spec.ts +50 -0
  246. package/src/components/shared/__tests__/MultiLoginError.spec.ts +50 -0
  247. package/src/components/shared/__tests__/NotificationToaster.spec.ts +65 -0
  248. package/src/components/shared/__tests__/UpdateSnackbar.spec.ts +7 -7
  249. package/src/components/shared/__tests__/ValueSelector.spec.ts +6 -0
  250. package/src/composables/__tests__/useDebounce.spec.ts +17 -0
  251. package/src/composables/__tests__/useMovingAverage.spec.ts +40 -0
  252. package/src/composables/__tests__/useNavigationCube.spec.ts +79 -0
  253. package/src/{functions/__tests__/notification.spec.ts → composables/__tests__/useNotificationHandler.spec.ts} +74 -17
  254. package/src/composables/__tests__/useToolGroup.spec.ts +41 -0
  255. package/src/composables/__tests__/useViewName.spec.ts +29 -0
  256. package/src/composables/useBoundingBox.ts +32 -0
  257. package/src/composables/useEventListener.ts +7 -9
  258. package/src/composables/useMedia.ts +27 -0
  259. package/src/composables/useNavigationCube.ts +80 -0
  260. package/src/composables/useNotificationHandler.ts +82 -0
  261. package/src/composables/useResizeObserver.ts +31 -0
  262. package/src/composables/useToolGroup.ts +36 -0
  263. package/src/composables/useViewName.ts +34 -0
  264. package/src/composables/useViewer3cr.ts +3 -2
  265. package/src/config.ts +1 -1
  266. package/src/constants.ts +8 -0
  267. package/src/functions/HotkeyScope.ts +87 -0
  268. package/src/functions/__tests__/HotkeyScope.spec.ts +49 -0
  269. package/src/functions/__tests__/colours.spec.ts +42 -0
  270. package/src/functions/__tests__/fileReader.spec.ts +130 -0
  271. package/src/functions/__tests__/modelHelper.spec.ts +1 -0
  272. package/src/functions/__tests__/tsHelper.spec.ts +7 -0
  273. package/src/functions/chunker.ts +21 -0
  274. package/src/functions/colours.ts +45 -0
  275. package/src/functions/fileReader.ts +48 -0
  276. package/src/functions/guards/__tests__/isDataOverlayAngle.spec.ts +21 -0
  277. package/src/functions/guards/__tests__/isDataOverlayLength.spec.ts +21 -0
  278. package/src/functions/guards/__tests__/isDataOverlayPolygon.spec.ts +21 -0
  279. package/src/functions/guards/isDataOverlayAngle.ts +3 -4
  280. package/src/functions/guards/isDataOverlayLength.ts +3 -4
  281. package/src/functions/guards/isDataOverlayPolygon.ts +3 -4
  282. package/src/functions/openUrl.ts +3 -0
  283. package/src/functions/{__tests__ → parsers/__tests__}/parseAction.spec.ts +1 -1
  284. package/src/functions/{__tests__ → parsers/__tests__}/parseCallToAction.spec.ts +1 -1
  285. package/src/functions/{__tests__ → parsers/__tests__}/parseDataOverlay.spec.ts +1 -1
  286. package/src/functions/{__tests__ → parsers/__tests__}/parseDataOverlayData.spec.ts +1 -1
  287. package/src/functions/{__tests__ → parsers/__tests__}/parseDataOverlayEvent.spec.ts +3 -3
  288. package/src/functions/{__tests__ → parsers/__tests__}/parseMcadEvent.spec.ts +3 -3
  289. package/src/functions/{parseAction.ts → parsers/parseAction.ts} +1 -2
  290. package/src/functions/{parseCallToAction.ts → parsers/parseCallToAction.ts} +2 -2
  291. package/src/functions/{parseDataOverlay.ts → parsers/parseDataOverlay.ts} +3 -3
  292. package/src/functions/parsers/parseDataOverlayData.ts +9 -0
  293. package/src/functions/parsers/parseDataOverlayEvent.ts +19 -0
  294. package/src/functions/parsers/parseMcadEvent.ts +9 -0
  295. package/src/functions/platform.ts +7 -0
  296. package/src/functions/translateTooltipShortcut.ts +35 -0
  297. package/src/functions/tsHelper.ts +3 -0
  298. package/src/main.ts +68 -43
  299. package/src/models/__tests__/iso-languages.spec.ts +23 -0
  300. package/src/models/__tests__/scanState.spec.ts +14 -38
  301. package/src/models/iso-languages.ts +1609 -0
  302. package/src/models/progress.ts +23 -0
  303. package/src/models/scanState.ts +56 -100
  304. package/src/plugins/__tests__/pinia.spec.ts +7 -0
  305. package/src/plugins/__tests__/vue-i18n.spec.ts +7 -0
  306. package/src/plugins/__tests__/vuetify.spec.ts +3 -3
  307. package/src/plugins/locales/en.json +442 -0
  308. package/src/plugins/material-symbols/aliases.ts +43 -0
  309. package/src/plugins/material-symbols/material-symbols-filled.ts +11 -0
  310. package/src/plugins/material-symbols/material-symbols-outlined.ts +9 -0
  311. package/src/plugins/pinia.ts +3 -0
  312. package/src/plugins/vue-i18n.ts +9 -0
  313. package/src/plugins/vuetify.ts +157 -25
  314. package/src/services/__tests__/features.service.spec.ts +14 -0
  315. package/src/services/__tests__/gpt.service.spec.ts +64 -3
  316. package/src/services/__tests__/viewer-3cr.service.spec.ts +616 -192
  317. package/src/services/features.service.ts +32 -0
  318. package/src/services/gpt.service.ts +5 -2
  319. package/src/services/viewer-3cr.service.ts +374 -307
  320. package/src/stores/__tests__/data-overlay.store.spec.ts +92 -0
  321. package/src/stores/__tests__/demo.store.spec.ts +196 -0
  322. package/src/stores/__tests__/drawer.store.spec.ts +27 -0
  323. package/src/stores/__tests__/intro.store.spec.ts +102 -0
  324. package/src/stores/__tests__/language.store.spec.ts +83 -0
  325. package/src/stores/__tests__/mcad-floating.store.spec.ts +339 -0
  326. package/src/stores/__tests__/mcad.store.spec.ts +88 -0
  327. package/src/stores/__tests__/save.store.spec.ts +51 -0
  328. package/src/stores/__tests__/theme.store.spec.ts +18 -0
  329. package/src/stores/__tests__/tool.store.spec.ts +53 -0
  330. package/src/stores/__tests__/upload.store.spec.ts +19 -0
  331. package/src/stores/__tests__/viewer.store.spec.ts +20 -0
  332. package/src/stores/data-overlay.store.ts +93 -0
  333. package/src/stores/demo.store.ts +284 -0
  334. package/src/stores/drawer.store.ts +23 -0
  335. package/src/stores/intro.store.ts +35 -0
  336. package/src/stores/language.store.ts +59 -0
  337. package/src/stores/mcad-floating.store.ts +141 -0
  338. package/src/stores/mcad.store.ts +53 -0
  339. package/src/stores/save.store.ts +59 -0
  340. package/src/stores/theme.store.ts +45 -0
  341. package/src/stores/tool.store.ts +41 -0
  342. package/src/stores/upload.store.ts +19 -0
  343. package/src/stores/viewer.store.ts +34 -0
  344. package/src/tools/__tests__/annotation-create.tool.spec.ts +90 -0
  345. package/src/tools/__tests__/annotation-move.tool.spec.ts +107 -0
  346. package/src/tools/__tests__/data-overlay.tool.spec.ts +127 -0
  347. package/src/tools/__tests__/mcad-move.tool.spec.ts +106 -0
  348. package/src/tools/__tests__/mcad-rotate.tool.spec.ts +103 -0
  349. package/src/tools/__tests__/mcad-scale.tool.spec.ts +8 -0
  350. package/src/tools/__tests__/mcad-select.tool.spec.ts +63 -0
  351. package/src/tools/__tests__/measurement-angle.tool.spec.ts +26 -0
  352. package/src/tools/__tests__/measurement-length.tool.spec.ts +26 -0
  353. package/src/tools/__tests__/measurement-polygon.tool.spec.ts +26 -0
  354. package/src/tools/annotation-create.tool.ts +48 -0
  355. package/src/tools/annotation-move.tool.ts +48 -0
  356. package/src/tools/data-overlay.tool.ts +45 -48
  357. package/src/tools/mcad-move.tool.ts +47 -0
  358. package/src/tools/mcad-rotate.tool.ts +47 -0
  359. package/src/tools/mcad-scale-tool.ts +10 -0
  360. package/src/tools/mcad-select-tool.ts +32 -0
  361. package/src/tools/measurement-angle.tool.ts +20 -0
  362. package/src/tools/measurement-length.tool.ts +20 -0
  363. package/src/tools/measurement-polygon.tool.ts +20 -0
  364. package/src/types/feature.ts +6 -0
  365. package/src/types/viewer-tool.ts +5 -0
  366. package/test/MockImportOriginal.ts +7 -0
  367. package/test/assertions/beTypeOrNull.ts +24 -0
  368. package/test/fakers/action-data.faker.ts +1 -1
  369. package/test/fakers/action.faker.ts +1 -1
  370. package/test/fakers/call-to-action.faker.ts +1 -1
  371. package/test/fakers/data-overlay-angle.faker.ts +1 -1
  372. package/test/fakers/data-overlay-annotation.faker.ts +1 -1
  373. package/test/fakers/data-overlay-data.faker.ts +18 -15
  374. package/test/fakers/data-overlay-interaction.faker.ts +25 -17
  375. package/test/fakers/data-overlay-length.faker.ts +1 -1
  376. package/test/fakers/data-overlay-mcad.faker.ts +4 -3
  377. package/test/fakers/data-overlay-polygon.faker.ts +1 -1
  378. package/test/fakers/feature.faker.ts +13 -0
  379. package/test/fakers/file.faker.ts +11 -0
  380. package/test/fakers/gpt-question.faker.ts +1 -1
  381. package/test/fakers/gpt-response.faker.ts +1 -1
  382. package/test/fakers/mcad-object-interaction.faker.ts +4 -4
  383. package/test/fakers/orientation.faker.ts +1 -1
  384. package/test/fakers/segment-angle.faker.ts +1 -1
  385. package/test/fakers/upload.faker.ts +14 -0
  386. package/test/getAnimationFrame.ts +3 -0
  387. package/test/plugins/findByTestId.ts +13 -6
  388. package/test/plugins/pinia.ts +20 -0
  389. package/test/plugins/vitest.d.ts +8 -0
  390. package/test/plugins/vue-i18n.ts +22 -0
  391. package/test/plugins/vuetify.ts +10 -0
  392. package/test/setup.ts +37 -12
  393. package/tsconfig.json +1 -1
  394. package/vite.config.mts +48 -18
  395. package/vitest.config.mts +12 -3
  396. package/src/assets/images/MainBackdrop.svg +0 -48
  397. package/src/assets/images/dark/Singular-Health-Disc-Mono.svg +0 -9
  398. package/src/assets/images/dark/demo-icon-small.png +0 -0
  399. package/src/assets/images/dark/demo-logo-small.svg +0 -27
  400. package/src/assets/images/dark/demo-logo.svg +0 -1
  401. package/src/assets/images/light/Singular-Health-Disc-Mono.svg +0 -9
  402. package/src/assets/logos/3dicom/white-mini.svg +0 -20
  403. package/src/components/demo/DemoModal.vue +0 -44
  404. package/src/components/demo/DemoPatientModal.vue +0 -52
  405. package/src/components/demo/__tests__/DemoModal.spec.ts +0 -26
  406. package/src/components/demo/__tests__/DemoPatientModal.spec.ts +0 -38
  407. package/src/components/demo/__tests__/options.spec.ts +0 -25
  408. package/src/components/demo/licence/DemoLicenceEnableCloudStorageModal.vue +0 -64
  409. package/src/components/demo/licence/DemoLicenceInfoModal.vue +0 -49
  410. package/src/components/demo/licence/DemoLicenceSendToPartyModal.vue +0 -178
  411. package/src/components/demo/licence/DemoLicenceShareToMobileModal.vue +0 -102
  412. package/src/components/demo/licence/__tests__/DemoLicenceSendToPartyModal.spec.ts +0 -36
  413. package/src/components/demo/licence/__tests__/DemoLicenceShareToMobileModal.spec.ts +0 -52
  414. package/src/components/demo/options.ts +0 -170
  415. package/src/components/demo/patient/DemoPatientEnableCloudStorageModal.vue +0 -64
  416. package/src/components/demo/patient/DemoPatientInfoModal.vue +0 -49
  417. package/src/components/demo/patient/DemoPatientSendToPartyModal.vue +0 -177
  418. package/src/components/demo/patient/DemoPatientShareToMobileModal.vue +0 -102
  419. package/src/components/demo/patient/__tests__/DemoPatientEnableCloudStorageModal.spec.ts +0 -38
  420. package/src/components/demo/patient/__tests__/DemoPatientInfoModal.spec.ts +0 -38
  421. package/src/components/demo/patient/__tests__/DemoPatientSendToPartyModal.spec.ts +0 -36
  422. package/src/components/demo/patient/__tests__/DemoPatientShareToMobileModal.spec.ts +0 -52
  423. package/src/components/modal/CloseViewerModal.vue +0 -69
  424. package/src/components/modal/MftpWebGL3DRModal.vue +0 -336
  425. package/src/components/modal/ViewerNavigationDrawerContent.vue +0 -168
  426. package/src/components/modal/ViewerNavigationDrawerFooter.vue +0 -131
  427. package/src/components/modal/ViewerNavigationDrawerHeader.vue +0 -33
  428. package/src/components/modal/WebGL3DR.vue +0 -91
  429. package/src/components/modal/__tests__/CloseViewerModal.spec.ts +0 -70
  430. package/src/components/modal/__tests__/ViewerNavigationDrawerContent.spec.ts +0 -11
  431. package/src/components/modal/__tests__/ViewerNavigationDrawerFooter.spec.ts +0 -46
  432. package/src/components/modal/__tests__/ViewerNavigationDrawerHeader.spec.ts +0 -40
  433. package/src/components/modal/actions/NavigationCubeAction.vue +0 -26
  434. package/src/components/modal/actions/__tests__/NavigationCubeAction.spec.ts +0 -25
  435. package/src/components/modal/buttons/AutoAnnotateBtn.vue +0 -197
  436. package/src/components/modal/buttons/__tests__/AutoAnnotateBtn.spec.ts +0 -28
  437. package/src/components/modal/menus/FileMenu.vue +0 -73
  438. package/src/components/modal/menus/__tests__/FileMenu.spec.ts +0 -110
  439. package/src/components/navigation/mcad/McadGlobalActions.vue +0 -20
  440. package/src/components/navigation/mcad/McadGlobalOpacitySlider.vue +0 -103
  441. package/src/components/navigation/mcad/McadGlobalScanViewBtn.vue +0 -28
  442. package/src/components/navigation/mcad/McadGlobalVisibilityBtn.vue +0 -38
  443. package/src/components/views/AnnotationTreeView.vue +0 -236
  444. package/src/components/views/DisplaySettings.vue +0 -94
  445. package/src/components/views/MarkupTreeView.vue +0 -201
  446. package/src/components/views/McadObjectTreeView.vue +0 -129
  447. package/src/components/views/__tests__/AnnotationTreeView.spec.ts +0 -62
  448. package/src/components/views/__tests__/MarkupTreeView.spec.ts +0 -41
  449. package/src/components/views/__tests__/McadObjectTreeView.spec.ts +0 -47
  450. package/src/components/views/modals/DataOverlayGeneralModal.vue +0 -71
  451. package/src/components/views/modals/DataOverlayMarkupModal.vue +0 -60
  452. package/src/components/views/modals/DataOverlayModal.vue +0 -88
  453. package/src/components/views/shared/MaskIcon.vue +0 -44
  454. package/src/components/views/shared/ObjectColor.vue +0 -31
  455. package/src/components/views/shared/ObjectLabel.vue +0 -30
  456. package/src/components/views/shared/Opacity.vue +0 -65
  457. package/src/components/views/shared/VisibilityBtn.vue +0 -25
  458. package/src/components/views/shared/__tests__/MaskIcon.spec.ts +0 -10
  459. package/src/components/views/shared/__tests__/ObjectColor.spec.ts +0 -10
  460. package/src/components/views/shared/__tests__/ObjectLabel.spec.ts +0 -10
  461. package/src/components/views/shared/__tests__/Opacity.spec.ts +0 -24
  462. package/src/components/views/shared/__tests__/VisibilityBtn.spec.ts +0 -11
  463. package/src/components/views/types/annotation-tree-view-item-child-action.ts +0 -6
  464. package/src/components/views/types/annotation-tree-view-item-child.ts +0 -9
  465. package/src/components/views/types/annotation-tree-view-item.ts +0 -13
  466. package/src/components/views/types/markup-tree-view-item-child.ts +0 -6
  467. package/src/components/views/types/markup-tree-view-item.ts +0 -14
  468. package/src/components/views/types/mcad-object-tree-view-item.ts +0 -13
  469. package/src/composables/__tests__/useAnnotations.spec.ts +0 -35
  470. package/src/composables/__tests__/useIntroJs.spec.ts +0 -51
  471. package/src/composables/__tests__/useMarkups.spec.ts +0 -39
  472. package/src/composables/__tests__/useMcadObjects.spec.ts +0 -36
  473. package/src/composables/__tests__/useNavigationCubeObserver.spec.ts +0 -61
  474. package/src/composables/useAnnotations.ts +0 -37
  475. package/src/composables/useIntroJs.ts +0 -142
  476. package/src/composables/useMarkups.ts +0 -41
  477. package/src/composables/useMcadObjects.ts +0 -32
  478. package/src/composables/useNavigationCubeObserver.ts +0 -114
  479. package/src/composables/useVersion3cr.ts +0 -7
  480. package/src/functions/__tests__/layoutOverlayStyle.spec.ts +0 -325
  481. package/src/functions/layoutOverlayStyle.ts +0 -84
  482. package/src/functions/notification.ts +0 -82
  483. package/src/functions/parseDataOverlayData.ts +0 -10
  484. package/src/functions/parseDataOverlayEvent.ts +0 -17
  485. package/src/functions/parseMcadEvent.ts +0 -10
  486. package/src/functions/rgbaToCss.ts +0 -13
  487. package/src/models/__tests__/loadViewerOptions.spec.ts +0 -72
  488. package/src/models/__tests__/loadViewerPayload.spec.ts +0 -10
  489. package/src/models/callbacks.ts +0 -4
  490. package/src/models/loadViewerOptions.ts +0 -79
  491. package/src/models/loadViewerPayload.ts +0 -17
  492. package/src/plugins/__tests__/usePlugins.spec.ts +0 -12
  493. package/src/plugins/usePlugins.ts +0 -8
  494. package/src/types/action-data.ts +0 -4
  495. package/src/types/action.ts +0 -13
  496. package/src/types/call-to-action.ts +0 -9
  497. package/src/types/colour.ts +0 -6
  498. package/src/types/data-overlay-angle.ts +0 -16
  499. package/src/types/data-overlay-annotation.ts +0 -18
  500. package/src/types/data-overlay-data.ts +0 -15
  501. package/src/types/data-overlay-event.ts +0 -5
  502. package/src/types/data-overlay-info.ts +0 -18
  503. package/src/types/data-overlay-interaction.ts +0 -15
  504. package/src/types/data-overlay-length.ts +0 -16
  505. package/src/types/data-overlay-markup.ts +0 -7
  506. package/src/types/data-overlay-mcad.ts +0 -20
  507. package/src/types/data-overlay-polygon.ts +0 -18
  508. package/src/types/data-overlay.ts +0 -16
  509. package/src/types/demo-type.ts +0 -4
  510. package/src/types/gpt-question.ts +0 -4
  511. package/src/types/gpt-response-payload.ts +0 -6
  512. package/src/types/mcad-object-interaction.ts +0 -15
  513. package/src/types/segment-angle.ts +0 -5
  514. package/src/types/vector2.ts +0 -4
  515. package/src/types/vector3.ts +0 -5
  516. package/test/fakers/data-overlay-info.faker.ts +0 -29
  517. /package/src/assets/logos/3dicom/{white.svg → white/white.svg} +0 -0
  518. /package/src/assets/{images/light/Singular-Health-Trademark-mono.svg → logos/singular-health/black/black.svg} +0 -0
  519. /package/src/assets/{images/dark/Singular-Health-Trademark-mono.svg → logos/singular-health/white/white.svg} +0 -0
@@ -0,0 +1,27 @@
1
+ import { ref, watchEffect } from 'vue';
2
+
3
+ export function useMedia(query: string) {
4
+ const matches = ref<boolean>(false);
5
+
6
+ watchEffect((onInvalidate) => {
7
+ const media = window.matchMedia(query);
8
+
9
+ if (media.matches !== matches.value) {
10
+ matches.value = media.matches;
11
+ }
12
+
13
+ /* v8 ignore start -- @preserve */
14
+ const onChange = () => {
15
+ matches.value = media.matches;
16
+ };
17
+
18
+ media.addEventListener('change', onChange);
19
+
20
+ onInvalidate(() => {
21
+ media.removeEventListener('change', onChange);
22
+ });
23
+ /* v8 ignore stop -- @preserve */
24
+ });
25
+
26
+ return matches;
27
+ }
@@ -0,0 +1,80 @@
1
+ import { fullscreenView, isFullscreen, isLayout1x3, isLayout2x2 } from '@/models/scanState';
2
+ import { AnchorPoint, InteractivityState, NavigationCubeTransform, ScanView } from '@3cr/types-ts';
3
+ import { useViewer3cr } from '@/composables/useViewer3cr';
4
+ import { watch, ref, MaybeRef, computed } from 'vue';
5
+ import { useBoundingBox } from '@/composables/useBoundingBox';
6
+
7
+ // TODO: possible move to store?
8
+ export const isVisible = ref<boolean>(true);
9
+ export const navCubeBoundingBox = ref({ x: 0, y: 0, width: 0, height: 0 });
10
+
11
+ export function useNavigationCube(canvas: MaybeRef<HTMLCanvasElement | null | undefined>) {
12
+ const viewer3cr = useViewer3cr();
13
+ const { width: cwidth, height: cheight } = useBoundingBox(canvas);
14
+
15
+ const canvasWidth = computed(() => cwidth.value * window.devicePixelRatio);
16
+ const canvasHeight = computed(() => cheight.value * window.devicePixelRatio);
17
+
18
+ watch([isVisible, isFullscreen, fullscreenView], async ([isVisible, isFullscreen, fullscreenView]) => {
19
+ if (!isFullscreen || fullscreenView === ScanView.Volume) {
20
+ const message: InteractivityState = { Version: '0.0.0', Value: isVisible };
21
+ await viewer3cr.setNavCubeVisibility({ message });
22
+ } else {
23
+ // Disable navigation cube when fullscreen on a 2D view
24
+ const message: InteractivityState = { Version: '0.0.0', Value: false };
25
+ await viewer3cr.setNavCubeVisibility({ message });
26
+ }
27
+ });
28
+
29
+ watch(navCubeBoundingBox, async (boundingBox) => {
30
+ const message: NavigationCubeTransform = {
31
+ Version: '0.0.0',
32
+ AnchorPoint: AnchorPoint.TOP_LEFT,
33
+ Position: { Version: '0.0.0', X: boundingBox.x, Y: boundingBox.y },
34
+ Size: { Version: '0.0.0', X: boundingBox.width, Y: boundingBox.height }
35
+ };
36
+ await viewer3cr.setNavCubePositionSize({ message });
37
+ });
38
+
39
+ watch(
40
+ [isFullscreen, canvasWidth, canvasHeight],
41
+ ([isFullscreen, canvasWidth, canvasHeight]) => {
42
+ if (isFullscreen) {
43
+ const width = canvasWidth * 0.1;
44
+ const height = canvasWidth * 0.1; // Proportional to width so cube is always square
45
+ const x = canvasWidth - width;
46
+ const y = canvasHeight - height;
47
+ navCubeBoundingBox.value = { x, y, width, height };
48
+ }
49
+ },
50
+ { immediate: true }
51
+ );
52
+
53
+ watch(
54
+ [isLayout2x2, isFullscreen, canvasWidth, canvasHeight],
55
+ ([isLayout2x2, isFullscreen, canvasWidth, canvasHeight]) => {
56
+ if (isLayout2x2 && !isFullscreen) {
57
+ const width = canvasWidth * 0.1;
58
+ const height = canvasWidth * 0.1; // Proportional to width so cube is always square
59
+ const x = Math.floor(canvasWidth / 2 - width);
60
+ const y = Math.floor(canvasHeight / 2 - height);
61
+ navCubeBoundingBox.value = { x, y, width, height };
62
+ }
63
+ },
64
+ { immediate: true }
65
+ );
66
+
67
+ watch(
68
+ [isLayout1x3, isFullscreen, canvasWidth, canvasHeight],
69
+ ([isLayout1x3, isFullscreen, canvasWidth, canvasHeight]) => {
70
+ if (isLayout1x3 && !isFullscreen) {
71
+ const width = canvasWidth * 0.1;
72
+ const height = canvasWidth * 0.1; // Proportional to width so cube is always square
73
+ const x = Math.floor(canvasWidth - canvasHeight / 3 - width);
74
+ const y = canvasHeight - height;
75
+ navCubeBoundingBox.value = { x, y, width, height };
76
+ }
77
+ },
78
+ { immediate: true }
79
+ );
80
+ }
@@ -0,0 +1,82 @@
1
+ import { useNotification } from '@kyvg/vue3-notification';
2
+ import { useI18n } from 'vue-i18n';
3
+ import { InteractivityActions, NavigationCubeActions, NotificationPayload, NotificationsActions } from '@3cr/types-ts';
4
+ import { t as t3 } from '@3cr/translations-ts';
5
+
6
+ type NotificationType = 'success' | 'info' | 'warn' | 'error';
7
+
8
+ export function useNotificationHandler() {
9
+ const { t } = useI18n();
10
+ const { notify } = useNotification();
11
+
12
+ function getNotificationText(notification: NotificationPayload): string {
13
+ return t3(notification.Code, {});
14
+ }
15
+
16
+ function getNotificationTitle(action: NotificationsActions): string {
17
+ switch (action) {
18
+ case NotificationsActions.no01:
19
+ return t('enums.notificationType.success');
20
+ case NotificationsActions.no02:
21
+ return t('enums.notificationType.error');
22
+ case NotificationsActions.no03:
23
+ return t('enums.notificationType.warning');
24
+ default:
25
+ case NotificationsActions.no04:
26
+ return t('enums.notificationType.info');
27
+ }
28
+ }
29
+
30
+ function getNotificationType(action: NotificationsActions): NotificationType {
31
+ switch (action) {
32
+ case NotificationsActions.no01:
33
+ return 'success';
34
+ case NotificationsActions.no02:
35
+ return 'error';
36
+ case NotificationsActions.no03:
37
+ return 'warn';
38
+ default:
39
+ case NotificationsActions.no04:
40
+ return 'info';
41
+ }
42
+ }
43
+
44
+ function shouldDisplayNotification(action: NotificationsActions, notification: NotificationPayload): boolean {
45
+ // Hide type specific messages
46
+ const hiddenTypes = [NotificationsActions.no01, NotificationsActions.no04];
47
+ if (hiddenTypes.includes(action)) {
48
+ return false;
49
+ }
50
+
51
+ // Hide all slider actions
52
+ if (notification.Action.startsWith('sl')) {
53
+ return false;
54
+ }
55
+
56
+ // Hide action specific messages
57
+ const hiddenActions = [
58
+ InteractivityActions.in01,
59
+ InteractivityActions.in02,
60
+ InteractivityActions.in03,
61
+ InteractivityActions.in04,
62
+ NavigationCubeActions.nc01
63
+ ] as string[];
64
+
65
+ return !hiddenActions.includes(notification.Action);
66
+ }
67
+
68
+ async function handler(action: NotificationsActions, message: string) {
69
+ const notification = JSON.parse(message) as NotificationPayload;
70
+ if (shouldDisplayNotification(action, notification)) {
71
+ await announce(action, notification);
72
+ }
73
+ }
74
+ async function announce(action: NotificationsActions, notification: NotificationPayload) {
75
+ const title = getNotificationTitle(action);
76
+ const text = getNotificationText(notification);
77
+ const type = getNotificationType(action);
78
+ notify({ title, text, type });
79
+ }
80
+
81
+ return { handler, announce };
82
+ }
@@ -0,0 +1,31 @@
1
+ import { MaybeRef, onBeforeUnmount, unref, watch } from 'vue';
2
+
3
+ export type ResizeCallback = (entries: ResizeObserverEntry[]) => void;
4
+
5
+ /**
6
+ * Observes resize events for elements.
7
+ * @param element The element to observe.
8
+ * @param callback The resize callback.
9
+ */
10
+ export function useResizeObserver(element: MaybeRef<Element | null | undefined>, callback: ResizeCallback) {
11
+ const observer = new ResizeObserver(callback);
12
+
13
+ onBeforeUnmount(() => {
14
+ observer?.disconnect();
15
+ });
16
+
17
+ watch(
18
+ () => unref(element),
19
+ (next, prev) => {
20
+ /* v8 ignore start -- @preserve */
21
+ if (prev instanceof Element) {
22
+ observer?.unobserve(prev);
23
+ }
24
+ if (next instanceof Element) {
25
+ observer?.observe(next);
26
+ }
27
+ /* v8 ignore stop -- @preserve */
28
+ },
29
+ { immediate: true }
30
+ );
31
+ }
@@ -0,0 +1,36 @@
1
+ import { ViewerTool } from '@/types/viewer-tool';
2
+ import { computed } from 'vue';
3
+
4
+ type Tool<T extends ViewerTool[]> = T extends ViewerTool[] ? T[number] : never;
5
+
6
+ /**
7
+ * Create a tool group where only one tool can be active at a time.
8
+ * @param tools The tools.
9
+ */
10
+ export function useToolGroup<T extends ViewerTool[]>(...tools: T) {
11
+ const activeTool = computed({
12
+ get(): Tool<T> | null {
13
+ const idx = tools.findIndex((tool) => tool.isActive.value);
14
+ return idx !== -1 ? (tools[idx] as Tool<T>) : null;
15
+ },
16
+ set(tool: Tool<T> | null): void {
17
+ const prev = tools.find((t) => t.isActive.value);
18
+ if (prev) prev.isActive.value = false;
19
+ const next = tools.find((t) => t === tool);
20
+ if (next) next.isActive.value = true;
21
+ }
22
+ });
23
+
24
+ const activeIndex = computed(() => {
25
+ const idx = tools.findIndex((tool) => tool.isActive.value);
26
+ return idx !== -1 ? idx : null;
27
+ });
28
+
29
+ function toggleTool(tool: Tool<T>): boolean {
30
+ const state = activeTool.value !== tool;
31
+ activeTool.value = state ? tool : null;
32
+ return state;
33
+ }
34
+
35
+ return { activeTool, activeIndex, toggleTool };
36
+ }
@@ -0,0 +1,34 @@
1
+ import { ScanView } from '@3cr/types-ts';
2
+ import { MaybeRef, ref, unref, watch } from 'vue';
3
+ import { useI18n } from 'vue-i18n';
4
+
5
+ export function useViewName(view: MaybeRef<ScanView>) {
6
+ const { t } = useI18n();
7
+
8
+ const name = ref<string>();
9
+
10
+ function getViewName(view: ScanView): string {
11
+ switch (view) {
12
+ case ScanView.Volume:
13
+ return t('enums.scanView.volume');
14
+ case ScanView.Transverse:
15
+ return t('enums.scanView.transverse');
16
+ case ScanView.Sagittal:
17
+ return t('enums.scanView.sagittal');
18
+ case ScanView.Coronal:
19
+ return t('enums.scanView.coronal');
20
+ default:
21
+ return '';
22
+ }
23
+ }
24
+
25
+ watch(
26
+ () => unref(view),
27
+ (value: ScanView) => {
28
+ name.value = getViewName(value);
29
+ },
30
+ { immediate: true }
31
+ );
32
+
33
+ return { name };
34
+ }
@@ -1,6 +1,7 @@
1
- import { Viewer3crService } from '@/services/viewer-3cr.service';
1
+ import { Viewer3crServiceImpl } from '@/services/viewer-3cr.service';
2
+ import { Viewer3crService } from '@3cr/viewer-types-ts';
2
3
 
3
- const viewer3cr = new Viewer3crService();
4
+ const viewer3cr = new Viewer3crServiceImpl();
4
5
 
5
6
  export function useViewer3cr(): Viewer3crService {
6
7
  return viewer3cr;
package/src/config.ts CHANGED
@@ -1 +1 @@
1
- export const injectedStyleId = "__3cr-viewer-injected-style__";
1
+ export const injectedStyleId = '__3cr-viewer-injected-style__';
@@ -0,0 +1,8 @@
1
+ export const PRICING_URL = 'https://3dicomviewer.com/pricing';
2
+
3
+ export const SUPPORT_URL = 'https://3dicomviewer.com/contact-3dicom';
4
+
5
+ export const DEMO_BUCKET_URL = 'https://webgl-3dr.singular.health/demo';
6
+
7
+ export const EMAIL_REGEX =
8
+ /^(([^<>()[\]\\.,;:\s@']+(\.[^<>()\\[\]\\.,;:\s@']+)*)|('.+'))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
@@ -0,0 +1,87 @@
1
+ import hotkeys, { HotkeysEvent } from 'hotkeys-js';
2
+ import { isMac } from '@/functions/platform';
3
+ import { MaybePromise } from 'rollup';
4
+
5
+ export enum HotkeyScope {
6
+ MCAD_MENU = 'mcad_menu',
7
+ PRIMARY = 'primary',
8
+ MEASUREMENT_MENU = 'measurement_menu',
9
+ ANNOTATION_MENU = 'annotation_menu',
10
+ ANNOTATION_TOOLS = 'annotation_tools',
11
+ MEASUREMENT_TOOLS = 'measurement_tools'
12
+ // NAVIGATION_DRAWER = 'navigation_drawer'
13
+ }
14
+
15
+ // Always Windows keys. Mac keys will be generated at runtime.
16
+ export enum Hotkeys {
17
+ CTRL_SHIFT_A = 'ctrl+shift+a',
18
+ CTRL_SHIFT_C = 'ctrl+shift+c',
19
+ CTRL_SHIFT_D = 'ctrl+shift+d',
20
+ CTRL_SHIFT_DEL = 'ctrl+shift+del',
21
+ CTRL_SHIFT_E = 'ctrl+shift+e',
22
+ CTRL_SHIFT_F = 'ctrl+shift+f',
23
+ CTRL_SHIFT_G = 'ctrl+shift+g',
24
+ CTRL_SHIFT_H = 'ctrl+shift+h',
25
+ CTRL_SHIFT_L = 'ctrl+shift+l',
26
+ CTRL_SHIFT_M = 'ctrl+shift+m',
27
+ CTRL_SHIFT_P = 'ctrl+shift+p',
28
+ CTRL_SHIFT_Q = 'ctrl+shift+q',
29
+ CTRL_SHIFT_R = 'ctrl+shift+r',
30
+ CTRL_SHIFT_S = 'ctrl+shift+s',
31
+ CTRL_SHIFT_V = 'ctrl+shift+v',
32
+ CTRL_SHIFT_PLUS = 'ctrl+shift++',
33
+ CTRL_SHIFT_MINUS = 'ctrl+shift+-',
34
+ CTRL_SHIFT_SPACE = 'ctrl+shift+space',
35
+ ALT_A = 'alt+a',
36
+ ALT_H = 'alt+h',
37
+ ALT_D = 'alt+d',
38
+ ALT_M = 'alt+m',
39
+ ALT_E = 'alt+e',
40
+ ALT_F = 'alt+f'
41
+ }
42
+
43
+ function generateHotkey(...params: Array<string[]>) {
44
+ return params.map(String).join(',');
45
+ }
46
+
47
+ type HotkeyDefinition = {
48
+ [key in Hotkeys]?: (event: any, handler: any) => MaybePromise<void>;
49
+ };
50
+
51
+ // Vitest not liking us testing defaultfunctions with object modifiers
52
+ /* v8 ignore start -- @preserve */
53
+ export function defineHotkeys(scope: HotkeyScope, definition: HotkeyDefinition) {
54
+ const keys = transformHotkeysMac(Object.values(Object.keys(definition)));
55
+ hotkeys(generateHotkey(keys), scope, (event: KeyboardEvent, handler: HotkeysEvent) =>
56
+ hotKeyHandler(definition, event, handler)
57
+ );
58
+ hotkeys.setScope(scope);
59
+ }
60
+ /* v8 ignore stop -- @preserve */
61
+
62
+ export function defaultHotkeys(definition: HotkeyDefinition) {
63
+ const keys = transformHotkeysMac(Object.values(Object.keys(definition)));
64
+ hotkeys(generateHotkey(keys), (event: KeyboardEvent, handler: HotkeysEvent) =>
65
+ hotKeyHandler(definition, event, handler)
66
+ );
67
+ }
68
+
69
+ function hotKeyHandler(definition: HotkeyDefinition, event: KeyboardEvent, handler: HotkeysEvent) {
70
+ event.preventDefault();
71
+ const key = handler.key as Hotkeys;
72
+ const method = definition[key.replace('command', 'ctrl').replace('option', 'alt') as Hotkeys];
73
+ if (method) {
74
+ method(event, handler);
75
+ }
76
+ }
77
+ export function resetScope(scope: HotkeyScope) {
78
+ hotkeys.deleteScope(scope);
79
+ hotkeys.setScope(HotkeyScope.PRIMARY);
80
+ }
81
+
82
+ /* v8 ignore start -- @preserve */
83
+ function transformHotkeysMac(keys: string[]): string[] {
84
+ if (isMac()) return keys.map((key) => key.replace('ctrl', 'command').replace('alt', 'option'));
85
+ return keys;
86
+ }
87
+ /* v8 ignore stop -- @preserve */
@@ -0,0 +1,49 @@
1
+ import { MockImportOriginal } from '@test/MockImportOriginal';
2
+ import { defaultHotkeys, Hotkeys, HotkeyScope, resetScope } from '@/functions/HotkeyScope';
3
+ import hotkeys from 'hotkeys-js';
4
+ import { flushPromises } from '@vue/test-utils';
5
+
6
+ vi.mock('hotkeys-js', async (importOriginal: MockImportOriginal<typeof import('hotkeys-js')>) => {
7
+ return {
8
+ ...(await importOriginal()),
9
+ default: vi.fn().mockReturnValue({
10
+ setScope: vi.fn(),
11
+ deleteScope: vi.fn()
12
+ })
13
+ };
14
+ });
15
+ describe('HotkeyScope tests', () => {
16
+ it('should map defaultHotkeys', async () => {
17
+ const altD = vi.fn();
18
+ const hotkeysMocked = vi.mocked(hotkeys);
19
+ hotkeysMocked.mockImplementationOnce((params: any, params2: any) => {
20
+ params2({ preventDefault: vi.fn() }, { key: params });
21
+ });
22
+ defaultHotkeys({
23
+ [Hotkeys.ALT_D]: altD
24
+ });
25
+
26
+ await flushPromises();
27
+ expect(altD).toHaveBeenCalled();
28
+ });
29
+
30
+ it('should resetScope', async () => {
31
+ const setScope = vi.fn();
32
+ const deleteScope = vi.fn();
33
+ Object.defineProperty(hotkeys, 'setScope', {
34
+ value: setScope,
35
+ writable: true // possibility to override
36
+ });
37
+ Object.defineProperty(hotkeys, 'deleteScope', {
38
+ value: deleteScope,
39
+ writable: true // possibility to override
40
+ });
41
+
42
+ await flushPromises();
43
+ resetScope(HotkeyScope.MCAD_MENU);
44
+
45
+ await flushPromises();
46
+ expect(setScope).toHaveBeenCalled();
47
+ expect(deleteScope).toHaveBeenCalled();
48
+ });
49
+ });
@@ -0,0 +1,42 @@
1
+ import { fromColourData, hexToRgb, hexToRgba, rgbaToHex, rgbToHex, toColourData } from '@/functions/colours';
2
+ import { ColourData } from '@3cr/types-ts/types/ColourData';
3
+
4
+ describe('colour tests', () => {
5
+ it('should convert RGB to hex', () => {
6
+ const hex = rgbToHex(255, 0, 255);
7
+ expect(hex).toBe('#ff00ff');
8
+ });
9
+
10
+ it('should convert RGBA to hex', () => {
11
+ const hex = rgbaToHex(255, 0, 255, 16);
12
+ expect(hex).toBe('#ff00ff10');
13
+ });
14
+
15
+ it('should convert hex to RGB', () => {
16
+ const [r, g, b] = hexToRgb('#fa4b8c');
17
+ expect(r).toBe(250);
18
+ expect(g).toBe(75);
19
+ expect(b).toBe(140);
20
+ });
21
+
22
+ it('should convert hex to RGBA', () => {
23
+ const [r, g, b, a] = hexToRgba('#1c7a4ff9');
24
+ expect(r).toBe(28);
25
+ expect(g).toBe(122);
26
+ expect(b).toBe(79);
27
+ expect(a).toBe(249);
28
+ });
29
+
30
+ it('should convert from colour data', () => {
31
+ const data: ColourData = { Version: '0.0.0', R: 0.1, G: 0.2, B: 0.3, A: 1 };
32
+ const hex = fromColourData(data);
33
+ expect(hex).toBe('#19334c');
34
+ });
35
+
36
+ it('should convert to colour data', () => {
37
+ const { R, G, B } = toColourData('#9f6a4d');
38
+ expect(R).toBeCloseTo(0.62);
39
+ expect(G).toBeCloseTo(0.42);
40
+ expect(B).toBeCloseTo(0.3);
41
+ });
42
+ });
@@ -0,0 +1,130 @@
1
+ import { OnFileCallback, traverseDirectoryTreeWrapper } from '@/functions/fileReader';
2
+
3
+ describe('fileReader', () => {
4
+ it('should process a single file', async () => {
5
+ const mockFileCallback: OnFileCallback = vi.fn();
6
+ const mockFile = new File(['content'], 'mockFile.txt', { type: 'text/plain' });
7
+
8
+ // Mock FileSystemFileEntry
9
+ const mockFileEntry = {
10
+ isFile: true,
11
+ isDirectory: false,
12
+ file: vi.fn((callback: (file: File) => void) => callback(mockFile))
13
+ } as unknown as FileSystemFileEntry;
14
+
15
+ // Mock DataTransferItem
16
+ const mockEntry = {
17
+ webkitGetAsEntry: vi.fn(() => mockFileEntry)
18
+ } as unknown as DataTransferItem;
19
+
20
+ // Mock DataTransferItemList
21
+ const mockDataTransferItemList = [mockEntry] as unknown as DataTransferItemList;
22
+
23
+ // Call the wrapper function
24
+ await traverseDirectoryTreeWrapper(mockDataTransferItemList, mockFileCallback);
25
+
26
+ // Assertions
27
+ expect(mockFileCallback).toHaveBeenCalledWith(mockFile, '');
28
+ expect(mockFileCallback).toHaveBeenCalledTimes(1);
29
+ });
30
+
31
+ it('should process a nested directory with files', async () => {
32
+ const mockFileCallback: OnFileCallback = vi.fn();
33
+ const mockFile = new File(['content'], 'nestedFile.txt', { type: 'text/plain' });
34
+
35
+ // Mock FileSystemFileEntry
36
+ const mockFileEntry = {
37
+ isFile: true,
38
+ isDirectory: false,
39
+ file: vi.fn((callback: (file: File) => void) => callback(mockFile))
40
+ } as unknown as FileSystemFileEntry;
41
+
42
+ // Mock nested FileSystemDirectoryReader
43
+ const mockNestedDirectoryReader = {
44
+ readEntries: vi
45
+ .fn()
46
+ .mockImplementationOnce((success: (entries: Array<FileSystemEntry>) => void) => {
47
+ success([mockFileEntry]); // First call returns the file entry
48
+ })
49
+ .mockImplementationOnce((success: (entries: Array<FileSystemEntry>) => void) => {
50
+ success([]); // Second call returns no entries, ending recursion
51
+ })
52
+ } as unknown as FileSystemDirectoryReader;
53
+
54
+ // Mock nested FileSystemDirectoryEntry
55
+ const mockNestedDirectoryEntry = {
56
+ isFile: false,
57
+ isDirectory: true,
58
+ name: 'nestedDirectory',
59
+ createReader: vi.fn(() => mockNestedDirectoryReader)
60
+ } as unknown as FileSystemDirectoryEntry;
61
+
62
+ // Mock top-level FileSystemDirectoryReader
63
+ const mockDirectoryReader = {
64
+ readEntries: vi
65
+ .fn()
66
+ .mockImplementationOnce((success: (entries: Array<FileSystemEntry>) => void) => {
67
+ success([mockNestedDirectoryEntry]); // First call returns the nested directory
68
+ })
69
+ .mockImplementationOnce((success: (entries: Array<FileSystemEntry>) => void) => {
70
+ success([]); // Second call returns no entries, ending recursion
71
+ })
72
+ } as unknown as FileSystemDirectoryReader;
73
+
74
+ // Mock top-level FileSystemDirectoryEntry
75
+ const mockDirectoryEntry = {
76
+ isFile: false,
77
+ isDirectory: true,
78
+ name: 'mockDirectory',
79
+ createReader: vi.fn(() => mockDirectoryReader)
80
+ } as unknown as FileSystemDirectoryEntry;
81
+
82
+ // Mock DataTransferItem
83
+ const mockEntry = {
84
+ webkitGetAsEntry: vi.fn(() => mockDirectoryEntry)
85
+ } as unknown as DataTransferItem;
86
+
87
+ // Mock DataTransferItemList
88
+ const mockDataTransferItemList = [mockEntry] as unknown as DataTransferItemList;
89
+
90
+ // Call the wrapper function
91
+ await traverseDirectoryTreeWrapper(mockDataTransferItemList, mockFileCallback);
92
+
93
+ // Assertions
94
+ expect(mockFileCallback).toHaveBeenCalledWith(mockFile, 'mockDirectory/nestedDirectory/');
95
+ expect(mockFileCallback).toHaveBeenCalledTimes(1);
96
+ });
97
+
98
+ it('should handle an empty directory gracefully', async () => {
99
+ const mockFileCallback: OnFileCallback = vi.fn();
100
+
101
+ // Mock FileSystemDirectoryReader
102
+ const mockDirectoryReader = {
103
+ readEntries: vi.fn((success: (entries: Array<FileSystemEntry>) => void) => {
104
+ success([]); // Immediately return no entries
105
+ })
106
+ } as unknown as FileSystemDirectoryReader;
107
+
108
+ // Mock FileSystemDirectoryEntry
109
+ const mockDirectoryEntry = {
110
+ isFile: false,
111
+ isDirectory: true,
112
+ name: 'emptyDirectory',
113
+ createReader: vi.fn(() => mockDirectoryReader)
114
+ } as unknown as FileSystemDirectoryEntry;
115
+
116
+ // Mock DataTransferItem
117
+ const mockEntry = {
118
+ webkitGetAsEntry: vi.fn(() => mockDirectoryEntry)
119
+ } as unknown as DataTransferItem;
120
+
121
+ // Mock DataTransferItemList
122
+ const mockDataTransferItemList = [mockEntry] as unknown as DataTransferItemList;
123
+
124
+ // Call the wrapper function
125
+ await traverseDirectoryTreeWrapper(mockDataTransferItemList, mockFileCallback);
126
+
127
+ // Assertions
128
+ expect(mockFileCallback).not.toHaveBeenCalled(); // Callback should not be invoked
129
+ });
130
+ });
@@ -88,6 +88,7 @@ describe('model helper tests', () => {
88
88
  },
89
89
  NavigationCube: {
90
90
  Version: '1.0.0',
91
+ NavCubeGraphicType: 1,
91
92
  Transform: {
92
93
  Version: '1.0.0',
93
94
  AnchorPoint: AnchorPoint.DEFAULT,
@@ -0,0 +1,7 @@
1
+ import { forceType } from '@/functions/tsHelper';
2
+
3
+ describe('forceType tests', () => {
4
+ it('should forceType min', () => {
5
+ expect(forceType<String>('123')).toBeTypeOrNull(String);
6
+ });
7
+ });
@@ -0,0 +1,21 @@
1
+ type WorkerFunction<T> = (arg: T) => Promise<void>;
2
+
3
+ export class Chunker {
4
+ static Instantiate() {
5
+ return new Chunker();
6
+ }
7
+
8
+ async ExecuteInChunks<T>(chunkSize: number, dataSet: T[], worker: WorkerFunction<T>) {
9
+ const chunks: T[][] = [];
10
+
11
+ for (let i = 0; i < dataSet.length; i += chunkSize) {
12
+ chunks.push(dataSet.slice(i, i + chunkSize));
13
+ }
14
+ for (const chunk of chunks) {
15
+ // NOTE: This is on purpose. We don't want to overwhelm
16
+ // the server with requests spawned, so we rate limit to `chunksize` at a time
17
+ // eslint-disable-next-line no-await-in-loop
18
+ await Promise.all(chunk.map((x) => worker(x)));
19
+ }
20
+ }
21
+ }