@vitessce/vit-s 3.9.0 → 3.9.2

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.
@@ -1,4 +1,4 @@
1
1
  export function DebugWindow({ debugErrors }: {
2
2
  debugErrors: any;
3
- }): import(".pnpm/@types+react@19.1.6/node_modules/@types/react").JSX.Element;
3
+ }): import("react").JSX.Element;
4
4
  //# sourceMappingURL=DebugWindow.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"DebugWindow.d.ts","sourceRoot":"","sources":["../src/DebugWindow.js"],"names":[],"mappings":"AAIA;;8EA8BC"}
1
+ {"version":3,"file":"DebugWindow.d.ts","sourceRoot":"","sources":["../src/DebugWindow.js"],"names":[],"mappings":"AAIA;;gCA8BC"}
@@ -1 +1 @@
1
- {"version":3,"file":"TitleInfo.d.ts","sourceRoot":"","sources":["../src/TitleInfo.js"],"names":[],"mappings":"AAmNA,mDAoEC"}
1
+ {"version":3,"file":"TitleInfo.d.ts","sourceRoot":"","sources":["../src/TitleInfo.js"],"names":[],"mappings":"AAmNA,mDAwEC"}
@@ -102,15 +102,17 @@ export function TitleInfo(props) {
102
102
  const { title, info, children, isScroll, isSpatial, removeGridComponent, urls, isReady, options, closeButtonVisible = true, downloadButtonVisible = true, helpText, withPadding = true, errors: errorsProp, } = props;
103
103
  const errors = errorsProp?.filter(Boolean);
104
104
  const { classes } = useTitleStyles();
105
+ const hasInfoText = Boolean(info);
105
106
  return (
106
107
  // d-flex without wrapping div is not always full height; I don't understand the root cause.
107
- _jsxs(_Fragment, { children: [_jsxs("div", { className: classes.title, role: "banner", children: [_jsx("div", { className: classes.titleLeft, role: "heading", "aria-level": "1", children: title }), _jsx("div", { className: classes.titleInfo, title: info, role: "note", children: info }), _jsxs("div", { className: classes.titleButtons, role: "toolbar", "aria-label": "Plot options and controls", children: [_jsx(PlotOptions, { options: options }), downloadButtonVisible ? (_jsx(DownloadOptions, { urls: urls })) : null, Array.isArray(errors) && errors.length > 0 ? (_jsx(ErrorInfo, { errors: errors })) : null, helpText ? (_jsx(HelpButton, { helpText: helpText })) : null, closeButtonVisible && removeGridComponent ? (_jsx(ClosePaneButton, { removeGridComponent: removeGridComponent })) : null] })] }), _jsxs("div", { className: clsx(TOOLTIP_ANCESTOR, classes.card, {
108
+ _jsxs(_Fragment, { children: [_jsxs("div", { className: classes.title, role: "banner", children: [_jsx("div", { className: classes.titleLeft, role: "heading", "aria-level": "1", children: title }), _jsxs("div", { className: classes.titleButtons, role: "toolbar", "aria-label": "Plot options and controls", children: [_jsx(PlotOptions, { options: options }), downloadButtonVisible ? (_jsx(DownloadOptions, { urls: urls })) : null, Array.isArray(errors) && errors.length > 0 ? (_jsx(ErrorInfo, { errors: errors })) : null, helpText ? (_jsx(HelpButton, { helpText: helpText })) : null, closeButtonVisible && removeGridComponent ? (_jsx(ClosePaneButton, { removeGridComponent: removeGridComponent })) : null] })] }), _jsxs("div", { className: clsx(TOOLTIP_ANCESTOR, classes.card, {
108
109
  [classes.scrollCard]: isScroll,
109
110
  [classes.spatialCard]: isSpatial,
110
111
  [classes.noScrollCard]: !isScroll && !isSpatial,
111
112
  [classes.noPaddingCard]: !withPadding,
112
113
  [classes.paddingCard]: withPadding,
113
- }), "aria-busy": !isReady, role: "main", children: [!isReady ? _jsx(LoadingIndicator, {}) : null, children] })] })
114
+ [classes.cardWithoutInfoText]: !hasInfoText,
115
+ }), "aria-busy": !isReady, role: "main", children: [!isReady ? _jsx(LoadingIndicator, {}) : null, children] }), hasInfoText ? (_jsx("div", { className: classes.infoText, title: info, role: "note", children: info })) : null] })
114
116
  // "pl-2" only matters when the window is very narrow.
115
117
  );
116
118
  }
@@ -1 +1 @@
1
- {"version":3,"file":"VitS.d.ts","sourceRoot":"","sources":["../src/VitS.js"],"names":[],"mappings":"AAqCA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,4BApCG;IAAsB,MAAM,EAApB,MAAM;IAGkB,SAAS,EAAjC,SAAS,GAAC,MAAM;IACF,MAAM,EAApB,MAAM;IACQ,KAAK,EAAnB,MAAM;IAEU,MAAM;IACN,cAAc;IAEd,cAAc;IAEf,cAAc,EAA7B,OAAO;IAIQ,sBAAsB,EAArC,OAAO;IAGY,GAAG,EAAtB,IAAI,GAAC,MAAM;IAGW,cAAc,EAApC,SAAS,GAAC,IAAI;IAEC,kBAAkB,EAAjC,OAAO;IAEM,SAAS,EAAtB,KAAK;IACQ,SAAS,EAAtB,KAAK;IACQ,cAAc,EAA3B,KAAK;IACQ,iBAAiB,EAA9B,KAAK;IACc,OAAO,EAA1B,IAAI,GAAC,MAAM;IACI,QAAQ,EAAvB,OAAO;IACQ,SAAS,EAAxB,OAAO;IACY,QAAQ,EAA3B,IAAI,GAAC,MAAM;CAErB,eA4PA"}
1
+ {"version":3,"file":"VitS.d.ts","sourceRoot":"","sources":["../src/VitS.js"],"names":[],"mappings":"AAwCA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,4BApCG;IAAsB,MAAM,EAApB,MAAM;IAGkB,SAAS,EAAjC,SAAS,GAAC,MAAM;IACF,MAAM,EAApB,MAAM;IACQ,KAAK,EAAnB,MAAM;IAEU,MAAM;IACN,cAAc;IAEd,cAAc;IAEf,cAAc,EAA7B,OAAO;IAIQ,sBAAsB,EAArC,OAAO;IAGY,GAAG,EAAtB,IAAI,GAAC,MAAM;IAGW,cAAc,EAApC,SAAS,GAAC,IAAI;IAEC,kBAAkB,EAAjC,OAAO;IAEM,SAAS,EAAtB,KAAK;IACQ,SAAS,EAAtB,KAAK;IACQ,cAAc,EAA3B,KAAK;IACQ,iBAAiB,EAA9B,KAAK;IACc,OAAO,EAA1B,IAAI,GAAC,MAAM;IACI,QAAQ,EAAvB,OAAO;IACQ,SAAS,EAAxB,OAAO;IACY,QAAQ,EAA3B,IAAI,GAAC,MAAM;CAErB,eAsSA"}
package/dist-tsc/VitS.js CHANGED
@@ -1,6 +1,9 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import React, { useState, useEffect, useMemo, useCallback, useLayoutEffect } from 'react';
3
3
  import { ThemeProvider, CacheProvider, createCache, } from '@vitessce/styles';
4
+ // eslint-disable-next-line import/no-unresolved
5
+ import ZipFileStore from '@zarrita/storage/zip';
6
+ import { transformEntriesForZipFileStore } from '@vitessce/zarr-utils';
4
7
  import { QueryCache, QueryClient, QueryClientProvider, } from '@tanstack/react-query';
5
8
  import { isEqual } from 'lodash-es';
6
9
  import { buildConfigSchema, latestConfigSchema } from '@vitessce/schemas';
@@ -54,7 +57,7 @@ import { AsyncFunctionsContext } from './contexts.js';
54
57
  * provided by the parent.
55
58
  */
56
59
  export function VitS(props) {
57
- const { config, stores, rowHeight, height, theme, onWarn, onConfigChange, onLoaderChange, validateConfig = true, validateOnConfigChange = false, isBounded = false, uid = null, styleContainer, remountOnUidChange = true, viewTypes: viewTypesProp, fileTypes: fileTypesProp, jointFileTypes: jointFileTypesProp, coordinationTypes: coordinationTypesProp, asyncFunctions: asyncFunctionsProp, warning, pageMode = false, children, debugMode = DEFAULT_DEBUG_MODE, logLevel = DEFAULT_LOG_LEVEL, } = props;
60
+ const { config, stores: storesProp, rowHeight, height, theme, onWarn, onConfigChange, onLoaderChange, validateConfig = true, validateOnConfigChange = false, isBounded = false, uid = null, styleContainer, remountOnUidChange = true, viewTypes: viewTypesProp, fileTypes: fileTypesProp, jointFileTypes: jointFileTypesProp, coordinationTypes: coordinationTypesProp, asyncFunctions: asyncFunctionsProp, warning, pageMode = false, children, debugMode = DEFAULT_DEBUG_MODE, logLevel = DEFAULT_LOG_LEVEL, } = props;
58
61
  // eslint-disable-next-line no-unused-vars
59
62
  const [debugErrors, setDebugErrors] = useState([]);
60
63
  const viewTypes = useMemo(() => (viewTypesProp || []), [viewTypesProp]);
@@ -81,6 +84,41 @@ export function VitS(props) {
81
84
  return JSON.stringify(config);
82
85
  }, [config]);
83
86
  const pluginSpecificConfigSchema = useMemo(() => buildConfigSchema(fileTypes, jointFileTypes, coordinationTypes, viewTypes), [viewTypes, fileTypes, jointFileTypes, coordinationTypes]);
87
+ // Pre-create and cache ZipFileStore instances for all zip URLs
88
+ // This ensures that multiple loaders pointing to the same zip URL
89
+ // share the same zip store, preserving HTTP2 multiplexing.
90
+ const mergedStores = useMemo(() => {
91
+ // Note: this logic could alternatively be located inside createLoaders,
92
+ // and could initialize all stores (not only zip stores).
93
+ const result = storesProp ?? {};
94
+ // Collect unique zip URLs and their requestInit from the config
95
+ // Maps URL -> requestInit for store creation with authentication headers
96
+ const urlMap = new Map();
97
+ if (Array.isArray(config.datasets)) {
98
+ config.datasets.forEach((dataset) => {
99
+ dataset.files?.forEach((file) => {
100
+ if (file.fileType?.endsWith('.zip') && file.url) {
101
+ // Use the first requestInit encountered for each URL.
102
+ if (!urlMap.has(file.url)) {
103
+ urlMap.set(file.url, file.requestInit);
104
+ }
105
+ }
106
+ });
107
+ });
108
+ }
109
+ // Create stores for new zip URLs with their requestInit
110
+ urlMap.forEach((requestInit, url) => {
111
+ // Only create a new store if one does not already exist for this URL.
112
+ if (!result[url]) {
113
+ result[url] = ZipFileStore.fromUrl(url, {
114
+ overrides: requestInit,
115
+ transformEntries: transformEntriesForZipFileStore,
116
+ });
117
+ }
118
+ });
119
+ return result;
120
+ // TODO: also depend on storesProp here?
121
+ }, [configKey]);
84
122
  // Process the view config and memoize the result:
85
123
  // - Validate.
86
124
  // - Upgrade, if legacy schema.
@@ -176,7 +214,7 @@ export function VitS(props) {
176
214
  // Initialize the view config and loaders in the global state.
177
215
  const createViewConfigStoreClosure = useCallback(() => {
178
216
  if (success) {
179
- const loaders = createLoaders(configOrWarning.datasets, configOrWarning.description, fileTypes, coordinationTypes, stores, queryClient);
217
+ const loaders = createLoaders(configOrWarning.datasets, configOrWarning.description, fileTypes, coordinationTypes, mergedStores, queryClient);
180
218
  return createViewConfigStore(loaders, configOrWarning);
181
219
  }
182
220
  // No config found, so clear the loaders.
@@ -189,5 +227,5 @@ export function VitS(props) {
189
227
  if (debugMode && debugErrors.length > 0) {
190
228
  return (_jsx(CacheProvider, { value: muiCache, children: _jsx(ThemeProvider, { theme: muiTheme[theme], children: _jsx(DebugWindow, { debugErrors: debugErrors }) }) }));
191
229
  }
192
- return success ? (_jsx(CacheProvider, { value: muiCache, children: _jsx(ThemeProvider, { theme: muiTheme[theme], children: _jsx(QueryClientProvider, { client: queryClient, children: _jsx(ViewConfigProvider, { createStore: createViewConfigStoreClosure, children: _jsx(AuxiliaryProvider, { createStore: createAuxiliaryStore, children: _jsxs(AsyncFunctionsContext.Provider, { value: asyncFunctions, children: [_jsx(VitessceGrid, { pageMode: pageMode, success: success, configKey: configKey, viewTypes: viewTypes, fileTypes: fileTypes, coordinationTypes: coordinationTypes, config: configOrWarning, rowHeight: rowHeight, height: height, theme: theme, isBounded: isBounded, stores: stores, queryClient: queryClient, children: children }), _jsx(CallbackPublisher, { onWarn: onWarn, onConfigChange: onConfigChange, onLoaderChange: onLoaderChange, validateOnConfigChange: validateOnConfigChange, pluginSpecificConfigSchema: pluginSpecificConfigSchema })] }) }) }, (remountOnUidChange ? configKey : undefined)) }) }) })) : (_jsx(CacheProvider, { value: muiCache, children: _jsx(ThemeProvider, { theme: muiTheme[theme], children: _jsx(Warning, { ...configOrWarning }) }) }));
230
+ return success ? (_jsx(CacheProvider, { value: muiCache, children: _jsx(ThemeProvider, { theme: muiTheme[theme], children: _jsx(QueryClientProvider, { client: queryClient, children: _jsx(ViewConfigProvider, { createStore: createViewConfigStoreClosure, children: _jsx(AuxiliaryProvider, { createStore: createAuxiliaryStore, children: _jsxs(AsyncFunctionsContext.Provider, { value: asyncFunctions, children: [_jsx(VitessceGrid, { pageMode: pageMode, success: success, configKey: configKey, viewTypes: viewTypes, fileTypes: fileTypes, coordinationTypes: coordinationTypes, config: configOrWarning, rowHeight: rowHeight, height: height, theme: theme, isBounded: isBounded, stores: mergedStores, queryClient: queryClient, children: children }), _jsx(CallbackPublisher, { onWarn: onWarn, onConfigChange: onConfigChange, onLoaderChange: onLoaderChange, validateOnConfigChange: validateOnConfigChange, pluginSpecificConfigSchema: pluginSpecificConfigSchema })] }) }) }, (remountOnUidChange ? configKey : undefined)) }) }) })) : (_jsx(CacheProvider, { value: muiCache, children: _jsx(ThemeProvider, { theme: muiTheme[theme], children: _jsx(Warning, { ...configOrWarning }) }) }));
193
231
  }
@@ -1,2 +1,2 @@
1
- export function Warning(props: any): import(".pnpm/@types+react@19.1.6/node_modules/@types/react").JSX.Element;
1
+ export function Warning(props: any): import("react").JSX.Element;
2
2
  //# sourceMappingURL=Warning.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"Warning.d.ts","sourceRoot":"","sources":["../src/Warning.js"],"names":[],"mappings":"AAIA,+GAoBC"}
1
+ {"version":3,"file":"Warning.d.ts","sourceRoot":"","sources":["../src/Warning.js"],"names":[],"mappings":"AAIA,iEAoBC"}
@@ -96,7 +96,7 @@ const gridLayoutStyles = {
96
96
  },
97
97
  '.react-grid-item > .react-resizable-handle.react-resizable-handle-se': {
98
98
  bottom: '0',
99
- right: '0',
99
+ right: '10px',
100
100
  cursor: 'se-resize',
101
101
  },
102
102
  '.react-grid-item > .react-resizable-handle.react-resizable-handle-nw': {
@@ -75,6 +75,7 @@ const lightPalette = {
75
75
  gridLayoutBackground: '#FFFFFF',
76
76
  paperBackground: '#FFFFFF',
77
77
  cardBorder: 'rgba(241, 241, 241, 0.125)',
78
+ cardInfoBorder: '#ccc',
78
79
  tooltipText: 'rgba(0, 0, 0, 0.87)',
79
80
  };
80
81
  export const muiTheme = {
@@ -100,6 +101,7 @@ export const muiTheme = {
100
101
  gridLayoutBackground: '#333333',
101
102
  paperBackground: '#2b2b2b',
102
103
  cardBorder: 'rgba(0, 0, 0, 0.125)',
104
+ cardInfoBorder: '#222222',
103
105
  tooltipText: '#FFFFFF',
104
106
  ...globalColors,
105
107
  },
@@ -109,6 +111,7 @@ export const muiTheme = {
109
111
  light: createTheme({
110
112
  palette: {
111
113
  ...lightPalette,
114
+ cardInfoBorder: '#F1F1F1',
112
115
  ...globalColors,
113
116
  },
114
117
  ...sharedThemeOptions,
@@ -126,6 +129,6 @@ export const muiTheme = {
126
129
  ...globalColors,
127
130
  },
128
131
  ...sharedThemeOptions,
129
- cardBorderSize: '2px',
132
+ cardBorderSize: '1px',
130
133
  }),
131
134
  };
@@ -1 +1 @@
1
- {"version":3,"file":"title-styles.d.ts","sourceRoot":"","sources":["../src/title-styles.js"],"names":[],"mappings":"AAEA;;cAyB0B,CAAC;;;kBAMR,eACC;gBAEd,WAAK;eACP,WAEJ;EAuCI"}
1
+ {"version":3,"file":"title-styles.d.ts","sourceRoot":"","sources":["../src/title-styles.js"],"names":[],"mappings":"AAEA;;cAyBO,CAAC;;;kBAE+B,eACvC;gBAAyB,WAAU;eAAsB,WACpD;EAyDD"}
@@ -13,18 +13,23 @@ export const useTitleStyles = makeStyles()(theme => ({
13
13
  textOverflow: 'ellipsis',
14
14
  whiteSpace: 'nowrap',
15
15
  },
16
- titleInfo: {
16
+ infoText: {
17
+ color: theme.palette.primaryForeground,
17
18
  width: '100%',
18
19
  textOverflow: 'ellipsis',
19
20
  whiteSpace: 'nowrap',
20
21
  overflow: 'hidden',
21
- fontSize: '80% !important',
22
+ fontSize: '60% !important',
22
23
  opacity: '.8',
23
- padding: '0 4px',
24
+ padding: '0',
24
25
  justifyContent: 'center',
25
- lineHeight: '25px !important',
26
- flexShrink: '1',
27
- textAlign: 'right !important',
26
+ textAlign: 'center !important',
27
+ borderTop: '0px',
28
+ borderBottom: `1px solid ${theme.palette.cardInfoBorder}`,
29
+ borderLeft: `1px solid ${theme.palette.cardInfoBorder}`,
30
+ borderRight: `1px solid ${theme.palette.cardInfoBorder}`,
31
+ borderBottomLeftRadius: '4px',
32
+ borderBottomRightRadius: '4px',
28
33
  },
29
34
  titleButtons: {
30
35
  display: 'flex',
@@ -40,15 +45,20 @@ export const useTitleStyles = makeStyles()(theme => ({
40
45
  border: `${theme.cardBorderSize} solid ${theme.palette.cardBorder}`,
41
46
  flex: '1 1 auto',
42
47
  minHeight: '1px',
43
- marginTop: '8px',
44
- marginBottom: '8px',
48
+ marginTop: '0',
49
+ marginBottom: '0',
45
50
  position: 'relative',
46
51
  display: 'flex',
47
52
  flexDirection: 'column',
48
53
  minWidth: '0',
49
54
  wordWrap: 'break-word',
50
55
  backgroundClip: 'border-box',
51
- borderRadius: '4px',
56
+ borderTopLeftRadius: '4px',
57
+ borderTopRightRadius: '4px',
58
+ },
59
+ cardWithoutInfoText: {
60
+ borderBottomLeftRadius: '4px',
61
+ borderBottomRightRadius: '4px',
52
62
  },
53
63
  paddingCard: {
54
64
  padding: '12px',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vitessce/vit-s",
3
- "version": "3.9.0",
3
+ "version": "3.9.2",
4
4
  "author": "HIDIVE Lab at HMS",
5
5
  "homepage": "http://vitessce.io",
6
6
  "repository": {
@@ -17,6 +17,7 @@
17
17
  ],
18
18
  "dependencies": {
19
19
  "@tanstack/react-query": "^5.80.2",
20
+ "@zarrita/storage": "0.1.3",
20
21
  "clsx": "^1.1.1",
21
22
  "d3-array": "^2.4.0",
22
23
  "fast-deep-equal": "^3.1.3",
@@ -30,16 +31,17 @@
30
31
  "react-error-boundary": "^5.0.0",
31
32
  "@placemarkio/flat-drop-files": "^1.0.2",
32
33
  "zarrita": "0.5.4",
33
- "@vitessce/styles": "3.9.0",
34
- "@vitessce/error": "3.9.0",
35
- "@vitessce/constants-internal": "3.9.0",
36
- "@vitessce/abstract": "3.9.0",
37
- "@vitessce/utils": "3.9.0",
38
- "@vitessce/schemas": "3.9.0",
39
- "@vitessce/sets-utils": "3.9.0",
40
- "@vitessce/plugins": "3.9.0",
41
- "@vitessce/config": "3.9.0",
42
- "@vitessce/globals": "3.9.0"
34
+ "@vitessce/styles": "3.9.2",
35
+ "@vitessce/abstract": "3.9.2",
36
+ "@vitessce/error": "3.9.2",
37
+ "@vitessce/constants-internal": "3.9.2",
38
+ "@vitessce/plugins": "3.9.2",
39
+ "@vitessce/schemas": "3.9.2",
40
+ "@vitessce/sets-utils": "3.9.2",
41
+ "@vitessce/zarr-utils": "3.9.2",
42
+ "@vitessce/config": "3.9.2",
43
+ "@vitessce/utils": "3.9.2",
44
+ "@vitessce/globals": "3.9.2"
43
45
  },
44
46
  "devDependencies": {
45
47
  "@testing-library/jest-dom": "^6.6.3",
@@ -48,7 +50,7 @@
48
50
  "react-dom": "18.3.1",
49
51
  "vite": "^7.0.0",
50
52
  "vitest": "^3.1.4",
51
- "@vitessce/types": "3.9.0"
53
+ "@vitessce/types": "3.9.2"
52
54
  },
53
55
  "peerDependencies": {
54
56
  "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
package/src/TitleInfo.js CHANGED
@@ -219,6 +219,7 @@ export function TitleInfo(props) {
219
219
  const errors = errorsProp?.filter(Boolean);
220
220
 
221
221
  const { classes } = useTitleStyles();
222
+ const hasInfoText = Boolean(info);
222
223
 
223
224
  return (
224
225
  // d-flex without wrapping div is not always full height; I don't understand the root cause.
@@ -227,9 +228,6 @@ export function TitleInfo(props) {
227
228
  <div className={classes.titleLeft} role="heading" aria-level="1">
228
229
  {title}
229
230
  </div>
230
- <div className={classes.titleInfo} title={info} role="note">
231
- {info}
232
- </div>
233
231
  <div className={classes.titleButtons} role="toolbar" aria-label="Plot options and controls">
234
232
  <PlotOptions
235
233
  options={options}
@@ -266,6 +264,7 @@ export function TitleInfo(props) {
266
264
  [classes.noScrollCard]: !isScroll && !isSpatial,
267
265
  [classes.noPaddingCard]: !withPadding,
268
266
  [classes.paddingCard]: withPadding,
267
+ [classes.cardWithoutInfoText]: !hasInfoText,
269
268
  },
270
269
  )}
271
270
  aria-busy={!isReady}
@@ -274,6 +273,11 @@ export function TitleInfo(props) {
274
273
  { !isReady ? <LoadingIndicator /> : null }
275
274
  {children}
276
275
  </div>
276
+ {hasInfoText ? (
277
+ <div className={classes.infoText} title={info} role="note">
278
+ {info}
279
+ </div>
280
+ ) : null}
277
281
  </>
278
282
  // "pl-2" only matters when the window is very narrow.
279
283
  );
package/src/VitS.js CHANGED
@@ -4,6 +4,9 @@ import {
4
4
  CacheProvider,
5
5
  createCache,
6
6
  } from '@vitessce/styles';
7
+ // eslint-disable-next-line import/no-unresolved
8
+ import ZipFileStore from '@zarrita/storage/zip';
9
+ import { transformEntriesForZipFileStore } from '@vitessce/zarr-utils';
7
10
  import {
8
11
  QueryCache,
9
12
  QueryClient,
@@ -77,7 +80,7 @@ import { AsyncFunctionsContext } from './contexts.js';
77
80
  export function VitS(props) {
78
81
  const {
79
82
  config,
80
- stores,
83
+ stores: storesProp,
81
84
  rowHeight,
82
85
  height,
83
86
  theme,
@@ -104,6 +107,7 @@ export function VitS(props) {
104
107
 
105
108
  // eslint-disable-next-line no-unused-vars
106
109
  const [debugErrors, setDebugErrors] = useState([]);
110
+
107
111
  const viewTypes = useMemo(() => (viewTypesProp || []), [viewTypesProp]);
108
112
  const fileTypes = useMemo(() => (fileTypesProp || []), [fileTypesProp]);
109
113
  const jointFileTypes = useMemo(
@@ -143,6 +147,46 @@ export function VitS(props) {
143
147
  viewTypes,
144
148
  ), [viewTypes, fileTypes, jointFileTypes, coordinationTypes]);
145
149
 
150
+
151
+ // Pre-create and cache ZipFileStore instances for all zip URLs
152
+ // This ensures that multiple loaders pointing to the same zip URL
153
+ // share the same zip store, preserving HTTP2 multiplexing.
154
+ const mergedStores = useMemo(() => {
155
+ // Note: this logic could alternatively be located inside createLoaders,
156
+ // and could initialize all stores (not only zip stores).
157
+ const result = storesProp ?? {};
158
+
159
+ // Collect unique zip URLs and their requestInit from the config
160
+ // Maps URL -> requestInit for store creation with authentication headers
161
+ const urlMap = new Map();
162
+ if (Array.isArray(config.datasets)) {
163
+ config.datasets.forEach((dataset) => {
164
+ dataset.files?.forEach((file) => {
165
+ if (file.fileType?.endsWith('.zip') && file.url) {
166
+ // Use the first requestInit encountered for each URL.
167
+ if (!urlMap.has(file.url)) {
168
+ urlMap.set(file.url, file.requestInit);
169
+ }
170
+ }
171
+ });
172
+ });
173
+ }
174
+
175
+ // Create stores for new zip URLs with their requestInit
176
+ urlMap.forEach((requestInit, url) => {
177
+ // Only create a new store if one does not already exist for this URL.
178
+ if (!result[url]) {
179
+ result[url] = ZipFileStore.fromUrl(url, {
180
+ overrides: requestInit,
181
+ transformEntries: transformEntriesForZipFileStore,
182
+ });
183
+ }
184
+ });
185
+
186
+ return result;
187
+ // TODO: also depend on storesProp here?
188
+ }, [configKey]);
189
+
146
190
  // Process the view config and memoize the result:
147
191
  // - Validate.
148
192
  // - Upgrade, if legacy schema.
@@ -233,6 +277,7 @@ export function VitS(props) {
233
277
  [asyncFunctionsProp, queryClient],
234
278
  );
235
279
 
280
+
236
281
  const muiCache = useMemo(() => createCache({
237
282
  key: uid || 'vit',
238
283
  prepend: true,
@@ -256,7 +301,7 @@ export function VitS(props) {
256
301
  configOrWarning.description,
257
302
  fileTypes,
258
303
  coordinationTypes,
259
- stores,
304
+ mergedStores,
260
305
  queryClient,
261
306
  );
262
307
  return createViewConfigStore(loaders, configOrWarning);
@@ -300,7 +345,7 @@ export function VitS(props) {
300
345
  height={height}
301
346
  theme={theme}
302
347
  isBounded={isBounded}
303
- stores={stores}
348
+ stores={mergedStores}
304
349
  queryClient={queryClient}
305
350
  >
306
351
  {children}
@@ -97,7 +97,7 @@ const gridLayoutStyles = {
97
97
  },
98
98
  '.react-grid-item > .react-resizable-handle.react-resizable-handle-se': {
99
99
  bottom: '0',
100
- right: '0',
100
+ right: '10px',
101
101
  cursor: 'se-resize',
102
102
  },
103
103
  '.react-grid-item > .react-resizable-handle.react-resizable-handle-nw': {
@@ -78,6 +78,7 @@ const lightPalette = {
78
78
  gridLayoutBackground: '#FFFFFF',
79
79
  paperBackground: '#FFFFFF',
80
80
  cardBorder: 'rgba(241, 241, 241, 0.125)',
81
+ cardInfoBorder: '#ccc',
81
82
  tooltipText: 'rgba(0, 0, 0, 0.87)',
82
83
  };
83
84
 
@@ -104,6 +105,7 @@ export const muiTheme = {
104
105
  gridLayoutBackground: '#333333',
105
106
  paperBackground: '#2b2b2b',
106
107
  cardBorder: 'rgba(0, 0, 0, 0.125)',
108
+ cardInfoBorder: '#222222',
107
109
  tooltipText: '#FFFFFF',
108
110
  ...globalColors,
109
111
  },
@@ -113,6 +115,7 @@ export const muiTheme = {
113
115
  light: createTheme({
114
116
  palette: {
115
117
  ...lightPalette,
118
+ cardInfoBorder: '#F1F1F1',
116
119
  ...globalColors,
117
120
  },
118
121
  ...sharedThemeOptions,
@@ -130,6 +133,6 @@ export const muiTheme = {
130
133
  ...globalColors,
131
134
  },
132
135
  ...sharedThemeOptions,
133
- cardBorderSize: '2px',
136
+ cardBorderSize: '1px',
134
137
  }),
135
138
  };
@@ -14,18 +14,23 @@ export const useTitleStyles = makeStyles()(theme => ({
14
14
  textOverflow: 'ellipsis',
15
15
  whiteSpace: 'nowrap',
16
16
  },
17
- titleInfo: {
17
+ infoText: {
18
+ color: theme.palette.primaryForeground,
18
19
  width: '100%',
19
20
  textOverflow: 'ellipsis',
20
21
  whiteSpace: 'nowrap',
21
22
  overflow: 'hidden',
22
- fontSize: '80% !important',
23
+ fontSize: '60% !important',
23
24
  opacity: '.8',
24
- padding: '0 4px',
25
+ padding: '0',
25
26
  justifyContent: 'center',
26
- lineHeight: '25px !important',
27
- flexShrink: '1',
28
- textAlign: 'right !important',
27
+ textAlign: 'center !important',
28
+ borderTop: '0px',
29
+ borderBottom: `1px solid ${theme.palette.cardInfoBorder}`,
30
+ borderLeft: `1px solid ${theme.palette.cardInfoBorder}`,
31
+ borderRight: `1px solid ${theme.palette.cardInfoBorder}`,
32
+ borderBottomLeftRadius: '4px',
33
+ borderBottomRightRadius: '4px',
29
34
  },
30
35
  titleButtons: {
31
36
  display: 'flex',
@@ -41,15 +46,20 @@ export const useTitleStyles = makeStyles()(theme => ({
41
46
  border: `${theme.cardBorderSize} solid ${theme.palette.cardBorder}`,
42
47
  flex: '1 1 auto',
43
48
  minHeight: '1px',
44
- marginTop: '8px',
45
- marginBottom: '8px',
49
+ marginTop: '0',
50
+ marginBottom: '0',
46
51
  position: 'relative',
47
52
  display: 'flex',
48
53
  flexDirection: 'column',
49
54
  minWidth: '0',
50
55
  wordWrap: 'break-word',
51
56
  backgroundClip: 'border-box',
52
- borderRadius: '4px',
57
+ borderTopLeftRadius: '4px',
58
+ borderTopRightRadius: '4px',
59
+ },
60
+ cardWithoutInfoText: {
61
+ borderBottomLeftRadius: '4px',
62
+ borderBottomRightRadius: '4px',
53
63
  },
54
64
  paddingCard: {
55
65
  padding: '12px',