@vitessce/launcher 3.8.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 (51) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +34 -0
  3. package/dist/OrbitControls-BF52POQp.js +18694 -0
  4. package/dist/ReactNeuroglancer-BzSHY22y.js +65780 -0
  5. package/dist/blosc-DvQQ1ST0.js +887 -0
  6. package/dist/chunk-INHXZS53-DHVJiuU3.js +19 -0
  7. package/dist/deflate-CQwOaSLw.js +10 -0
  8. package/dist/higlass-C7qBSekP.js +133163 -0
  9. package/dist/index-B00rYWyD.js +3686 -0
  10. package/dist/index-BIrF4FKE.js +9854 -0
  11. package/dist/index-CYL9MBhI.js +384168 -0
  12. package/dist/index.js +5 -0
  13. package/dist/jpeg-BFml4vEx.js +856 -0
  14. package/dist/lerc-BOiupD76.js +2057 -0
  15. package/dist/lz4-BIbM36RN.js +801 -0
  16. package/dist/lzw-DjHwit8p.js +125 -0
  17. package/dist/packbits-io9qfQZa.js +27 -0
  18. package/dist/pako.esm-SxljTded.js +2216 -0
  19. package/dist/raw-BzEs_FKC.js +9 -0
  20. package/dist/troika-three-text.esm-26C0zM6u.js +5685 -0
  21. package/dist/webimage-CwJqBfRI.js +29 -0
  22. package/dist/zstd-CO575QiM.js +769 -0
  23. package/dist-tsc/ControlledLauncher.d.ts +2 -0
  24. package/dist-tsc/ControlledLauncher.d.ts.map +1 -0
  25. package/dist-tsc/ControlledLauncher.js +129 -0
  26. package/dist-tsc/LauncherStart.d.ts +2 -0
  27. package/dist-tsc/LauncherStart.d.ts.map +1 -0
  28. package/dist-tsc/LauncherStart.js +142 -0
  29. package/dist-tsc/UncontrolledLauncher.d.ts +14 -0
  30. package/dist-tsc/UncontrolledLauncher.d.ts.map +1 -0
  31. package/dist-tsc/UncontrolledLauncher.js +52 -0
  32. package/dist-tsc/index.d.ts +3 -0
  33. package/dist-tsc/index.d.ts.map +1 -0
  34. package/dist-tsc/index.js +2 -0
  35. package/dist-tsc/launcher-styles.d.ts +10 -0
  36. package/dist-tsc/launcher-styles.d.ts.map +1 -0
  37. package/dist-tsc/launcher-styles.js +69 -0
  38. package/dist-tsc/use-hash-param.d.ts +3 -0
  39. package/dist-tsc/use-hash-param.d.ts.map +1 -0
  40. package/dist-tsc/use-hash-param.js +110 -0
  41. package/dist-tsc/use-query-or-hash-param.d.ts +2 -0
  42. package/dist-tsc/use-query-or-hash-param.d.ts.map +1 -0
  43. package/dist-tsc/use-query-or-hash-param.js +51 -0
  44. package/package.json +51 -0
  45. package/src/ControlledLauncher.js +208 -0
  46. package/src/LauncherStart.js +351 -0
  47. package/src/UncontrolledLauncher.js +110 -0
  48. package/src/index.js +2 -0
  49. package/src/launcher-styles.js +71 -0
  50. package/src/use-hash-param.js +124 -0
  51. package/src/use-query-or-hash-param.js +63 -0
@@ -0,0 +1,2 @@
1
+ export function ControlledLauncher(props: any): JSX.Element;
2
+ //# sourceMappingURL=ControlledLauncher.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ControlledLauncher.d.ts","sourceRoot":"","sources":["../src/ControlledLauncher.js"],"names":[],"mappings":"AA+LA,4DAgBC"}
@@ -0,0 +1,129 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /* eslint-disable no-unused-vars */
3
+ /* eslint-disable no-nested-ternary */
4
+ /* eslint-disable max-len */
5
+ /* eslint-disable no-console */
6
+ /* eslint-disable prefer-destructuring */
7
+ import React, { useMemo, useState, useEffect } from 'react';
8
+ import { Vitessce } from '@vitessce/all';
9
+ import { generateConfigAlt as generateConfig, parseUrls } from '@vitessce/config';
10
+ import { Alert } from '@vitessce/styles';
11
+ import clsx from 'clsx';
12
+ import { QueryClient, QueryClientProvider, useQuery } from '@tanstack/react-query';
13
+ import { useStyles } from './launcher-styles.js';
14
+ import { LauncherStart } from './LauncherStart.js';
15
+ function logConfigUpgrade(prevConfig, nextConfig) {
16
+ // eslint-disable-next-line no-console
17
+ console.log(`Upgrade view config schema from ${prevConfig.version} to ${nextConfig.version}`);
18
+ // eslint-disable-next-line no-console
19
+ console.log(prevConfig);
20
+ // eslint-disable-next-line no-console
21
+ console.log(nextConfig);
22
+ }
23
+ function ControlledLauncherInner(props) {
24
+ const { isEditing, setIsEditing, setIsUsingLocalFiles, configUrl, setConfigUrl, sourceUrlArr, setSourceUrlArr, marginTop = 0, launcherError, setLauncherError, } = props;
25
+ const { classes } = useStyles();
26
+ const [validConfigFromDroppedData, setValidConfigFromDroppedData] = useState(null);
27
+ const [storesFromDroppedData, setStoresFromDroppedData] = useState(null);
28
+ const configUrlQueryEnabled = !isEditing && !!configUrl;
29
+ const configUrlResult = useQuery({
30
+ enabled: configUrlQueryEnabled,
31
+ queryKey: ['config-from-url', configUrl],
32
+ queryFn: async () => {
33
+ const resp = await fetch(configUrl);
34
+ if (!resp.ok) {
35
+ throw new Error(`Failed to fetch config from URL: ${resp.status} ${resp.statusText}`);
36
+ }
37
+ const configJson = await resp.json();
38
+ return configJson;
39
+ },
40
+ });
41
+ const sourceUrlQueryEnabled = !isEditing && Array.isArray(sourceUrlArr) && sourceUrlArr.length > 0;
42
+ const sourceUrlResult = useQuery({
43
+ enabled: sourceUrlQueryEnabled,
44
+ queryKey: ['data-metadata-from-urls', sourceUrlArr],
45
+ queryFn: async () => {
46
+ const { config, stores } = await generateConfig(parseUrls(sourceUrlArr));
47
+ return { config, stores };
48
+ },
49
+ });
50
+ let isLoading = false;
51
+ let validConfig = null;
52
+ let stores = null;
53
+ if (configUrlQueryEnabled) {
54
+ isLoading = configUrlResult.isLoading;
55
+ if (configUrlResult.isSuccess) {
56
+ validConfig = configUrlResult.data;
57
+ setLauncherError(null);
58
+ }
59
+ else if (configUrlResult.isError) {
60
+ setIsEditing(true);
61
+ setLauncherError(`Error loading config from URL: ${configUrlResult.error.message}`);
62
+ }
63
+ }
64
+ else if (sourceUrlQueryEnabled) {
65
+ isLoading = sourceUrlResult.isLoading;
66
+ if (sourceUrlResult.isSuccess) {
67
+ validConfig = sourceUrlResult.data.config.toJSON();
68
+ stores = sourceUrlResult.data.stores;
69
+ setLauncherError(null);
70
+ }
71
+ else if (sourceUrlResult.isError) {
72
+ setIsEditing(true);
73
+ setLauncherError(`Error loading data/metadata from source URL(s): ${sourceUrlResult.error.message}`);
74
+ }
75
+ }
76
+ else if (validConfigFromDroppedData) {
77
+ validConfig = validConfigFromDroppedData;
78
+ stores = storesFromDroppedData;
79
+ setLauncherError(null);
80
+ }
81
+ // Possible states:
82
+ // - needsStart: No URL parameters; Show the launcher UI.
83
+ // - Awaiting URL parameter parsing.
84
+ // - Invalid/unrecognized example ID
85
+ // - Loading config or data/metadata from URL.
86
+ // - Loaded config but was invalid.
87
+ // - Could not identify a valid/supported data format from data file extension(s).
88
+ // - Suggest to append `$supportedFileType` to source URL(s) to specify the data format if file extension is non-standard.
89
+ // - Failed to load config or data/metadata from URL.
90
+ // - Error if BOTH config and source parameters are provided.
91
+ // - Error if BOTH hash and query parameters are provided.
92
+ const pageMode = false; // TODO
93
+ const PageComponent = null; // TODO
94
+ const isExpanded = true; // TODO
95
+ const onDropHandler = null; // TODO
96
+ const debug = false; // TODO
97
+ const theme = 'light2'; // TODO
98
+ return (isEditing ? (_jsxs(_Fragment, { children: [launcherError ? (_jsx(Alert, { severity: "error", onClose: () => setLauncherError(null), children: launcherError })) : null, _jsx(LauncherStart, { setIsEditing: setIsEditing, setIsUsingLocalFiles: setIsUsingLocalFiles, configUrl: configUrl, setConfigUrl: setConfigUrl, sourceUrlArr: sourceUrlArr, setSourceUrlArr: setSourceUrlArr, setValidConfigFromDroppedData: setValidConfigFromDroppedData, setStoresFromDroppedData: setStoresFromDroppedData, launcherError: launcherError, setLauncherError: setLauncherError })] })) : (validConfig ? (_jsxs("main", { className: clsx(classes.vitessceApp, { 'vitessce-expanded': isExpanded, 'vitessce-page': pageMode }), style: {
99
+ height: `calc(100vh - ${marginTop}px)`,
100
+ }, children: [pageMode ? (_jsx("style", { children: `
101
+ .vitessce-container {
102
+ height: max(100%,100vh);
103
+ width: 100%;
104
+ overflow: hidden;
105
+ position: relative;
106
+ left: 0;
107
+ }
108
+ ` })) : (_jsx("style", { children: `
109
+ .vitessce-container {
110
+ min-height: calc(100vh - ${marginTop}px);
111
+ width: 100%;
112
+ overflow: hidden;
113
+ position: relative;
114
+ left: 0;
115
+ }
116
+ ` })), _jsx(Vitessce, { theme: theme, validateOnConfigChange: debug, onConfigChange: debug ? console.log : undefined, onConfigUpgrade: debug ? logConfigUpgrade : undefined, config: validConfig, height: isExpanded ? undefined : 800, pageMode: pageMode, stores: stores, onDrop: onDropHandler, children: pageMode && PageComponent ? (_jsx(PageComponent, {})) : null })] })) : (isLoading ? (_jsx("h2", { children: "Loading..." })) : (_jsx("p", { children: "Error: undefined state" })))));
117
+ }
118
+ export function ControlledLauncher(props) {
119
+ const queryClient = useMemo(() => new QueryClient({
120
+ // Reference: https://tanstack.com/query/latest/docs/react/guides/window-focus-refetching
121
+ defaultOptions: {
122
+ queries: {
123
+ refetchOnWindowFocus: false,
124
+ retry: 2,
125
+ },
126
+ },
127
+ }), []);
128
+ return (_jsx(QueryClientProvider, { client: queryClient, children: _jsx(ControlledLauncherInner, { ...props }) }));
129
+ }
@@ -0,0 +1,2 @@
1
+ export function LauncherStart(props: any): JSX.Element;
2
+ //# sourceMappingURL=LauncherStart.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LauncherStart.d.ts","sourceRoot":"","sources":["../src/LauncherStart.js"],"names":[],"mappings":"AAoBA,uDA0UC"}
@@ -0,0 +1,142 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /* eslint-disable no-unused-vars */
3
+ /* eslint-disable react/no-unknown-property */
4
+ /* eslint-disable max-len */
5
+ import React, { useEffect, useMemo, useRef, useState } from 'react';
6
+ import { createOnDrop } from '@vitessce/vit-s';
7
+ import { TextField, Button, Typography, Card, CardContent, Accordion, AccordionSummary, AccordionDetails, ExpandMore, } from '@vitessce/styles';
8
+ import clsx from 'clsx';
9
+ import { useStyles } from './launcher-styles.js';
10
+ export function LauncherStart(props) {
11
+ const { setIsEditing, setIsUsingLocalFiles, configUrl, setConfigUrl, sourceUrlArr, setSourceUrlArr, setValidConfigFromDroppedData, setStoresFromDroppedData, setLauncherError, } = props;
12
+ const { classes } = useStyles();
13
+ const [spotlightCard, setSpotlightCard] = useState(null);
14
+ // Single dropzone for both data files/folders and config files.
15
+ const dropzoneRef = useRef(null);
16
+ const localDataInputFoldersRef = useRef(null);
17
+ const localDataInputFilesRef = useRef(null);
18
+ const localConfigInputRef = useRef(null);
19
+ // Handlers for drag-and-drop and file input changes.
20
+ const onDropHandler = useMemo(() => createOnDrop({ setViewConfig: setValidConfigFromDroppedData, setStores: setStoresFromDroppedData }, false, // isFileInput
21
+ true), [setValidConfigFromDroppedData, setStoresFromDroppedData]);
22
+ const onFileInputHandler = useMemo(() => createOnDrop({ setViewConfig: setValidConfigFromDroppedData, setStores: setStoresFromDroppedData }, true, // isFileInput
23
+ true), [setValidConfigFromDroppedData, setStoresFromDroppedData]);
24
+ // Effect for setting up drag-and-drop event listeners for data file/folder input.
25
+ useEffect(() => {
26
+ // Make the entire Launcher a dropzone,
27
+ // and then determine whether the dropped file(s) are config or data files
28
+ // (e.g., if a single .json file, assume config; else data).
29
+ const zone = dropzoneRef.current;
30
+ const zoneDataInput1 = localDataInputFoldersRef.current;
31
+ const zoneDataInput2 = localDataInputFilesRef.current;
32
+ const zoneConfigInput = localConfigInputRef.current;
33
+ const onDragEnter = (e) => {
34
+ e.preventDefault();
35
+ setSpotlightCard('data-local');
36
+ };
37
+ const onDragLeave = () => {
38
+ setSpotlightCard(null);
39
+ };
40
+ const onDragOver = (e) => {
41
+ e.preventDefault();
42
+ };
43
+ const onDrop = async (e) => {
44
+ e.preventDefault();
45
+ setIsEditing(false);
46
+ // Clear the other field values.
47
+ setConfigUrl(undefined);
48
+ setSourceUrlArr(undefined);
49
+ // Call onDrop handler passed in from parent of <VitS/Vitessce/> via prop.
50
+ try {
51
+ await onDropHandler(e);
52
+ setIsUsingLocalFiles(true);
53
+ }
54
+ catch (err) {
55
+ setLauncherError(`Error processing dropped files: ${err.message}`);
56
+ setIsEditing(true);
57
+ }
58
+ setSpotlightCard(null);
59
+ };
60
+ const onDataInputChange = async (e) => {
61
+ // Reference: https://developer.mozilla.org/en-US/docs/Web/API/HTML_Drag_and_Drop_API/File_drag_and_drop
62
+ e.preventDefault();
63
+ setIsEditing(false);
64
+ setSpotlightCard('data-local');
65
+ // Clear the other field values.
66
+ setConfigUrl(undefined);
67
+ setSourceUrlArr(undefined);
68
+ try {
69
+ await onFileInputHandler(e);
70
+ setIsUsingLocalFiles(true);
71
+ }
72
+ catch (err) {
73
+ setLauncherError(`Error processing selected files: ${err.message}`);
74
+ setIsEditing(true);
75
+ }
76
+ setSpotlightCard(null);
77
+ };
78
+ const onConfigInputChange = async (e) => {
79
+ // Reference: https://developer.mozilla.org/en-US/docs/Web/API/HTML_Drag_and_Drop_API/File_drag_and_drop
80
+ e.preventDefault();
81
+ setIsEditing(false);
82
+ setSpotlightCard('config-local');
83
+ // Clear the other field values.
84
+ setConfigUrl(undefined);
85
+ setSourceUrlArr(undefined);
86
+ try {
87
+ await onFileInputHandler(e);
88
+ setIsUsingLocalFiles(true);
89
+ }
90
+ catch (err) {
91
+ setLauncherError(`Error processing selected file: ${err.message}`);
92
+ setIsEditing(true);
93
+ }
94
+ setSpotlightCard(null);
95
+ };
96
+ // The dragenter event happens at the moment you drag something in to the target element,
97
+ // and then it stops.
98
+ // The dragover event happens during the time you are dragging something until you drop it.
99
+ zone.addEventListener('dragenter', onDragEnter);
100
+ zone.addEventListener('dragleave', onDragLeave);
101
+ zone.addEventListener('dragover', onDragOver);
102
+ zone.addEventListener('drop', onDrop);
103
+ zoneDataInput1.addEventListener('change', onDataInputChange);
104
+ zoneDataInput2.addEventListener('change', onDataInputChange);
105
+ zoneConfigInput.addEventListener('change', onConfigInputChange);
106
+ return () => {
107
+ zone.removeEventListener('dragenter', onDragEnter);
108
+ zone.removeEventListener('dragleave', onDragLeave);
109
+ zone.removeEventListener('dragover', onDragOver);
110
+ zone.removeEventListener('drop', onDrop);
111
+ zoneDataInput1.removeEventListener('change', onDataInputChange);
112
+ zoneDataInput2.removeEventListener('change', onDataInputChange);
113
+ zoneConfigInput.removeEventListener('change', onConfigInputChange);
114
+ };
115
+ }, [dropzoneRef, localDataInputFoldersRef, localDataInputFilesRef,
116
+ onDropHandler, setIsUsingLocalFiles, setIsEditing, setSpotlightCard,
117
+ onFileInputHandler, setConfigUrl, setSourceUrlArr, setLauncherError,
118
+ ]);
119
+ return (_jsxs("div", { className: classes.launcher, ref: dropzoneRef, children: [_jsxs("div", { className: classes.launcherRow, children: [_jsxs("div", { className: clsx({ [classes.cardDim]: spotlightCard && (spotlightCard === 'config-local' || spotlightCard === 'config-remote') }), children: [_jsx(Typography, { variant: "h5", children: "Begin with data" }), _jsxs("p", { children: ["Let Vitessce ", _jsx("a", { href: "https://vitessce.io/docs/default-config-json/", children: "infer an initial configuration" }), " based on the contents of your ", _jsx("a", { href: "https://vitessce.io/docs/data-types-file-types/", children: "data files." })] })] }), _jsxs("div", { className: classes.launcherCardRow, children: [_jsx(Card, { className: clsx(classes.launcherCard, { [classes.cardDim]: spotlightCard && spotlightCard !== 'data-local' }), children: _jsx("span", { className: classes.cardDashed, children: _jsxs(CardContent, { className: classes.cardContent, children: [_jsxs(Typography, { variant: "h6", children: ["Local data ", _jsx("br", {}), "(Drag and drop)"] }), _jsx("p", { children: "Select or drop local files (or folders) to view them in Vitessce. Vitessce launches with a default configuration (based on file extensions and contents). Files remain local; no upload occurs." }), _jsx("div", { className: classes.buttonSpacer }), _jsxs("div", { className: classes.dataButtonGroup, children: [_jsxs(Button, { component: "label", htmlFor: "data-local-input-directory", variant: "outlined", fullWidth: true, children: [_jsx("span", { children: "Select folder(s)" }), _jsx("input", { id: "data-local-input-directory", type: "file", ref: localDataInputFoldersRef, className: classes.hiddenFileInput, multiple: true, directory: "true", webkitdirectory: "true", mozdirectory: "true" })] }), "\u00A0", _jsxs(Button, { component: "label", htmlFor: "data-local-input-files", variant: "outlined", fullWidth: true, children: [_jsx("span", { children: "Select file(s)" }), _jsx("input", { id: "data-local-input-files", type: "file", ref: localDataInputFilesRef, className: classes.hiddenFileInput, multiple: true })] })] }), _jsx("div", { className: classes.buttonSpacer })] }) }) }), _jsx(Card, { className: clsx(classes.launcherCard, { [classes.cardDim]: spotlightCard && spotlightCard !== 'data-remote' }), children: _jsxs(CardContent, { children: [_jsxs(Typography, { variant: "h6", children: ["Remote data ", _jsx("br", {}), " (Load from URL)"] }), _jsxs("p", { children: ["Enter file (or folder) URLs to view them in Vitessce. Vitessce launches with a default configuration (based on file types and contents). See our ", _jsx("a", { href: "https://vitessce.io/docs/data-hosting/", children: "data hosting" }), " documentation for assistance in making your data accessible to Vitessce.\u00A0"] }), _jsxs("div", { className: classes.textareaAndButton, children: [_jsx(TextField, { multiline: true, label: "Data URL(s)", placeholder: "One or more file URLs (semicolon-separated)", minRows: 2, className: classes.dataUrlTextarea, value: sourceUrlArr ? sourceUrlArr.join(';') : '', onChange: (e) => {
120
+ // Clear existing state.
121
+ setConfigUrl(undefined);
122
+ setSourceUrlArr(e.target.value);
123
+ }, onBlur: () => setSpotlightCard(null), onFocus: () => {
124
+ setSpotlightCard('data-remote');
125
+ // We need to set isEditing, otherwise the launcher UI will disappear
126
+ // as soon as there is a non-empty sourceUrlArr value.
127
+ setIsEditing(true);
128
+ } }), _jsx(Button, { variant: "outlined", onClick: () => {
129
+ setIsEditing(false);
130
+ }, children: "Visualize" })] }), _jsxs(Accordion, { disableGutters: true, style: { marginTop: '5px' }, children: [_jsx(AccordionSummary, { expandIcon: _jsx(ExpandMore, {}), "aria-controls": "panel1-content", id: "panel1-header", children: "Specifying file extensions" }), _jsx(AccordionDetails, { children: _jsxs(Typography, { variant: "body2", children: ["Vitessce automatically tries to infer the file type based on the file extension. For URLs with non-standard extensions, the file type can be specified by appending ", _jsxs("code", { children: [_jsx("b", { children: "$" }), "FILE_TYPE"] }), " to the URL. For example, to specify that a folder at ", _jsx("code", { children: "https://example.com/my_data.zarr" }), " is in SpatialData format, use the URL ", _jsxs("code", { children: ["https://example.com/my_data.zarr", _jsx("b", { children: "$" }), "spatialdata.zarr"] }), ". See our ", _jsx("a", { href: "https://vitessce.io/docs/data-types-file-types/", children: "data types and file types documentation" }), " for a list of supported file types."] }) })] })] }) })] })] }), _jsxs("div", { className: classes.launcherRow, children: [_jsxs("div", { className: clsx({ [classes.cardDim]: spotlightCard && (spotlightCard === 'data-local' || spotlightCard === 'data-remote') }), children: [_jsx(Typography, { variant: "h5", children: "Begin with configuration" }), _jsxs("p", { children: ["Use the options below to supply a Vitessce ", _jsx("a", { href: "https://vitessce.io/docs/view-config-json/", children: "configuration in JSON format." })] })] }), _jsxs("div", { className: classes.launcherCardRow, children: [_jsx(Card, { className: clsx(classes.launcherCard, { [classes.cardDim]: spotlightCard && spotlightCard !== 'config-local' }), children: _jsx("span", { className: classes.cardDashed, children: _jsxs(CardContent, { className: classes.cardContent, children: [_jsxs(Typography, { variant: "h6", children: ["Local config file ", _jsx("br", {}), " (Drag and drop)"] }), _jsx("p", { children: "View a configured Vitessce visualization by selecting or dropping a local JSON file." }), _jsx("div", { className: classes.buttonSpacer }), _jsxs(Button, { component: "label", variant: "outlined", fullWidth: true, children: [_jsx("span", { children: "Select file" }), _jsx("input", { type: "file", ref: localConfigInputRef, className: classes.hiddenFileInput, accept: ".json,application/json" })] }), _jsx("div", { className: classes.buttonSpacer })] }) }) }), _jsx(Card, { className: clsx(classes.launcherCard, { [classes.cardDim]: spotlightCard && spotlightCard !== 'config-remote' }), children: _jsxs(CardContent, { children: [_jsxs(Typography, { variant: "h6", children: ["Remote config file ", _jsx("br", {}), " (Load from URL)"] }), _jsxs("p", { children: ["View a configured Vitessce visualization by specifying a URL to a JSON config file. See our ", _jsx("a", { href: "https://vitessce.io/docs/data-hosting/", children: "data hosting" }), " documentation for assistance in making your config file accessible to Vitessce.\u00A0"] }), _jsxs("div", { className: classes.textareaAndButton, children: [_jsx(TextField, { multiline: true, label: "Config URL", placeholder: "Enter a URL", minRows: 2, className: classes.dataUrlTextarea, value: configUrl || '', onChange: (e) => {
131
+ // Clear existing state.
132
+ setSourceUrlArr(undefined);
133
+ setConfigUrl(e.target.value);
134
+ }, onBlur: () => setSpotlightCard(null), onFocus: () => {
135
+ setSpotlightCard('config-remote');
136
+ // We need to set isEditing, otherwise the launcher UI will disappear
137
+ // as soon as there is a non-empty configUrl value.
138
+ setIsEditing(true);
139
+ } }), _jsx(Button, { variant: "outlined", onClick: () => {
140
+ setIsEditing(false);
141
+ }, children: "Visualize" })] })] }) })] })] })] }));
142
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ *
3
+ * @param {object} props
4
+ * @param {number} [props.marginTop=0] The distance in pixels from the top of the window.
5
+ * This is used to calculate the height of the Vitessce component (100vh minus marginTop).
6
+ * @param {function} [props.setIsFooterVisible] Callback to set whether the footer is visible.
7
+ * Used to hide the footer when Vitessce component is visible.
8
+ * @returns
9
+ */
10
+ export function UncontrolledLauncher(props: {
11
+ marginTop?: number | undefined;
12
+ setIsFooterVisible?: Function | undefined;
13
+ }): JSX.Element;
14
+ //# sourceMappingURL=UncontrolledLauncher.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"UncontrolledLauncher.d.ts","sourceRoot":"","sources":["../src/UncontrolledLauncher.js"],"names":[],"mappings":"AAmEA;;;;;;;;GAQG;AACH,4CANG;IAAuB,SAAS;IAEP,kBAAkB;CAE3C,eAmCF"}
@@ -0,0 +1,52 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /* eslint-disable no-unused-vars */
3
+ /* eslint-disable max-len */
4
+ import React, { useEffect, useState } from 'react';
5
+ import { QueryParamProvider } from 'use-query-params';
6
+ import { useHashOrQueryParam } from './use-query-or-hash-param.js';
7
+ import { ControlledLauncher } from './ControlledLauncher.js';
8
+ function UncontrolledLauncherInner(props) {
9
+ const { marginTop = 0, setIsFooterVisible, configUrlParamName = 'config', sourceUrlParamName = 'source', exampleIdParamName = 'example', isEditingParamName = 'edit', } = props;
10
+ const [launcherError, setLauncherError] = useState(null);
11
+ // Logic for managing state and query/hash params.
12
+ const [exampleId, setExampleId, exampleIdParamError] = useHashOrQueryParam(exampleIdParamName, undefined, 'string');
13
+ // TODO: support vitessce-link code param
14
+ // const wsCodeValue = useHashOrQueryParam('session', undefined, 'string');
15
+ const [configUrl, setConfigUrl, configUrlParamError] = useHashOrQueryParam(configUrlParamName, undefined, 'string');
16
+ const [sourceUrlArr, setSourceUrlArr, sourceUrlArrParamError] = useHashOrQueryParam(sourceUrlParamName, undefined, 'string-array');
17
+ const [isEditingParam, setIsEditing, isEditingParamError] = useHashOrQueryParam(isEditingParamName, undefined, 'boolean');
18
+ // We need this here since the URL param will not indicate whether local data was dropped.
19
+ // TODO: Store in a cookie/localStorage? Store in a URL param?
20
+ // However if the page changes, we will lose the dropped data anyway...
21
+ const [isUsingLocalFiles, setIsUsingLocalFiles] = useState(false);
22
+ // Check for parameter errors (e.g., both hash+query for same value) and conflicting parameters.
23
+ const hasNoParamError = !exampleIdParamError && !configUrlParamError && !sourceUrlArrParamError && !isEditingParamError;
24
+ const hasNoConflictingParams = ((exampleId ? 1 : 0)
25
+ + (configUrl ? 1 : 0)
26
+ + (sourceUrlArr && sourceUrlArr.length > 0 ? 1 : 0)) <= 1;
27
+ const needsStart = !exampleId && !configUrl && (!sourceUrlArr || (Array.isArray(sourceUrlArr) && sourceUrlArr.length === 0));
28
+ const isEditing = !isUsingLocalFiles && (isEditingParam === true || needsStart);
29
+ useEffect(() => {
30
+ if (typeof setIsFooterVisible === 'function') {
31
+ setIsFooterVisible(isEditing);
32
+ }
33
+ }, [isEditing, setIsFooterVisible]);
34
+ return (_jsx(ControlledLauncher, { isEditing: isEditing, setIsEditing: setIsEditing, isUsingLocalFiles: isUsingLocalFiles, setIsUsingLocalFiles: setIsUsingLocalFiles, configUrl: configUrl, setConfigUrl: setConfigUrl, sourceUrlArr: sourceUrlArr, setSourceUrlArr: setSourceUrlArr, marginTop: marginTop, launcherError: launcherError, setLauncherError: setLauncherError }));
35
+ }
36
+ /**
37
+ *
38
+ * @param {object} props
39
+ * @param {number} [props.marginTop=0] The distance in pixels from the top of the window.
40
+ * This is used to calculate the height of the Vitessce component (100vh minus marginTop).
41
+ * @param {function} [props.setIsFooterVisible] Callback to set whether the footer is visible.
42
+ * Used to hide the footer when Vitessce component is visible.
43
+ * @returns
44
+ */
45
+ export function UncontrolledLauncher(props) {
46
+ const { marginTop = 0, setIsFooterVisible = null, configUrlParamName = 'config', sourceUrlParamName = 'source', exampleIdParamName = 'example', isEditingParamName = 'edit',
47
+ // TODO: Optional mapping from example IDs to their full JSON config, and potentially more values such as PageComponent, Plugins, etc.
48
+ // See https://github.com/vitessce/vitessce/blob/main/sites/demo/src/api.js
49
+ exampleConfigs = null, } = props;
50
+ // TODO: wrap in MUI themeProvider?
51
+ return (_jsxs(QueryParamProvider, { children: [_jsxs("div", { style: { display: 'none' }, children: [_jsx("a", { href: "/", children: "Reset" }), "\u00A0", _jsx("a", { href: "/?source=https://storage.googleapis.com/vitessce-demo-data/maynard-2021/151673.sdata.zarr", children: "SpatialData Example" }), "\u00A0", _jsx("a", { href: "/#?source=https://uk1s3.embassy.ebi.ac.uk/idr/zarr/v0.4/idr0062A/6001240.zarr$image.ome-zarr", children: "OME-Zarr Example" })] }), _jsx(UncontrolledLauncherInner, { marginTop: marginTop, setIsFooterVisible: setIsFooterVisible, isEditingParamName: isEditingParamName, exampleIdParamName: exampleIdParamName, configUrlParamName: configUrlParamName, sourceUrlParamName: sourceUrlParamName })] }));
52
+ }
@@ -0,0 +1,3 @@
1
+ export { UncontrolledLauncher } from "./UncontrolledLauncher.js";
2
+ export { ControlledLauncher } from "./ControlledLauncher.js";
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.js"],"names":[],"mappings":""}
@@ -0,0 +1,2 @@
1
+ export { UncontrolledLauncher } from './UncontrolledLauncher.js';
2
+ export { ControlledLauncher } from './ControlledLauncher.js';
@@ -0,0 +1,10 @@
1
+ export const useStyles: (params: void, muiStyleOverridesParams?: {
2
+ props: Record<string, unknown>;
3
+ ownerState?: Record<string, unknown> | undefined;
4
+ } | undefined) => {
5
+ classes: Record<"launcher" | "launcherRow" | "launcherRowTitle" | "launcherCardRow" | "launcherCard" | "cardTitle" | "cardDashed" | "cardDim" | "cardContent" | "buttonSpacer" | "dataButtonGroup" | "vitessceApp" | "dataUrlTextarea" | "textareaAndButton" | "hiddenFileInput", string>;
6
+ theme: import("@mui/material").Theme;
7
+ css: import("tss-react").Css;
8
+ cx: import("tss-react").Cx;
9
+ };
10
+ //# sourceMappingURL=launcher-styles.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"launcher-styles.d.ts","sourceRoot":"","sources":["../src/launcher-styles.js"],"names":[],"mappings":"AAGA;;cAmCG,CAAC;;;kBAMF,eACC;gBAEG,WAAU;eACC,WACT;EAqBJ"}
@@ -0,0 +1,69 @@
1
+ import { makeStyles } from '@vitessce/styles';
2
+ export const useStyles = makeStyles()(() => ({
3
+ launcher: {
4
+ display: 'flex',
5
+ flexDirection: 'column',
6
+ margin: '0 10px',
7
+ },
8
+ launcherRow: {
9
+ marginTop: '15px',
10
+ marginBottom: '15px',
11
+ },
12
+ launcherRowTitle: {
13
+ marginBottom: 0,
14
+ },
15
+ launcherCardRow: {
16
+ display: 'grid',
17
+ gridTemplateColumns: '1fr 1fr',
18
+ gap: '10px',
19
+ },
20
+ launcherCard: {
21
+ transition: 'opacity 0.3s',
22
+ },
23
+ cardTitle: {
24
+ marginTop: 0,
25
+ },
26
+ cardDashed: {
27
+ display: 'inline-block',
28
+ boxSizing: 'border-box',
29
+ height: '100%',
30
+ width: '100%',
31
+ borderRadius: '4px',
32
+ border: '2px dashed rgba(128, 128, 128, 0.2)',
33
+ },
34
+ cardDim: {
35
+ opacity: 0.2,
36
+ },
37
+ cardContent: {
38
+ display: 'flex',
39
+ flexDirection: 'column',
40
+ height: '100%',
41
+ boxSizing: 'border-box',
42
+ },
43
+ buttonSpacer: {
44
+ flexGrow: 1,
45
+ },
46
+ dataButtonGroup: {
47
+ width: '100%',
48
+ display: 'flex',
49
+ flexDirection: 'row',
50
+ gap: '5px',
51
+ },
52
+ vitessceApp: {
53
+ position: 'relative',
54
+ display: 'flex',
55
+ width: '100%',
56
+ },
57
+ dataUrlTextarea: {
58
+ width: '100%',
59
+ boxSizing: 'border-box',
60
+ },
61
+ textareaAndButton: {
62
+ display: 'flex',
63
+ flexDirection: 'row',
64
+ gap: '5px',
65
+ },
66
+ hiddenFileInput: {
67
+ display: 'none',
68
+ },
69
+ }));
@@ -0,0 +1,3 @@
1
+ export function useHashParam(key: any, defaultValue: any, varType: any): any[];
2
+ export function useSetHashParams(): (values: any, location?: Location) => void;
3
+ //# sourceMappingURL=use-hash-param.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-hash-param.d.ts","sourceRoot":"","sources":["../src/use-hash-param.js"],"names":[],"mappings":"AAyEO,+EAmCN;AAEM,+EAaN"}
@@ -0,0 +1,110 @@
1
+ /* eslint-disable no-param-reassign */
2
+ // Adapted from https://github.com/hejmsdz/use-hash-param/blob/3fd6818/src/index.js
3
+ import { useState, useEffect, useCallback } from 'react';
4
+ const getHashSearchParams = (location) => {
5
+ const hash = location.hash.slice(1);
6
+ const [prefix, query] = hash.split('?');
7
+ return [prefix, new URLSearchParams(query)];
8
+ };
9
+ const getHashParam = (key, location = window.location) => {
10
+ // eslint-disable-next-line no-unused-vars
11
+ const [_, searchParams] = getHashSearchParams(location);
12
+ return searchParams.get(key);
13
+ };
14
+ const getAllHashParams = (key, location = window.location) => {
15
+ // eslint-disable-next-line no-unused-vars
16
+ const [_, searchParams] = getHashSearchParams(location);
17
+ return searchParams.getAll(key);
18
+ };
19
+ const getTypedHashParam = (key, varType) => {
20
+ if (varType === 'string-array') {
21
+ return getAllHashParams(key);
22
+ }
23
+ const val = getHashParam(key);
24
+ let typedVal = val;
25
+ if (varType === 'boolean') {
26
+ try {
27
+ typedVal = JSON.parse(val);
28
+ }
29
+ catch (e) {
30
+ typedVal = false;
31
+ }
32
+ }
33
+ return typedVal;
34
+ };
35
+ const setHashParam = (key, value, location = window.location) => {
36
+ const [prefix, searchParams] = getHashSearchParams(location);
37
+ if (typeof value === 'undefined' || value === '') {
38
+ searchParams.delete(key);
39
+ }
40
+ else {
41
+ searchParams.set(key, value);
42
+ }
43
+ const search = searchParams.toString();
44
+ location.hash = search ? `${prefix}?${search}` : prefix;
45
+ };
46
+ // There is no "pushState" or "replaceState" event on the
47
+ // window, only a "popstate" event. We can add our own
48
+ // pushState and replaceState events by wrapping the
49
+ // methods of the window.history object and dispatching
50
+ // as custom events.
51
+ // Reference: https://stackoverflow.com/a/25673911
52
+ const wrapHistoryMethod = (methodName) => {
53
+ const orig = window.history[methodName];
54
+ window.history.wrapped = true;
55
+ // eslint-disable-next-line func-names
56
+ return function () {
57
+ // eslint-disable-next-line prefer-rest-params
58
+ const rv = orig.apply(this, arguments);
59
+ const e = new Event(methodName);
60
+ // eslint-disable-next-line prefer-rest-params
61
+ e.arguments = arguments;
62
+ window.dispatchEvent(e);
63
+ return rv;
64
+ };
65
+ };
66
+ export const useHashParam = (key, defaultValue, varType) => {
67
+ const [innerValue, setInnerValue] = useState(getTypedHashParam(key, varType));
68
+ useEffect(() => {
69
+ let unmounted = false;
70
+ if (window.history && !window.history.wrapped) {
71
+ window.history.pushState = wrapHistoryMethod('pushState');
72
+ }
73
+ const handleHashChange = () => {
74
+ if (unmounted) {
75
+ return;
76
+ }
77
+ const nextValue = getTypedHashParam(key, varType);
78
+ setInnerValue(nextValue);
79
+ };
80
+ window.addEventListener('hashchange', handleHashChange);
81
+ window.addEventListener('pushState', handleHashChange);
82
+ return () => {
83
+ unmounted = true;
84
+ window.removeEventListener('hashchange', handleHashChange);
85
+ window.removeEventListener('pushState', handleHashChange);
86
+ };
87
+ }, [key, varType]);
88
+ const setValue = useCallback((value) => {
89
+ if (typeof value === 'function') {
90
+ setHashParam(key, value(getHashParam(key)));
91
+ }
92
+ else {
93
+ setHashParam(key, value);
94
+ }
95
+ }, [key]);
96
+ return [innerValue === undefined ? defaultValue : innerValue, setValue];
97
+ };
98
+ export const useSetHashParams = () => (values, location = window.location) => {
99
+ const [prefix, searchParams] = getHashSearchParams(location);
100
+ Object.entries(values).forEach(([key, value]) => {
101
+ if (typeof value === 'undefined' || value === '') {
102
+ searchParams.delete(key);
103
+ }
104
+ else {
105
+ searchParams.set(key, value);
106
+ }
107
+ });
108
+ const search = searchParams.toString();
109
+ location.hash = search ? `${prefix}?${search}` : prefix;
110
+ };
@@ -0,0 +1,2 @@
1
+ export function useHashOrQueryParam(paramName: any, defaultValue: any, dtype: any): any[];
2
+ //# sourceMappingURL=use-query-or-hash-param.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-query-or-hash-param.d.ts","sourceRoot":"","sources":["../src/use-query-or-hash-param.js"],"names":[],"mappings":"AAuDA,0FAOC"}
@@ -0,0 +1,51 @@
1
+ import { useQueryParam, StringParam as StringQueryParam, ArrayParam as StringArrayQueryParam, } from 'use-query-params';
2
+ import { useHashParam } from './use-hash-param.js';
3
+ const DtypeToParamType = {
4
+ string: StringQueryParam,
5
+ 'string-array': StringArrayQueryParam,
6
+ // Use string param for boolean since use-query-params only supports 0/1.
7
+ boolean: StringQueryParam,
8
+ };
9
+ function isTruthyString(v) {
10
+ return ['1', 'true'].includes(String(v).toLowerCase());
11
+ }
12
+ function useHashOrQueryParamAux(paramName, defaultValue, dtype) {
13
+ const [valueQ, setValueQ] = useQueryParam(paramName, DtypeToParamType[dtype]);
14
+ const [valueH, setValueH] = useHashParam(paramName, undefined, dtype);
15
+ if (dtype === 'boolean') {
16
+ const boolValueQ = valueQ !== undefined ? isTruthyString(valueQ) : undefined;
17
+ const boolValueH = valueH !== undefined ? isTruthyString(valueH) : undefined;
18
+ return valueQ ? [boolValueQ, setValueQ] : [boolValueH, setValueH];
19
+ }
20
+ if (dtype === 'string-array') {
21
+ // eslint-disable-next-line max-len
22
+ if (Array.isArray(valueH) && valueH.length > 0 && (!Array.isArray(valueQ) || valueQ.length === 0)) {
23
+ // Check for ; delimiter in individual array values, and split if necessary.
24
+ const splitValues = valueH.map(item => item.split(';')).flat();
25
+ return [splitValues, setValueH];
26
+ // eslint-disable-next-line max-len
27
+ }
28
+ if (Array.isArray(valueQ) && valueQ.length > 0 && (!Array.isArray(valueH) || valueH.length === 0)) {
29
+ // Check for ; delimiter in individual array values, and split if necessary.
30
+ const splitValues = valueQ.map(item => item.split(';')).flat();
31
+ return [splitValues, setValueQ];
32
+ }
33
+ return [null, setValueH];
34
+ }
35
+ if (Array.isArray(valueQ) || Array.isArray(valueH)) {
36
+ throw new Error(`Expected non-array values for "${paramName}".`);
37
+ }
38
+ if (valueQ && valueH) {
39
+ throw new Error(`Both query and hash parameters provided for "${paramName}". Please provide only one.`);
40
+ }
41
+ return valueQ ? [valueQ, setValueQ] : [valueH, setValueH];
42
+ }
43
+ export function useHashOrQueryParam(paramName, defaultValue, dtype) {
44
+ const [value, setValue] = useHashOrQueryParamAux(paramName, defaultValue, dtype);
45
+ try {
46
+ return [value, setValue, false];
47
+ }
48
+ catch (error) {
49
+ return [value, setValue, error.message];
50
+ }
51
+ }