@cytario/web 2.1.4 → 2.1.5

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
@@ -12,24 +12,21 @@ import { ClientOnly } from "~/components/ClientOnly";
12
12
  import { ProviderPill } from "~/components/Pills/ProviderPill";
13
13
  import { ScopePill } from "~/components/Pills/ScopePill";
14
14
  import { useNodeInfoModal } from "~/hooks/useNodeInfoModal";
15
- import {
16
- select,
17
- selectHttpsUrl,
18
- } from "~/utils/connectionsStore/selectors";
15
+ import { select, selectHttpsUrl } from "~/utils/connectionsStore/selectors";
19
16
  import { useConnectionsStore } from "~/utils/connectionsStore/useConnectionsStore";
20
17
  import { getNodeIcon, isImageFile } from "~/utils/fileType";
21
18
  import { buildConnectionPath, constructS3Url } from "~/utils/resourceId";
22
19
  import { createSignedFetch } from "~/utils/signedFetch";
23
20
 
24
21
  const ViewerStoreProvider = lazy(() =>
25
- import("~/components/.client/ImageViewer/state/store/ViewerStoreContext").then(
26
- (mod) => ({ default: mod.ViewerStoreProvider }),
27
- ),
22
+ import("~/components/.client/ImageViewer/state/store/ViewerStoreContext").then((mod) => ({
23
+ default: mod.ViewerStoreProvider,
24
+ })),
28
25
  );
29
26
  const ImagePreview = lazy(() =>
30
- import("~/components/.client/ImageViewer/components/Image/ImagePreview").then(
31
- (mod) => ({ default: mod.ImagePreview }),
32
- ),
27
+ import("~/components/.client/ImageViewer/components/Image/ImagePreview").then((mod) => ({
28
+ default: mod.ImagePreview,
29
+ })),
33
30
  );
34
31
 
35
32
  const gridClasses: Partial<Record<ViewMode, string>> = {
@@ -40,15 +37,12 @@ const gridClasses: Partial<Record<ViewMode, string>> = {
40
37
 
41
38
  /** Create a signedFetch that lazily resolves credentials from the connections store. */
42
39
  function useSignedFetch(connectionName: string) {
43
- const connectionConfig = useConnectionsStore(
44
- select.connectionConfig(connectionName),
45
- );
40
+ const connectionConfig = useConnectionsStore(select.connectionConfig(connectionName));
46
41
 
47
42
  const signedFetch = useMemo(() => {
48
43
  if (!connectionConfig) return null;
49
44
  return createSignedFetch(
50
- () =>
51
- useConnectionsStore.getState().connections[connectionName]?.credentials,
45
+ () => useConnectionsStore.getState().connections[connectionName]?.credentials,
52
46
  connectionConfig,
53
47
  );
54
48
  }, [connectionName, connectionConfig]);
@@ -56,13 +50,7 @@ function useSignedFetch(connectionName: string) {
56
50
  return { connectionConfig, signedFetch };
57
51
  }
58
52
 
59
- function BucketCardGridItem({
60
- node,
61
- connectionName,
62
- }: {
63
- node: TreeNode;
64
- connectionName: string;
65
- }) {
53
+ function BucketCardGridItem({ node, connectionName }: { node: TreeNode; connectionName: string }) {
66
54
  const navigate = useNavigate();
67
55
  const { connectionConfig, signedFetch } = useSignedFetch(connectionName);
68
56
 
@@ -73,10 +61,7 @@ function BucketCardGridItem({
73
61
  // `_Object.Key` (already absolute — includes any configured prefix).
74
62
  const previewKey = node._Object?.Key ?? null;
75
63
  const hasPreview = !!previewKey && isImageFile(previewKey) && !!signedFetch;
76
- const s3Url =
77
- hasPreview && connectionConfig
78
- ? constructS3Url(connectionConfig, previewKey)
79
- : "";
64
+ const s3Url = hasPreview && connectionConfig ? constructS3Url(connectionConfig, previewKey) : "";
80
65
 
81
66
  return (
82
67
  <StorageConnectionCard
@@ -97,11 +82,7 @@ function BucketCardGridItem({
97
82
  >
98
83
  {hasPreview && signedFetch && (
99
84
  <ClientOnly>
100
- <Suspense
101
- fallback={
102
- <div className="animate-pulse w-full h-full bg-slate-600" />
103
- }
104
- >
85
+ <Suspense fallback={<div className="animate-pulse w-full h-full bg-slate-600" />}>
105
86
  <ViewerStoreProvider url={s3Url} signedFetch={signedFetch}>
106
87
  <ImagePreview />
107
88
  </ViewerStoreProvider>
@@ -124,8 +105,7 @@ function FileCardGridItem({
124
105
  const navigate = useNavigate();
125
106
  const handleInfo = useNodeInfoModal(node);
126
107
 
127
- const { connectionConfig: config, signedFetch } =
128
- useSignedFetch(connectionName);
108
+ const { connectionConfig: config, signedFetch } = useSignedFetch(connectionName);
129
109
  // `_Object.Key` is absolute (prefix already applied) and set for both file
130
110
  // nodes (from listing) and directory nodes (first image inside, via
131
111
  // buildDirectoryTree). File nodes without `_Object` — e.g. recently-viewed
@@ -146,10 +126,7 @@ function FileCardGridItem({
146
126
  const hasPreview = !!s3Url && !!signedFetch;
147
127
 
148
128
  const nodeIcon = getNodeIcon(node);
149
- const size =
150
- node.type === "file" && node._Object?.Size
151
- ? filesize(node._Object.Size)
152
- : undefined;
129
+ const size = node.type === "file" && node._Object?.Size ? filesize(node._Object.Size) : undefined;
153
130
 
154
131
  return (
155
132
  <FileCard
@@ -162,11 +139,7 @@ function FileCardGridItem({
162
139
  >
163
140
  {hasPreview && signedFetch && (
164
141
  <ClientOnly>
165
- <Suspense
166
- fallback={
167
- <div className="animate-pulse w-full h-full bg-slate-600" />
168
- }
169
- >
142
+ <Suspense fallback={<div className="animate-pulse w-full h-full bg-slate-600" />}>
170
143
  <ViewerStoreProvider url={s3Url} signedFetch={signedFetch}>
171
144
  <ImagePreview />
172
145
  </ViewerStoreProvider>
@@ -196,13 +169,7 @@ export function DirectoryViewGrid({
196
169
  {nodes.map((node) => {
197
170
  const key = node.id;
198
171
  if (kind === "connections") {
199
- return (
200
- <BucketCardGridItem
201
- key={key}
202
- node={node}
203
- connectionName={node.connectionName}
204
- />
205
- );
172
+ return <BucketCardGridItem key={key} node={node} connectionName={node.connectionName} />;
206
173
  }
207
174
  return (
208
175
  <FileCardGridItem
@@ -94,10 +94,7 @@ export function DirectoryViewTableConnection({
94
94
  const connections = useConnectionsStore(select.connections);
95
95
 
96
96
  const data = useMemo(
97
- () =>
98
- nodes
99
- .map((n) => connections[n.connectionName]?.connectionConfig)
100
- .filter(Boolean),
97
+ () => nodes.map((n) => connections[n.connectionName]?.connectionConfig).filter(Boolean),
101
98
  [nodes, connections],
102
99
  );
103
100
 
@@ -3,11 +3,7 @@ import { filesize } from "filesize";
3
3
  import { useMemo } from "react";
4
4
  import { Link } from "react-router";
5
5
 
6
- import {
7
- TreeNode,
8
- computeDirectorySize,
9
- computeDirectoryLastModified,
10
- } from "./buildDirectoryTree";
6
+ import { TreeNode, computeDirectorySize, computeDirectoryLastModified } from "./buildDirectoryTree";
11
7
  import { CellRenderers, ColumnConfig, Table } from "~/components/Table/Table";
12
8
  import { getFileType } from "~/utils/fileType";
13
9
  import { formatHumanReadableDate } from "~/utils/formatHumanReadableDate";
@@ -74,8 +70,7 @@ const fileCellRenderers: CellRenderers<FileRow> = {
74
70
  // for directories, file-type icon for files) to match the grid's FileCard.
75
71
  name: (row) => <Link to={`/connections/${row.id}`}>{row.name}</Link>,
76
72
  file_type: (row) => <Pill>{row.file_type}</Pill>,
77
- last_modified: (row) =>
78
- row.last_modified ? formatHumanReadableDate(row.last_modified) : null,
73
+ last_modified: (row) => (row.last_modified ? formatHumanReadableDate(row.last_modified) : null),
79
74
  size: (row) => (row.size ? filesize(row.size).toString() : null),
80
75
  };
81
76
 
@@ -44,11 +44,7 @@ export function NodeLinkIcon({ node }: { node: TreeNode }) {
44
44
  * this wrapper and {@link DirectoryTree} should collapse into a single
45
45
  * component used across the app.
46
46
  */
47
- export function DirectoryViewTree({
48
- nodes,
49
- searchTerm,
50
- kind,
51
- }: DirectoryViewTreeProps) {
47
+ export function DirectoryViewTree({ nodes, searchTerm, kind }: DirectoryViewTreeProps) {
52
48
  const navigate = useNavigate();
53
49
 
54
50
  if (nodes.length === 0) return <DirectoryViewEmptyState kind={kind} />;
@@ -63,12 +59,8 @@ export function DirectoryViewTree({
63
59
  size="comfortable"
64
60
  height={600}
65
61
  searchTerm={searchTerm}
66
- searchMatch={(node, term) =>
67
- node.name.toLowerCase().includes(term.toLowerCase())
68
- }
69
- onActivate={(node) =>
70
- navigate(buildConnectionPath(node.connectionName, node.pathName))
71
- }
62
+ searchMatch={(node, term) => node.name.toLowerCase().includes(term.toLowerCase())}
63
+ onActivate={(node) => navigate(buildConnectionPath(node.connectionName, node.pathName))}
72
64
  />
73
65
  </div>
74
66
  );
@@ -84,11 +76,7 @@ interface DirectoryTreeProps {
84
76
  className?: string;
85
77
  }
86
78
 
87
- function DirectoryTreeRecursive({
88
- nodes,
89
- action,
90
- className,
91
- }: DirectoryTreeProps) {
79
+ function DirectoryTreeRecursive({ nodes, action, className }: DirectoryTreeProps) {
92
80
  return (
93
81
  <ul className="pl-6">
94
82
  {nodes.map((node) => {
@@ -121,11 +109,7 @@ function DirectoryTreeRecursive({
121
109
  </Link>
122
110
  </div>
123
111
  {node.children && node.children.length > 0 && (
124
- <DirectoryTreeRecursive
125
- nodes={node.children}
126
- action={action}
127
- className={className}
128
- />
112
+ <DirectoryTreeRecursive nodes={node.children} action={action} className={className} />
129
113
  )}
130
114
  </li>
131
115
  );
@@ -1,11 +1,4 @@
1
- import {
2
- Button,
3
- IconButton,
4
- Input,
5
- Pill,
6
- Select,
7
- type SelectItem,
8
- } from "@cytario/design";
1
+ import { Button, IconButton, Input, Pill, Select, type SelectItem } from "@cytario/design";
9
2
  import { FilterX, X } from "lucide-react";
10
3
  import { type ReactNode, useMemo } from "react";
11
4
 
@@ -40,19 +33,12 @@ interface FilterBarProps {
40
33
  * Introduced in C-82 to give grid and tree views the same filter capabilities
41
34
  * as the table view.
42
35
  */
43
- export function FilterBar({
44
- columns,
45
- tableId,
46
- dynamicOptions,
47
- }: FilterBarProps) {
36
+ export function FilterBar({ columns, tableId, dynamicOptions }: FilterBarProps) {
48
37
  const { columnFilters, setColumnFilters, resetFilters } = useColumnFilters({
49
38
  tableId,
50
39
  });
51
40
 
52
- const filterable = useMemo(
53
- () => columns.filter((c) => c.enableColumnFilter),
54
- [columns],
55
- );
41
+ const filterable = useMemo(() => columns.filter((c) => c.enableColumnFilter), [columns]);
56
42
 
57
43
  const hasActive = columnFilters.length > 0;
58
44
 
@@ -65,9 +51,7 @@ export function FilterBar({
65
51
  key={col.id}
66
52
  column={col}
67
53
  options={col.filterOptions ?? dynamicOptions?.[col.id]}
68
- value={
69
- (columnFilters.find((f) => f.id === col.id)?.value as string) ?? ""
70
- }
54
+ value={(columnFilters.find((f) => f.id === col.id)?.value as string) ?? ""}
71
55
  onChange={(next) => {
72
56
  setColumnFilters((prev) => {
73
57
  const rest = prev.filter((f) => f.id !== col.id);
@@ -77,12 +61,7 @@ export function FilterBar({
77
61
  />
78
62
  ))}
79
63
  {hasActive && (
80
- <Button
81
- variant="ghost"
82
- size="sm"
83
- iconLeft={FilterX}
84
- onPress={resetFilters}
85
- >
64
+ <Button variant="ghost" size="sm" iconLeft={FilterX} onPress={resetFilters}>
86
65
  Clear all
87
66
  </Button>
88
67
  )}
@@ -97,23 +76,11 @@ interface FilterControlProps {
97
76
  onChange: (value: string) => void;
98
77
  }
99
78
 
100
- function FilterControl({
101
- column,
102
- options,
103
- value,
104
- onChange,
105
- }: FilterControlProps) {
79
+ function FilterControl({ column, options, value, onChange }: FilterControlProps) {
106
80
  const clear = () => onChange("");
107
81
 
108
82
  if (column.filterType === "select") {
109
- return (
110
- <SelectFilter
111
- column={column}
112
- options={options}
113
- value={value}
114
- onChange={onChange}
115
- />
116
- );
83
+ return <SelectFilter column={column} options={options} value={value} onChange={onChange} />;
117
84
  }
118
85
 
119
86
  return (
@@ -154,9 +121,7 @@ function SelectFilter({
154
121
  if (!options) return [ALL_OPTION];
155
122
  return [
156
123
  ALL_OPTION,
157
- ...options
158
- .filter((o) => o.value !== "")
159
- .map((o) => ({ id: o.value, name: o.label })),
124
+ ...options.filter((o) => o.value !== "").map((o) => ({ id: o.value, name: o.label })),
160
125
  ];
161
126
  }, [options]);
162
127
 
@@ -166,11 +131,7 @@ function SelectFilter({
166
131
  if (!column.filterRender) return undefined;
167
132
  const render = column.filterRender;
168
133
  function FilterOption(item: SelectItem): ReactNode {
169
- return item.id === ALL_KEY ? (
170
- <AllPill />
171
- ) : (
172
- render({ label: item.name, value: item.id })
173
- );
134
+ return item.id === ALL_KEY ? <AllPill /> : render({ label: item.name, value: item.id });
174
135
  }
175
136
  return FilterOption;
176
137
  }, [column.filterRender]);
@@ -100,10 +100,7 @@ function buildDirectoryTreeRecursive(
100
100
  _Object: obj,
101
101
  };
102
102
  currentDir.push(existingDir);
103
- } else if (
104
- isImageFile(obj.Key ?? "") &&
105
- !isImageFile(existingDir._Object?.Key ?? "")
106
- ) {
103
+ } else if (isImageFile(obj.Key ?? "") && !isImageFile(existingDir._Object?.Key ?? "")) {
107
104
  existingDir._Object = obj;
108
105
  }
109
106
 
@@ -118,10 +115,7 @@ function buildDirectoryTreeRecursive(
118
115
  }
119
116
 
120
117
  /** Depth-first search for a node by its `id`. */
121
- export function findNodeById(
122
- nodes: TreeNode[],
123
- id: string,
124
- ): TreeNode | undefined {
118
+ export function findNodeById(nodes: TreeNode[], id: string): TreeNode | undefined {
125
119
  for (const node of nodes) {
126
120
  if (node.id === id) return node;
127
121
  if (node.children.length > 0) {
@@ -136,23 +130,16 @@ export function findNodeById(
136
130
  export function computeDirectorySize(node: TreeNode): number {
137
131
  if (node.type === "file") return node._Object?.Size ?? 0;
138
132
  if (node.children.length === 0) return node._Object?.Size ?? 0;
139
- return node.children.reduce(
140
- (sum, child) => sum + computeDirectorySize(child),
141
- 0,
142
- );
133
+ return node.children.reduce((sum, child) => sum + computeDirectorySize(child), 0);
143
134
  }
144
135
 
145
136
  /** Latest LastModified timestamp under a directory node. */
146
137
  export function computeDirectoryLastModified(node: TreeNode): number {
147
138
  if (node.type === "file") {
148
- return node._Object?.LastModified
149
- ? new Date(node._Object.LastModified).getTime()
150
- : 0;
139
+ return node._Object?.LastModified ? new Date(node._Object.LastModified).getTime() : 0;
151
140
  }
152
141
  if (node.children.length === 0) {
153
- return node._Object?.LastModified
154
- ? new Date(node._Object.LastModified).getTime()
155
- : 0;
142
+ return node._Object?.LastModified ? new Date(node._Object.LastModified).getTime() : 0;
156
143
  }
157
144
  return node.children.reduce(
158
145
  (max, child) => Math.max(max, computeDirectoryLastModified(child)),
@@ -182,13 +169,7 @@ export function buildDirectoryTree(
182
169
  const pathName = obj.Key.replace(prefix || "", "");
183
170
  const pathSegments = pathName.split("/");
184
171
 
185
- buildDirectoryTreeRecursive(
186
- root,
187
- pathSegments,
188
- obj,
189
- connectionName,
190
- basePath,
191
- );
172
+ buildDirectoryTreeRecursive(root, pathSegments, obj, connectionName, basePath);
192
173
  });
193
174
 
194
175
  return root;
@@ -20,15 +20,13 @@ type NodeAccessor = (node: TreeNode) => string;
20
20
 
21
21
  const fileAccessors: Record<string, NodeAccessor> = {
22
22
  name: (node) => node.name,
23
- file_type: (node) =>
24
- node.type === "file" ? getFileType(node.name) : "Directory",
23
+ file_type: (node) => (node.type === "file" ? getFileType(node.name) : "Directory"),
25
24
  };
26
25
 
27
26
  function makeConnectionAccessors(
28
27
  connections: Record<string, Connection>,
29
28
  ): Record<string, NodeAccessor> {
30
- const config = (node: TreeNode) =>
31
- connections[node.connectionName]?.connectionConfig;
29
+ const config = (node: TreeNode) => connections[node.connectionName]?.connectionConfig;
32
30
  return {
33
31
  name: (node) => node.name,
34
32
  provider: (node) => config(node)?.provider ?? "",
@@ -41,9 +39,7 @@ export function getNodeAccessors(
41
39
  kind: DirectoryKind,
42
40
  connections: Record<string, Connection> = {},
43
41
  ): Record<string, NodeAccessor> {
44
- return kind === "connections"
45
- ? makeConnectionAccessors(connections)
46
- : fileAccessors;
42
+ return kind === "connections" ? makeConnectionAccessors(connections) : fileAccessors;
47
43
  }
48
44
 
49
45
  /**
@@ -51,10 +47,7 @@ export function getNodeAccessors(
51
47
  * Filtering is applied recursively so that hidden children inside visible
52
48
  * directories are also removed (required for the tree view).
53
49
  */
54
- export function filterHiddenNodes(
55
- nodes: TreeNode[],
56
- showHidden: boolean,
57
- ): TreeNode[] {
50
+ export function filterHiddenNodes(nodes: TreeNode[], showHidden: boolean): TreeNode[] {
58
51
  if (showHidden) return nodes;
59
52
 
60
53
  return nodes
@@ -9,11 +9,7 @@ import { RouteModal } from "~/components/RouteModal";
9
9
  * Provides information and a download link for a Cyberduck connection profile.
10
10
  * Opens via `?modal=cyberduck&connectionName=my-connection`.
11
11
  */
12
- export default function CyberduckModal({
13
- onClose,
14
- }: {
15
- onClose: () => void;
16
- }) {
12
+ export default function CyberduckModal({ onClose }: { onClose: () => void }) {
17
13
  const [searchParams, setSearchParams] = useSearchParams();
18
14
  const connectionName = searchParams.get("connectionName");
19
15
 
@@ -34,14 +30,13 @@ export default function CyberduckModal({
34
30
  return (
35
31
  <RouteModal title="Access with Cyberduck" onClose={handleClose}>
36
32
  <p className="text-(--color-text-secondary)">
37
- Cyberduck is a free desktop application that allows you to browse and
38
- manage your cloud storage files with a familiar file manager interface.
33
+ Cyberduck is a free desktop application that allows you to browse and manage your cloud
34
+ storage files with a familiar file manager interface.
39
35
  </p>
40
36
 
41
37
  <p className="text-(--color-text-secondary)">
42
- Download a pre-configured connection profile for{" "}
43
- <strong>{connectionName}</strong> that will automatically set up your
44
- authentication and bucket access in Cyberduck.
38
+ Download a pre-configured connection profile for <strong>{connectionName}</strong> that will
39
+ automatically set up your authentication and bucket access in Cyberduck.
45
40
  </p>
46
41
 
47
42
  <div className="bg-(--color-surface-subtle) border border-(--color-border-default) rounded-sm px-4 py-2 flex flex-col gap-2">
@@ -49,11 +44,7 @@ export default function CyberduckModal({
49
44
  <ol className="list-decimal list-inside space-y-1 text-sm text-(--color-text-secondary)">
50
45
  <li>
51
46
  Download and install{" "}
52
- <Link
53
- href="https://cyberduck.io/download/"
54
- target="_blank"
55
- rel="noopener noreferrer"
56
- >
47
+ <Link href="https://cyberduck.io/download/" target="_blank" rel="noopener noreferrer">
57
48
  Cyberduck
58
49
  </Link>{" "}
59
50
  (version 8.7.0 or later)
@@ -4,11 +4,7 @@ import { useParams, useSearchParams } from "react-router";
4
4
 
5
5
  import { RouteModal } from "~/components/RouteModal";
6
6
 
7
- export default function FileInfoModal({
8
- onClose,
9
- }: {
10
- onClose: (extraKeys?: string[]) => void;
11
- }) {
7
+ export default function FileInfoModal({ onClose }: { onClose: (extraKeys?: string[]) => void }) {
12
8
  const [searchParams] = useSearchParams();
13
9
  const { name: connectionName } = useParams();
14
10
 
@@ -44,11 +44,7 @@ export const useLayoutStore = create<LayoutStore>()(
44
44
  ),
45
45
  showFilters: false,
46
46
  toggleShowFilters: () =>
47
- set(
48
- (state) => ({ showFilters: !state.showFilters }),
49
- false,
50
- "toggleShowFilters",
51
- ),
47
+ set((state) => ({ showFilters: !state.showFilters }), false, "toggleShowFilters"),
52
48
  headerSlot: null,
53
49
  setHeaderSlot: (headerSlot) => set({ headerSlot }),
54
50
  }),
@@ -61,34 +57,18 @@ export const useLayoutStore = create<LayoutStore>()(
61
57
  {
62
58
  0: (state) => {
63
59
  const s = state as { viewMode?: string };
64
- const OLD_VALID = [
65
- "list",
66
- "list-wide",
67
- "grid-sm",
68
- "grid-md",
69
- "grid-lg",
70
- ];
60
+ const OLD_VALID = ["list", "list-wide", "grid-sm", "grid-md", "grid-lg"];
71
61
  return {
72
- viewMode: OLD_VALID.includes(s?.viewMode ?? "")
73
- ? (s.viewMode as string)
74
- : "grid",
62
+ viewMode: OLD_VALID.includes(s?.viewMode ?? "") ? (s.viewMode as string) : "grid",
75
63
  showHiddenFiles: false,
76
64
  showFilters: false,
77
65
  };
78
66
  },
79
67
  1: (state) => {
80
68
  const s = state as { viewMode?: string };
81
- const OLD_VALID = [
82
- "list",
83
- "list-wide",
84
- "grid-sm",
85
- "grid-md",
86
- "grid-lg",
87
- ];
69
+ const OLD_VALID = ["list", "list-wide", "grid-sm", "grid-md", "grid-lg"];
88
70
  return {
89
- viewMode: OLD_VALID.includes(s?.viewMode ?? "")
90
- ? (s.viewMode as string)
91
- : "grid",
71
+ viewMode: OLD_VALID.includes(s?.viewMode ?? "") ? (s.viewMode as string) : "grid",
92
72
  showHiddenFiles: false,
93
73
  showFilters: false,
94
74
  };
@@ -40,10 +40,7 @@ export const GlobalSearch = () => {
40
40
  };
41
41
 
42
42
  const handleClickOutside = (event: Event) => {
43
- if (
44
- parentRef.current &&
45
- !parentRef.current.contains(event.target as Node)
46
- ) {
43
+ if (parentRef.current && !parentRef.current.contains(event.target as Node)) {
47
44
  setShowResults(false);
48
45
  }
49
46
  };
@@ -17,13 +17,7 @@ export function SearchBar({ value, onChange, onClear }: SearchBarProps) {
17
17
  placeholder="Search..."
18
18
  />
19
19
 
20
- {value ? (
21
- <IconButton
22
- icon={X}
23
- onPress={onClear}
24
- aria-label="Clear search"
25
- />
26
- ) : null}
20
+ {value ? <IconButton icon={X} onPress={onClear} aria-label="Clear search" /> : null}
27
21
  </div>
28
22
  );
29
23
  }
@@ -10,7 +10,6 @@ interface SuggestionsProps {
10
10
  showResults: boolean;
11
11
  }
12
12
  export const Suggestions = ({ nodes, showResults }: SuggestionsProps) => {
13
-
14
13
  return (
15
14
  <AnimatePresence>
16
15
  {showResults && (
@@ -1,8 +1,4 @@
1
- import type {
2
- FormatExtension,
3
- FormatHandler,
4
- FormatRegistry,
5
- } from "@cytario/plugin-api";
1
+ import type { FormatExtension, FormatHandler, FormatRegistry } from "@cytario/plugin-api";
6
2
  import { getExtension } from "~/utils/fileType";
7
3
 
8
4
  /**
@@ -87,8 +83,7 @@ class FormatRegistryImpl {
87
83
  */
88
84
  scopedFor(pluginName: string): FormatRegistry {
89
85
  return {
90
- register: (extension, handler) =>
91
- this.add(pluginName, extension, handler),
86
+ register: (extension, handler) => this.add(pluginName, extension, handler),
92
87
  };
93
88
  }
94
89
 
@@ -97,15 +92,9 @@ class FormatRegistryImpl {
97
92
  * same plugin name) is a no-op so HMR re-runs do not throw.
98
93
  * Cross-plugin overlap on any key throws `DuplicateRegistrationError`.
99
94
  */
100
- add(
101
- pluginName: string,
102
- extension: FormatExtension,
103
- handler: FormatHandler,
104
- ): void {
95
+ add(pluginName: string, extension: FormatExtension, handler: FormatHandler): void {
105
96
  const keys = normalizeKeys(extension);
106
- const existing = this.registrations.find((r) =>
107
- keysCollide(r.keys, keys),
108
- );
97
+ const existing = this.registrations.find((r) => keysCollide(r.keys, keys));
109
98
  if (existing) {
110
99
  if (existing.pluginName === pluginName) return;
111
100
  throw new DuplicateRegistrationError(
@@ -136,9 +125,7 @@ class FormatRegistryImpl {
136
125
  }
137
126
  }
138
127
  }
139
- throw new UnknownFormatError(
140
- `No format handler registered for URL: ${url}`,
141
- );
128
+ throw new UnknownFormatError(`No format handler registered for URL: ${url}`);
142
129
  }
143
130
 
144
131
  list(): readonly Registration[] {