@cytario/web 2.1.4 → 2.1.6

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 (202) hide show
  1. package/README.md +20 -20
  2. package/app/.server/auth/README.md +8 -8
  3. package/app/.server/auth/authMiddleware.ts +9 -25
  4. package/app/.server/auth/exchangeAuthCode.ts +2 -6
  5. package/app/.server/auth/getS3Client.ts +3 -13
  6. package/app/.server/auth/getSessionCredentials.ts +6 -20
  7. package/app/.server/auth/getUserInfo.ts +2 -6
  8. package/app/.server/auth/keycloakAdmin/client.ts +2 -9
  9. package/app/.server/auth/keycloakAdmin/groups.ts +9 -26
  10. package/app/.server/auth/keycloakAdmin/users.ts +7 -23
  11. package/app/.server/auth/oauthState.ts +4 -13
  12. package/app/.server/auth/redirectIfAuthenticated.ts +1 -3
  13. package/app/.server/auth/refreshAuthTokens.ts +5 -19
  14. package/app/.server/auth/sessionMiddleware.ts +1 -4
  15. package/app/.server/auth/sessionStorage.ts +1 -4
  16. package/app/.server/auth/verifyIdToken.ts +1 -3
  17. package/app/.server/auth/wellKnownEndpoints.ts +1 -4
  18. package/app/.server/db/redis.ts +5 -1
  19. package/app/.server/logging.ts +1 -4
  20. package/app/.server/requestDurationMiddleware.ts +1 -4
  21. package/app/components/.client/ImageViewer/README.md +5 -5
  22. package/app/components/.client/ImageViewer/components/ChannelsController/ChannelsController.tsx +7 -9
  23. package/app/components/.client/ImageViewer/components/ChannelsController/ChannelsControllerBrightfieldItem.tsx +1 -2
  24. package/app/components/.client/ImageViewer/components/ChannelsController/ChannelsControllerItem.tsx +2 -5
  25. package/app/components/.client/ImageViewer/components/ChannelsController/ChannelsControllerItemList.tsx +7 -15
  26. package/app/components/.client/ImageViewer/components/ChannelsController/ColorPicker/ColorPicker.tsx +2 -9
  27. package/app/components/.client/ImageViewer/components/ChannelsController/ColorPicker/ColorSwatch.tsx +1 -4
  28. package/app/components/.client/ImageViewer/components/ChannelsController/DomainSlider.tsx +1 -3
  29. package/app/components/.client/ImageViewer/components/ChannelsController/Histogram.tsx +16 -18
  30. package/app/components/.client/ImageViewer/components/ChannelsController/HistogramChannel.tsx +2 -8
  31. package/app/components/.client/ImageViewer/components/ChannelsController/MinMaxSettings.tsx +6 -15
  32. package/app/components/.client/ImageViewer/components/FeatureBar/FeatureBarDragHandle.tsx +1 -5
  33. package/app/components/.client/ImageViewer/components/FeatureBar/FeatureBarToggle.tsx +1 -5
  34. package/app/components/.client/ImageViewer/components/FeatureBar/FeatureItem.tsx +1 -5
  35. package/app/components/.client/ImageViewer/components/FeatureBar/Presets.tsx +3 -11
  36. package/app/components/.client/ImageViewer/components/FeatureBar/useFeatureBar.tsx +16 -25
  37. package/app/components/.client/ImageViewer/components/Image/Channels/useChannelsLayer.ts +7 -18
  38. package/app/components/.client/ImageViewer/components/Image/ImageContainer.tsx +1 -1
  39. package/app/components/.client/ImageViewer/components/Image/ImagePanel.tsx +6 -26
  40. package/app/components/.client/ImageViewer/components/Image/ImagePreview.tsx +2 -9
  41. package/app/components/.client/ImageViewer/components/Image/Overlays/AdditivePolygonLayer.tsx +1 -5
  42. package/app/components/.client/ImageViewer/components/Image/Overlays/AdditiveScatterplotLayer.tsx +1 -5
  43. package/app/components/.client/ImageViewer/components/Image/Overlays/OverlaysLayer.tsx +6 -24
  44. package/app/components/.client/ImageViewer/components/Image/Overlays/markerUniforms.ts +2 -5
  45. package/app/components/.client/ImageViewer/components/Image/Overlays/useOverlaysLayer.tsx +7 -21
  46. package/app/components/.client/ImageViewer/components/Image/useInitializeChannels.ts +1 -7
  47. package/app/components/.client/ImageViewer/components/Image/useResizeObserver.ts +1 -1
  48. package/app/components/.client/ImageViewer/components/Magnifier.tsx +5 -13
  49. package/app/components/.client/ImageViewer/components/Measurements/ActiveViewStatePreview.tsx +3 -8
  50. package/app/components/.client/ImageViewer/components/Measurements/CursorTick.tsx +2 -7
  51. package/app/components/.client/ImageViewer/components/Measurements/Ruler.tsx +1 -8
  52. package/app/components/.client/ImageViewer/components/Measurements/SlideCarrier.tsx +3 -13
  53. package/app/components/.client/ImageViewer/components/Measurements/Tick.tsx +1 -1
  54. package/app/components/.client/ImageViewer/components/Measurements/calculateViewStateToFit.ts +1 -1
  55. package/app/components/.client/ImageViewer/components/Measurements/useMeasurements.ts +9 -28
  56. package/app/components/.client/ImageViewer/components/OverlaysController/AddOverlay.tsx +4 -13
  57. package/app/components/.client/ImageViewer/components/OverlaysController/OverlayPicker.modal.tsx +1 -6
  58. package/app/components/.client/ImageViewer/components/OverlaysController/OverlaysController.Item.tsx +24 -54
  59. package/app/components/.client/ImageViewer/components/OverlaysController/OverlaysController.tsx +1 -3
  60. package/app/components/.client/ImageViewer/components/SplitViewToggle.tsx +1 -3
  61. package/app/components/.client/ImageViewer/components/ViewerHeader.tsx +1 -3
  62. package/app/components/.client/ImageViewer/state/decoders/decodeJPEG2000.d.ts +9 -11
  63. package/app/components/.client/ImageViewer/state/decoders/decodeJPEG2000.js +11 -11
  64. package/app/components/.client/ImageViewer/state/decoders/decoder.worker.js +49 -49
  65. package/app/components/.client/ImageViewer/state/decoders/genericDecoder.ts +76 -81
  66. package/app/components/.client/ImageViewer/state/decoders/jp2k-decoder.ts +9 -9
  67. package/app/components/.client/ImageViewer/state/decoders/lzwDecoder.ts +9 -9
  68. package/app/components/.client/ImageViewer/state/loaders/loadBioformatsZarrWithCredentials.ts +10 -22
  69. package/app/components/.client/ImageViewer/state/store/ViewerStoreContext.tsx +4 -18
  70. package/app/components/.client/ImageViewer/state/store/createViewerStore.ts +110 -194
  71. package/app/components/.client/ImageViewer/state/store/getInitialChannelsState.ts +2 -6
  72. package/app/components/.client/ImageViewer/state/store/selectors.ts +9 -9
  73. package/app/components/.client/ImageViewer/state/store/types.ts +3 -12
  74. package/app/components/.client/ImageViewer/state/transport/CredentialedHTTPStore.ts +1 -5
  75. package/app/components/.client/ImageViewer/state/transport/SigV4TiffClient.ts +2 -9
  76. package/app/components/.client/ImageViewer/utils/getSelectionStats.ts +1 -4
  77. package/app/components/.client/ImageViewer/utils/handleImageViewerHover.ts +1 -1
  78. package/app/components/.client/ImageViewer/utils/mapChannelConfigsToState.ts +2 -4
  79. package/app/components/.client/ImageViewer/utils/useTilesLoading.ts +3 -3
  80. package/app/components/AppHeader.tsx +1 -4
  81. package/app/components/Breadcrumbs/Breadcrumbs.tsx +4 -13
  82. package/app/components/Breadcrumbs/getCrumbs.tsx +1 -1
  83. package/app/components/ClientOnly.tsx +1 -1
  84. package/app/components/Container.tsx +3 -15
  85. package/app/components/DataGrid/ConvertOverlay.modal.tsx +1 -6
  86. package/app/components/DataGrid/DataGrid.tsx +7 -27
  87. package/app/components/DataGrid/WktSvg.tsx +2 -4
  88. package/app/components/DataGrid/getParquetSchema.ts +1 -4
  89. package/app/components/DescriptionList.tsx +1 -3
  90. package/app/components/DirectoryView/ConnectionMenu.tsx +8 -22
  91. package/app/components/DirectoryView/DirectoryView.tsx +10 -46
  92. package/app/components/DirectoryView/DirectoryViewGrid.tsx +16 -49
  93. package/app/components/DirectoryView/DirectoryViewTableConnection.tsx +1 -4
  94. package/app/components/DirectoryView/DirectoryViewTableDirectory.tsx +2 -7
  95. package/app/components/DirectoryView/DirectoryViewTree.tsx +5 -21
  96. package/app/components/DirectoryView/FilterBar.tsx +9 -48
  97. package/app/components/DirectoryView/buildDirectoryTree.ts +6 -25
  98. package/app/components/DirectoryView/filterNodes.ts +4 -11
  99. package/app/components/DirectoryView/modals/Cyberduck.modal.tsx +6 -15
  100. package/app/components/DirectoryView/modals/FileInfo.modal.tsx +1 -5
  101. package/app/components/DirectoryView/useLayoutStore.ts +5 -25
  102. package/app/components/GlobalSearch/GlobalSearch.tsx +1 -4
  103. package/app/components/GlobalSearch/SearchBar.tsx +1 -7
  104. package/app/components/GlobalSearch/Suggestions.tsx +0 -1
  105. package/app/components/ImageViewer/state/formatRegistry.ts +5 -18
  106. package/app/components/LavaLoader.tsx +4 -11
  107. package/app/components/Layout/Footer.tsx +1 -4
  108. package/app/components/Pills/ScopePill.tsx +2 -8
  109. package/app/components/Table/ColumnFilterInput.tsx +5 -20
  110. package/app/components/Table/ColumnResizeHandle.tsx +1 -5
  111. package/app/components/Table/ColumnSortButton.tsx +1 -5
  112. package/app/components/Table/SelectionFooter.tsx +3 -5
  113. package/app/components/Table/Table.tsx +5 -21
  114. package/app/components/Table/TableBodyRow.tsx +19 -31
  115. package/app/components/Table/TableHeaderRow.tsx +7 -28
  116. package/app/components/Table/TableMenu.tsx +4 -20
  117. package/app/components/Table/state/createTableStore.ts +3 -9
  118. package/app/components/Table/state/useTableStore.ts +1 -3
  119. package/app/components/Table/types.ts +4 -10
  120. package/app/components/Table/useColumnFilters.ts +1 -4
  121. package/app/components/Table/useColumnVisibility.ts +4 -11
  122. package/app/components/Table/useColumnWidths.ts +1 -3
  123. package/app/components/Table/useTableSorting.ts +4 -12
  124. package/app/components/Tooltip/Tooltip.tsx +4 -17
  125. package/app/components/Tooltip/TooltipSpan.tsx +5 -28
  126. package/app/components/Tooltip/useCopyToClipboard.ts +1 -3
  127. package/app/components/Tooltip/useMiddleEllipsis.ts +2 -7
  128. package/app/components/Tooltip/useOverflowDetection.ts +2 -5
  129. package/app/components/UserMenu.tsx +2 -9
  130. package/app/entry.server.tsx +9 -19
  131. package/app/hooks/useSearchParam.ts +2 -4
  132. package/app/lib/bootstrapPluginsCore.ts +4 -9
  133. package/app/root.tsx +4 -15
  134. package/app/routes/admin/assertAdminScope.ts +1 -3
  135. package/app/routes/admin/assertGroupPathsInScope.ts +3 -11
  136. package/app/routes/admin/assertGroupsInScope.ts +3 -11
  137. package/app/routes/admin/assertUsersInScope.ts +2 -8
  138. package/app/routes/admin/bulkInvite/bulkInvite.action.ts +2 -13
  139. package/app/routes/admin/bulkInvite/bulkInvite.form.tsx +18 -35
  140. package/app/routes/admin/createGroup/createGroup.action.ts +3 -10
  141. package/app/routes/admin/createGroup/createGroup.form.tsx +2 -9
  142. package/app/routes/admin/createGroup/createGroup.modal.tsx +1 -5
  143. package/app/routes/admin/inviteUser/inviteUser.action.ts +1 -4
  144. package/app/routes/admin/inviteUser/inviteUser.form.tsx +2 -8
  145. package/app/routes/admin/inviteUser/inviteUser.loader.ts +1 -4
  146. package/app/routes/admin/inviteUser/inviteUser.modal.tsx +3 -16
  147. package/app/routes/admin/updateUser/updateUser.form.tsx +4 -15
  148. package/app/routes/admin/updateUser/updateUser.modal.tsx +5 -23
  149. package/app/routes/admin/updateUser/userDetail.action.ts +2 -10
  150. package/app/routes/admin/users/BulkActions.tsx +15 -38
  151. package/app/routes/admin/users/bulkUsers.action.ts +2 -9
  152. package/app/routes/admin/users/bulkUsers.schema.ts +1 -6
  153. package/app/routes/admin/users/users.route.tsx +14 -63
  154. package/app/routes/api/cyberduck-profile.$name.ts +6 -2
  155. package/app/routes/auth/callback.route.tsx +8 -33
  156. package/app/routes/auth/login.route.tsx +8 -11
  157. package/app/routes/auth/logout.route.tsx +4 -14
  158. package/app/routes/config.route.tsx +1 -5
  159. package/app/routes/connections/connection.form.tsx +5 -14
  160. package/app/routes/connections/connection.schema.ts +4 -16
  161. package/app/routes/connections/connections.loader.ts +11 -23
  162. package/app/routes/connections/connections.route.tsx +1 -5
  163. package/app/routes/connections/connections.server.ts +1 -3
  164. package/app/routes/connections/createConnection.action.ts +2 -8
  165. package/app/routes/connections/createConnection.modal.tsx +3 -13
  166. package/app/routes/connections/deleteConnection.action.ts +1 -4
  167. package/app/routes/connections/updateConnection.action.ts +2 -8
  168. package/app/routes/connections/updateConnection.modal.tsx +4 -14
  169. package/app/routes/home/home.route.tsx +8 -33
  170. package/app/routes/layouts/ModalOutlet.tsx +6 -18
  171. package/app/routes/objects/objects.loader.ts +5 -19
  172. package/app/routes/objects/objects.route.tsx +11 -30
  173. package/app/routes/presign.route.tsx +5 -18
  174. package/app/routes/recent.route.tsx +1 -4
  175. package/app/routes/search.route.tsx +4 -17
  176. package/app/routes.ts +1 -4
  177. package/app/tailwind.css +17 -12
  178. package/app/types/cornerstone-codecs.d.ts +25 -29
  179. package/app/utils/connectionsStore/selectors.ts +2 -6
  180. package/app/utils/connectionsStore/useConnectionsStore.ts +2 -6
  181. package/app/utils/db/convertCsvToParquet.ts +1 -3
  182. package/app/utils/db/createDatabase.ts +1 -3
  183. package/app/utils/db/createSingleton.ts +1 -1
  184. package/app/utils/db/getBlobFromObjectNode.ts +3 -9
  185. package/app/utils/db/getGeomQuery.ts +1 -1
  186. package/app/utils/db/getMarkerInfoWasm.ts +1 -3
  187. package/app/utils/db/getTileBoundingBox.ts +1 -4
  188. package/app/utils/db/sqlQueries.ts +1 -4
  189. package/app/utils/fileType.ts +80 -10
  190. package/app/utils/filterObjects.ts +2 -5
  191. package/app/utils/localFilesStore/useFileStore.ts +7 -7
  192. package/app/utils/recentlyViewed.server.ts +1 -4
  193. package/app/utils/resourceId.ts +3 -11
  194. package/app/utils/s3Provider.ts +3 -7
  195. package/app/utils/signedFetch.ts +4 -13
  196. package/bin-src/codegen.ts +4 -1
  197. package/package.json +5 -1
  198. package/prisma/seed.ts +1 -2
  199. package/public/favicon/site.webmanifest +1 -1
  200. package/server.js +1 -4
  201. package/server.js.map +1 -1
  202. package/vite-plugins/cytario-plugins.ts +2 -8
@@ -186,8 +186,8 @@ GeoTIFF decoders are registered at the module level in `ImageViewer.tsx`:
186
186
 
187
187
  ```typescript
188
188
  import { addDecoder } from "geotiff";
189
- addDecoder(5, () => LZWDecoder); // LZW compression
190
- addDecoder(33005, () => JP2KDecoder); // JPEG2000 compression
189
+ addDecoder(5, () => LZWDecoder); // LZW compression
190
+ addDecoder(33005, () => JP2KDecoder); // JPEG2000 compression
191
191
  ```
192
192
 
193
193
  This runs client-side only — decoders use Web Workers and must not be imported during SSR. The `ImageViewer` component is lazy-loaded from the route via `React.lazy()` + `<ClientOnly>` + `<Suspense>`, ensuring decoders are never evaluated on the server.
@@ -215,9 +215,9 @@ ImageViewer (client)
215
215
 
216
216
  ### Naming Convention
217
217
 
218
- | Image key | Offsets key |
219
- |---|---|
220
- | `data/image.ome.tif` | `data/image.offsets.json` |
218
+ | Image key | Offsets key |
219
+ | --------------------- | ------------------------- |
220
+ | `data/image.ome.tif` | `data/image.offsets.json` |
221
221
  | `data/image.ome.tiff` | `data/image.offsets.json` |
222
222
 
223
223
  Offsets are generated with [`generate-tiff-offsets`](https://github.com/hms-dbmi/generate-tiff-offsets) and placed alongside the OME-TIFF in S3. If the file doesn't exist, the viewer loads normally via sequential IFD traversal.
@@ -20,11 +20,12 @@ export function ChannelsController() {
20
20
 
21
21
  // Show grouped counts: brightfield R/G/B counts as 1 item in the UI
22
22
  const groupOffset = brightfieldGroup ? 2 : 0; // 3 channels → 1 item = -2
23
- const isBrightfieldVisible = brightfieldGroup && channelsState
24
- ? channelsState[brightfieldGroup.red]?.isVisible &&
25
- channelsState[brightfieldGroup.green]?.isVisible &&
26
- channelsState[brightfieldGroup.blue]?.isVisible
27
- : false;
23
+ const isBrightfieldVisible =
24
+ brightfieldGroup && channelsState
25
+ ? channelsState[brightfieldGroup.red]?.isVisible &&
26
+ channelsState[brightfieldGroup.green]?.isVisible &&
27
+ channelsState[brightfieldGroup.blue]?.isVisible
28
+ : false;
28
29
  const visibleGrouped = visibleChannelCount - (isBrightfieldVisible ? 2 : 0);
29
30
  const totalGrouped = channelIds.length - groupOffset;
30
31
  const badge = `${visibleGrouped}/${totalGrouped}`;
@@ -39,10 +40,7 @@ export function ChannelsController() {
39
40
  >
40
41
  {layersStates.map((_, index) => (
41
42
  <TabPanel key={index} id={String(index)}>
42
- <ChannelsControllerItemList
43
- isExpanded={isExpanded}
44
- setIsExpanded={setIsExpanded}
45
- />
43
+ <ChannelsControllerItemList isExpanded={isExpanded} setIsExpanded={setIsExpanded} />
46
44
  </TabPanel>
47
45
  ))}
48
46
  </FeatureItem>
@@ -50,8 +50,7 @@ export function ChannelsControllerBrightfieldItem({
50
50
 
51
51
  // Brightfield needs 3 channel slots
52
52
  const disabled =
53
- !isVisible &&
54
- visibleChannelCount + BRIGHTFIELD_CHANNEL_COUNT > MAX_VISIBLE_CHANNELS;
53
+ !isVisible && visibleChannelCount + BRIGHTFIELD_CHANNEL_COUNT > MAX_VISIBLE_CHANNELS;
55
54
 
56
55
  let tooltip = `${isVisible ? "Hide" : "Show"} Brightfield`;
57
56
  if (disabled)
@@ -62,8 +62,7 @@ export function ChannelsControllerItem({
62
62
  const disabled = !isVisible && visibleChannelCount >= MAX_VISIBLE_CHANNELS;
63
63
  let tooltip = `${isVisible ? "Hide" : "Show"} ${name}`;
64
64
 
65
- if (disabled)
66
- tooltip = `Only ${MAX_VISIBLE_CHANNELS} channels can be visible at once`;
65
+ if (disabled) tooltip = `Only ${MAX_VISIBLE_CHANNELS} channels can be visible at once`;
67
66
 
68
67
  return (
69
68
  <Radio key={name} value={name} className={cx}>
@@ -88,9 +87,7 @@ export function ChannelsControllerItem({
88
87
 
89
88
  {/* Pixel Value */}
90
89
  {pixelValue > 0 && (
91
- <span className="text-xs tabular-nums text-(--color-text-secondary)">
92
- {pixelValue}
93
- </span>
90
+ <span className="text-xs tabular-nums text-(--color-text-secondary)">{pixelValue}</span>
94
91
  )}
95
92
 
96
93
  {/* Visibility Toggle */}
@@ -31,11 +31,7 @@ export function ChannelsControllerItemList({
31
31
 
32
32
  const brightfieldChannelIds = useMemo(() => {
33
33
  if (!brightfieldGroup) return new Set<string>();
34
- return new Set([
35
- brightfieldGroup.red,
36
- brightfieldGroup.green,
37
- brightfieldGroup.blue,
38
- ]);
34
+ return new Set([brightfieldGroup.red, brightfieldGroup.green, brightfieldGroup.blue]);
39
35
  }, [brightfieldGroup]);
40
36
 
41
37
  const isBrightfieldVisible = useMemo(() => {
@@ -50,16 +46,13 @@ export function ChannelsControllerItemList({
50
46
  const visibleChannelIds = useMemo(
51
47
  () =>
52
48
  channelIds.filter(
53
- (id) =>
54
- !brightfieldChannelIds.has(id) &&
55
- (isExpanded || channelsState?.[id]?.isVisible),
49
+ (id) => !brightfieldChannelIds.has(id) && (isExpanded || channelsState?.[id]?.isVisible),
56
50
  ),
57
51
  [channelIds, channelsState, isExpanded, brightfieldChannelIds],
58
52
  );
59
53
 
60
54
  // Show brightfield item when expanded or when it's visible
61
- const showBrightfield =
62
- brightfieldGroup && (isExpanded || isBrightfieldVisible);
55
+ const showBrightfield = brightfieldGroup && (isExpanded || isBrightfieldVisible);
63
56
 
64
57
  useEffect(() => {
65
58
  if (visibleChannelIds.length === 0 && !showBrightfield) {
@@ -77,7 +70,9 @@ export function ChannelsControllerItemList({
77
70
  // Check if enabling this channel/group would exceed the viv limit
78
71
  const isBrightfield = name === BRIGHTFIELD_GROUP_ID;
79
72
  const slotsNeeded = isBrightfield ? 3 : 1;
80
- const alreadyVisible = isBrightfield ? isBrightfieldVisible : channelsState?.[name]?.isVisible;
73
+ const alreadyVisible = isBrightfield
74
+ ? isBrightfieldVisible
75
+ : channelsState?.[name]?.isVisible;
81
76
  if (!alreadyVisible && visibleChannelCount + slotsNeeded > MAX_VISIBLE_CHANNELS) return;
82
77
 
83
78
  setSelectedChannelId(name);
@@ -129,10 +124,7 @@ export function ChannelsControllerItemList({
129
124
  visibleChannelCount={visibleChannelCount}
130
125
  toggleVisibility={() => {
131
126
  const newVisible = !isBrightfieldVisible;
132
- setChannelVisibility(
133
- BRIGHTFIELD_GROUP_ID as keyof ChannelsStateColumns,
134
- newVisible,
135
- );
127
+ setChannelVisibility(BRIGHTFIELD_GROUP_ID as keyof ChannelsStateColumns, newVisible);
136
128
 
137
129
  if (!newVisible && selectedChannelId === BRIGHTFIELD_GROUP_ID) {
138
130
  setSelectedChannelId(null);
@@ -43,9 +43,7 @@ export function ColorPicker({ color, onColorChange }: ColorPickerProps) {
43
43
 
44
44
  <PopoverContent placement="bottom start" data-theme="dark">
45
45
  <RacColorPicker
46
- value={parseColor(
47
- `rgb(${color[0]}, ${color[1]}, ${color[2]})`,
48
- ).toFormat("hsb")}
46
+ value={parseColor(`rgb(${color[0]}, ${color[1]}, ${color[2]})`).toFormat("hsb")}
49
47
  onChange={(color) => {
50
48
  const rgb = color.toFormat("rgb");
51
49
  onColorChange([
@@ -78,12 +76,7 @@ export function ColorPicker({ color, onColorChange }: ColorPickerProps) {
78
76
  <ColorThumb className="block w-4 h-4 rounded-full border-2 border-white shadow-md focus-visible:outline-2 focus-visible:outline-(--color-border-focus)" />
79
77
  </ColorArea>
80
78
 
81
- <ColorSlider
82
- colorSpace="hsb"
83
- channel="hue"
84
- className="w-full"
85
- aria-label="Hue"
86
- >
79
+ <ColorSlider colorSpace="hsb" channel="hue" className="w-full" aria-label="Hue">
87
80
  <SliderTrack className="relative h-3 rounded touch-none">
88
81
  <ColorThumb className="block w-4 h-4 rounded-full border-2 border-white shadow-md top-1/2 focus-visible:outline-2 focus-visible:outline-(--color-border-focus)" />
89
82
  </SliderTrack>
@@ -9,10 +9,7 @@ interface ColorSwatchProps extends ButtonProps {
9
9
 
10
10
  export function ColorSwatch({ color, ...props }: ColorSwatchProps) {
11
11
  return (
12
- <Button
13
- className="group cursor-pointer flex items-center justify-center"
14
- {...props}
15
- >
12
+ <Button className="group cursor-pointer flex items-center justify-center" {...props}>
16
13
  <div
17
14
  className={`
18
15
  w-5 h-5 m-1 rounded-full border-2
@@ -11,9 +11,7 @@ export const DomainSlider = ({ domain }: { domain: ByteDomain }) => {
11
11
  const setContrastLimits = useViewerStore(select.setContrastLimits);
12
12
  const [min, max] = domain;
13
13
 
14
- const color = selectedChannel
15
- ? rgb(selectedChannel.color)
16
- : "var(--color-text-tertiary)";
14
+ const color = selectedChannel ? rgb(selectedChannel.color) : "var(--color-text-tertiary)";
17
15
 
18
16
  return (
19
17
  <div className="h-0">
@@ -45,25 +45,23 @@ export function Histogram() {
45
45
  <div ref={ref} className="top-0 overflow-hidden px-3 pt-3">
46
46
  <div className="p-2 pb-0 bg-[var(--color-surface-subtle)] rounded overflow-visible">
47
47
  <svg width={width} height={height}>
48
- {channelConfigs.map(
49
- ({ histogram, color, contrastLimits, isVisible }, channelIndex) => {
50
- if (!isVisible) return null;
48
+ {channelConfigs.map(({ histogram, color, contrastLimits, isVisible }, channelIndex) => {
49
+ if (!isVisible) return null;
51
50
 
52
- return (
53
- <HistogramChannel
54
- key={channelIndex}
55
- channelIndex={channelIndex}
56
- maxLogValue={maxLogValue}
57
- width={width}
58
- height={height}
59
- range={maxDomain}
60
- histogram={histogram}
61
- color={rgb(color)}
62
- contrastLimit={contrastLimits}
63
- />
64
- );
65
- },
66
- )}
51
+ return (
52
+ <HistogramChannel
53
+ key={channelIndex}
54
+ channelIndex={channelIndex}
55
+ maxLogValue={maxLogValue}
56
+ width={width}
57
+ height={height}
58
+ range={maxDomain}
59
+ histogram={histogram}
60
+ color={rgb(color)}
61
+ contrastLimit={contrastLimits}
62
+ />
63
+ );
64
+ })}
67
65
  </svg>
68
66
 
69
67
  <DomainSlider domain={[0, maxDomain]} />
@@ -6,8 +6,7 @@ const normalizeY = (value: number, maxLogValue: number, height: number) => {
6
6
  return height - normalizedY;
7
7
  };
8
8
 
9
- const normalizeX = (index: number, length: number, width: number) =>
10
- (index / (length - 1)) * width;
9
+ const normalizeX = (index: number, length: number, width: number) => (index / (length - 1)) * width;
11
10
 
12
11
  interface HistogramChannelProps {
13
12
  channelIndex: number;
@@ -76,12 +75,7 @@ export const HistogramChannel = ({
76
75
  fill={color}
77
76
  fillOpacity={0.1}
78
77
  />
79
- <polygon
80
- points={points}
81
- fill={color}
82
- fillOpacity={0.5}
83
- clipPath={`url(#${id})`}
84
- />
78
+ <polygon points={points} fill={color} fillOpacity={0.5} clipPath={`url(#${id})`} />
85
79
  </g>
86
80
  );
87
81
  };
@@ -9,18 +9,14 @@ import { useViewerStore } from "../../state/store/ViewerStoreContext";
9
9
  export function MinMaxSettings() {
10
10
  const selectedChannel = useViewerStore(select.selectedChannel);
11
11
  const setContrastLimits = useViewerStore(select.setContrastLimits);
12
- const resetContrastLimits = useViewerStore(
13
- (state) => state.resetContrastLimits,
14
- );
12
+ const resetContrastLimits = useViewerStore((state) => state.resetContrastLimits);
15
13
 
16
14
  // Local state only used while editing (null = not editing, use store value)
17
15
  const [editingMin, setEditingMin] = useState<string | null>(null);
18
16
  const [editingMax, setEditingMax] = useState<string | null>(null);
19
17
 
20
- const minValue =
21
- editingMin ?? String(selectedChannel?.contrastLimits[0] ?? 0);
22
- const maxValue =
23
- editingMax ?? String(selectedChannel?.contrastLimits[1] ?? 0);
18
+ const minValue = editingMin ?? String(selectedChannel?.contrastLimits[0] ?? 0);
19
+ const maxValue = editingMax ?? String(selectedChannel?.contrastLimits[1] ?? 0);
24
20
 
25
21
  const commitValue = (type: "min" | "max", value: string) => {
26
22
  // Clear editing state first
@@ -54,10 +50,7 @@ export function MinMaxSettings() {
54
50
  setContrastLimits(newLimits);
55
51
  };
56
52
 
57
- const handleKeyDown = (
58
- e: React.KeyboardEvent<HTMLInputElement>,
59
- type: "min" | "max",
60
- ) => {
53
+ const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>, type: "min" | "max") => {
61
54
  if (e.key === "Enter") {
62
55
  e.currentTarget.blur();
63
56
  } else if (e.key === "Escape") {
@@ -73,10 +66,8 @@ export function MinMaxSettings() {
73
66
 
74
67
  const isResetDisabled =
75
68
  !selectedChannel ||
76
- (selectedChannel.contrastLimits[0] ===
77
- selectedChannel.contrastLimitsInitial[0] &&
78
- selectedChannel.contrastLimits[1] ===
79
- selectedChannel.contrastLimitsInitial[1]);
69
+ (selectedChannel.contrastLimits[0] === selectedChannel.contrastLimitsInitial[0] &&
70
+ selectedChannel.contrastLimits[1] === selectedChannel.contrastLimitsInitial[1]);
80
71
 
81
72
  return (
82
73
  <div className="m-2 flex gap-2 items-center">
@@ -2,11 +2,7 @@ import { motion, MotionValue } from "motion/react";
2
2
 
3
3
  import { useFeatureBarStore } from "./useFeatureBar";
4
4
 
5
- export const FeatureBarDragHandle = ({
6
- motionWidth,
7
- }: {
8
- motionWidth: MotionValue<number>;
9
- }) => {
5
+ export const FeatureBarDragHandle = ({ motionWidth }: { motionWidth: MotionValue<number> }) => {
10
6
  const { minWidth, maxWidth } = useFeatureBarStore();
11
7
 
12
8
  return (
@@ -4,11 +4,7 @@ import { MotionValue } from "motion/react";
4
4
 
5
5
  import { useFeatureBarStore } from "./useFeatureBar";
6
6
 
7
- export const FeatureBarToggle = ({
8
- motionWidth,
9
- }: {
10
- motionWidth: MotionValue<number>;
11
- }) => {
7
+ export const FeatureBarToggle = ({ motionWidth }: { motionWidth: MotionValue<number> }) => {
12
8
  const { minWidth, setWidth, width } = useFeatureBarStore();
13
9
 
14
10
  return (
@@ -53,11 +53,7 @@ function FeatureItemInner({
53
53
  </span>
54
54
  </button>
55
55
 
56
- {badge && (
57
- <span className="text-xs text-[var(--color-text-tertiary)]">
58
- {badge}
59
- </span>
60
- )}
56
+ {badge && <span className="text-xs text-[var(--color-text-tertiary)]">{badge}</span>}
61
57
 
62
58
  {onToggleChange && !toggleHidden && (
63
59
  <IconButton
@@ -6,12 +6,8 @@ import { rgb } from "../ChannelsController/ColorPicker/ColorPicker";
6
6
  import { SplitViewToggle } from "../SplitViewToggle";
7
7
 
8
8
  export function Presets({ children }: { children: React.ReactNode }) {
9
- const activeChannelsStateIndex = useViewerStore(
10
- select.activeChannelsStateIndex,
11
- );
12
- const setActiveChannelsStateIndex = useViewerStore(
13
- select.setActiveChannelsStateIndex,
14
- );
9
+ const activeChannelsStateIndex = useViewerStore(select.activeChannelsStateIndex);
10
+ const setActiveChannelsStateIndex = useViewerStore(select.setActiveChannelsStateIndex);
15
11
 
16
12
  const handleSelectionChange = (key: Key) => {
17
13
  setActiveChannelsStateIndex(Number(key));
@@ -121,11 +117,7 @@ export const PresetLabel = ({ index }: { index: number }) => {
121
117
  {colors.length > 0 && (
122
118
  <div className="mt-auto flex w-full">
123
119
  {colors.map((color, i) => (
124
- <div
125
- key={i}
126
- className="h-1.5 flex-1"
127
- style={{ backgroundColor: color }}
128
- />
120
+ <div key={i} className="h-1.5 flex-1" style={{ backgroundColor: color }} />
129
121
  ))}
130
122
  </div>
131
123
  )}
@@ -35,8 +35,7 @@ export const useFeatureBarStore = create<ControlBarStore>()(
35
35
  maxWidth: 720,
36
36
  setWidth: (width: number) => set({ width }, false, "setWidth"),
37
37
  listStyle: "grid",
38
- setListStyle: (listStyle: FeatureBarListStyle) =>
39
- set({ listStyle }, false, "setListStyle"),
38
+ setListStyle: (listStyle: FeatureBarListStyle) => set({ listStyle }, false, "setListStyle"),
40
39
  showCellOutline: true,
41
40
  setShowCellOutline: (showCellOutline: boolean) =>
42
41
  set({ showCellOutline }, false, "setShowCellOutline"),
@@ -49,20 +48,19 @@ export const useFeatureBarStore = create<ControlBarStore>()(
49
48
  acc[id] = values[index];
50
49
  return acc;
51
50
  },
52
- { ...state.pixelValues }
51
+ { ...state.pixelValues },
53
52
  ),
54
53
  }),
55
54
  false,
56
- "setPixelValues"
55
+ "setPixelValues",
57
56
  ),
58
57
  isExpanded: true,
59
- setIsExpanded: (isExpanded: boolean) =>
60
- set({ isExpanded }, false, "setIsExpanded"),
58
+ setIsExpanded: (isExpanded: boolean) => set({ isExpanded }, false, "setIsExpanded"),
61
59
  }),
62
- { name }
60
+ { name },
63
61
  ),
64
- { name }
65
- )
62
+ { name },
63
+ ),
66
64
  );
67
65
 
68
66
  export function createFeatureItemStore(name: string) {
@@ -73,16 +71,16 @@ export function createFeatureItemStore(name: string) {
73
71
  isOpen: false,
74
72
  setIsOpen: (isOpen: boolean) => set({ isOpen }),
75
73
  }),
76
- { name }
74
+ { name },
77
75
  ),
78
- { name }
79
- )
76
+ { name },
77
+ ),
80
78
  );
81
79
  }
82
80
 
83
- const FeatureItemStoreContext = createContext<UseBoundStore<
84
- StoreApi<FeatureItemStore>
85
- > | null>(null);
81
+ const FeatureItemStoreContext = createContext<UseBoundStore<StoreApi<FeatureItemStore>> | null>(
82
+ null,
83
+ );
86
84
 
87
85
  export function FeatureItemStoreProvider({
88
86
  name,
@@ -93,20 +91,13 @@ export function FeatureItemStoreProvider({
93
91
  }) {
94
92
  const [store] = useState(() => createFeatureItemStore(name));
95
93
  return (
96
- <FeatureItemStoreContext.Provider value={store}>
97
- {children}
98
- </FeatureItemStoreContext.Provider>
94
+ <FeatureItemStoreContext.Provider value={store}>{children}</FeatureItemStoreContext.Provider>
99
95
  );
100
96
  }
101
97
 
102
98
  // export function useFeatureItemStore() {
103
- export const useFeatureItemStore = <T,>(
104
- selector: (state: FeatureItemStore) => T
105
- ): T => {
99
+ export const useFeatureItemStore = <T,>(selector: (state: FeatureItemStore) => T): T => {
106
100
  const store = useContext(FeatureItemStoreContext);
107
- if (!store)
108
- throw new Error(
109
- "useFeatureItemStore must be used within a FeatureItemStoreProvider"
110
- );
101
+ if (!store) throw new Error("useFeatureItemStore must be used within a FeatureItemStoreProvider");
111
102
  return useStore(store, selector);
112
103
  };
@@ -9,14 +9,9 @@ import { useTilesLoading } from "../../../utils/useTilesLoading";
9
9
 
10
10
  const EMPTY_OBJECT = Object.freeze({});
11
11
 
12
- type MultiscaleImageLayerProps = ConstructorParameters<
13
- typeof MultiscaleImageLayer
14
- >[0];
15
-
16
- export const useChannelsLayer = (
17
- imagePanelId: number,
18
- onHover?: (info: PickingInfo) => void
19
- ) => {
12
+ type MultiscaleImageLayerProps = ConstructorParameters<typeof MultiscaleImageLayer>[0];
13
+
14
+ export const useChannelsLayer = (imagePanelId: number, onHover?: (info: PickingInfo) => void) => {
20
15
  const dtype = useViewerStore((state) => {
21
16
  const type = state.metadata?.Pixels.Type ?? "Uint8";
22
17
  return type;
@@ -31,20 +26,16 @@ export const useChannelsLayer = (
31
26
 
32
27
  const channelsStateColumns = useMemo(
33
28
  () => mapChannelConfigsToState(channelsState ?? {}),
34
- [channelsState]
29
+ [channelsState],
35
30
  );
36
31
 
37
32
  const extensions = useMemo(() => [new ColorPaletteExtension()], []);
38
33
 
39
- const { selections, contrastLimits, colors, channelsVisible } =
40
- channelsStateColumns;
34
+ const { selections, contrastLimits, colors, channelsVisible } = channelsStateColumns;
41
35
 
42
36
  const rawLoader = useViewerStore(select.loader);
43
37
  const setIsChannelsLoading = useViewerStore(select.setIsChannelsLoading);
44
- const { loadTile, finishTile } = useTilesLoading(
45
- imagePanelId,
46
- setIsChannelsLoading
47
- );
38
+ const { loadTile, finishTile } = useTilesLoading(imagePanelId, setIsChannelsLoading);
48
39
  const channelsOpacity = useViewerStore((state) => {
49
40
  const channelsStateIndex = state.imagePanels[imagePanelId];
50
41
  return state.layersStates[channelsStateIndex]?.channelsOpacity ?? 1;
@@ -62,9 +53,7 @@ export const useChannelsLayer = (
62
53
  const wrappedLoader = Object.create(Object.getPrototypeOf(loaderLevel));
63
54
  Object.assign(wrappedLoader, loaderLevel);
64
55
 
65
- wrappedLoader.getTile = async (
66
- params: Parameters<typeof originalGetTile>[0]
67
- ) => {
56
+ wrappedLoader.getTile = async (params: Parameters<typeof originalGetTile>[0]) => {
68
57
  const tileId = `${params.x}-${params.y}-${params.selection?.z || 0}`;
69
58
 
70
59
  loadTile(tileId);
@@ -56,7 +56,7 @@ export function ImageContainer({
56
56
  border-[16px]
57
57
  box-border border-[var(--color-text-secondary)]
58
58
  `,
59
- isActivePanel ? "animate-pulse-once" : "border-none"
59
+ isActivePanel ? "animate-pulse-once" : "border-none",
60
60
  );
61
61
 
62
62
  return (
@@ -1,8 +1,4 @@
1
- import {
2
- InteractionState,
3
- OrthographicViewState,
4
- PickingInfo,
5
- } from "@deck.gl/core";
1
+ import { InteractionState, OrthographicViewState, PickingInfo } from "@deck.gl/core";
6
2
  import DeckGL from "@deck.gl/react";
7
3
  import { ReactNode, useCallback, useEffect, useMemo, useState } from "react";
8
4
 
@@ -57,10 +53,7 @@ const ImagePanelInner = ({
57
53
  );
58
54
 
59
55
  /** Setup Orthographic View */
60
- const { ids } = useMemo(
61
- () => mapChannelConfigsToState(channelsState ?? {}),
62
- [channelsState],
63
- );
56
+ const { ids } = useMemo(() => mapChannelConfigsToState(channelsState ?? {}), [channelsState]);
64
57
  const setPixelValues = useFeatureBarStore((state) => state.setPixelValues);
65
58
 
66
59
  const onMultiscaleLayerHover = useCallback(
@@ -73,10 +66,7 @@ const ImagePanelInner = ({
73
66
  );
74
67
 
75
68
  /* Setup Layers */
76
- const multiscaleLayer = useChannelsLayer(
77
- imagePanelId,
78
- onMultiscaleLayerHover,
79
- );
69
+ const multiscaleLayer = useChannelsLayer(imagePanelId, onMultiscaleLayerHover);
80
70
  const markersLayers = useOverlaysLayers(imagePanelId, setTooltip);
81
71
  const layers = [multiscaleLayer, ...markersLayers];
82
72
 
@@ -84,17 +74,10 @@ const ImagePanelInner = ({
84
74
  if (!metadata || !width || !height) return;
85
75
 
86
76
  if (!viewStateActive) {
87
- const initViewState = calculateViewStateToFit(
88
- metadata,
89
- { width, height },
90
- { padding },
91
- );
77
+ const initViewState = calculateViewStateToFit(metadata, { width, height }, { padding });
92
78
 
93
79
  setViewStateActive(initViewState);
94
- } else if (
95
- viewStateActive.width !== width ||
96
- viewStateActive.height !== height
97
- ) {
80
+ } else if (viewStateActive.width !== width || viewStateActive.height !== height) {
98
81
  setViewStateActive({ ...viewStateActive, width, height });
99
82
  }
100
83
  }, [metadata, padding, setViewStateActive, width, height, viewStateActive]);
@@ -109,10 +92,7 @@ const ImagePanelInner = ({
109
92
  const handleInteractionStateChange = useCallback(
110
93
  (event: InteractionState) => {
111
94
  const { isDragging, isPanning, isZooming } = event;
112
- if (
113
- (isDragging || isPanning || isZooming) &&
114
- activeImagePanelId !== imagePanelId
115
- ) {
95
+ if ((isDragging || isPanning || isZooming) && activeImagePanelId !== imagePanelId) {
116
96
  setActiveImagePanelId(imagePanelId);
117
97
  }
118
98
  },
@@ -88,19 +88,12 @@ const ImagePreviewInner = ({ viewPort, isInteractive }: ViewProps) => {
88
88
  );
89
89
  };
90
90
 
91
- export const ImagePreview = ({
92
- isInteractive = false,
93
- }: {
94
- isInteractive?: boolean;
95
- }) => {
91
+ export const ImagePreview = ({ isInteractive = false }: { isInteractive?: boolean }) => {
96
92
  return (
97
93
  <ImageContainer isPreview>
98
94
  {(viewPort) => (
99
95
  <>
100
- <ImagePreviewInner
101
- viewPort={viewPort}
102
- isInteractive={isInteractive}
103
- />
96
+ <ImagePreviewInner viewPort={viewPort} isInteractive={isInteractive} />
104
97
  <ActiveViewStatePreview />
105
98
  </>
106
99
  )}
@@ -1,11 +1,7 @@
1
1
  import { SolidPolygonLayer } from "@deck.gl/layers";
2
2
 
3
3
  import { shadersInject } from "./additiveBlending.glsl";
4
- import {
5
- type MarkerProps,
6
- type MarkerLayerProps,
7
- markerUniforms,
8
- } from "./markerUniforms";
4
+ import { type MarkerProps, type MarkerLayerProps, markerUniforms } from "./markerUniforms";
9
5
 
10
6
  export class AdditivePolygonLayer extends SolidPolygonLayer<MarkerLayerProps> {
11
7
  static layerName = "AdditivePolygonLayer";
@@ -1,11 +1,7 @@
1
1
  import { ScatterplotLayer } from "@deck.gl/layers";
2
2
 
3
3
  import { shadersInject } from "./additiveBlending.glsl";
4
- import {
5
- type MarkerProps,
6
- type MarkerLayerProps,
7
- markerUniforms,
8
- } from "./markerUniforms";
4
+ import { type MarkerProps, type MarkerLayerProps, markerUniforms } from "./markerUniforms";
9
5
 
10
6
  export class AdditiveScatterplotLayer extends ScatterplotLayer<MarkerLayerProps> {
11
7
  static layerName = "AdditiveScatterplotLayer";