@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.
- package/LICENSE +21 -0
- package/README.md +34 -0
- package/dist/OrbitControls-BF52POQp.js +18694 -0
- package/dist/ReactNeuroglancer-BzSHY22y.js +65780 -0
- package/dist/blosc-DvQQ1ST0.js +887 -0
- package/dist/chunk-INHXZS53-DHVJiuU3.js +19 -0
- package/dist/deflate-CQwOaSLw.js +10 -0
- package/dist/higlass-C7qBSekP.js +133163 -0
- package/dist/index-B00rYWyD.js +3686 -0
- package/dist/index-BIrF4FKE.js +9854 -0
- package/dist/index-CYL9MBhI.js +384168 -0
- package/dist/index.js +5 -0
- package/dist/jpeg-BFml4vEx.js +856 -0
- package/dist/lerc-BOiupD76.js +2057 -0
- package/dist/lz4-BIbM36RN.js +801 -0
- package/dist/lzw-DjHwit8p.js +125 -0
- package/dist/packbits-io9qfQZa.js +27 -0
- package/dist/pako.esm-SxljTded.js +2216 -0
- package/dist/raw-BzEs_FKC.js +9 -0
- package/dist/troika-three-text.esm-26C0zM6u.js +5685 -0
- package/dist/webimage-CwJqBfRI.js +29 -0
- package/dist/zstd-CO575QiM.js +769 -0
- package/dist-tsc/ControlledLauncher.d.ts +2 -0
- package/dist-tsc/ControlledLauncher.d.ts.map +1 -0
- package/dist-tsc/ControlledLauncher.js +129 -0
- package/dist-tsc/LauncherStart.d.ts +2 -0
- package/dist-tsc/LauncherStart.d.ts.map +1 -0
- package/dist-tsc/LauncherStart.js +142 -0
- package/dist-tsc/UncontrolledLauncher.d.ts +14 -0
- package/dist-tsc/UncontrolledLauncher.d.ts.map +1 -0
- package/dist-tsc/UncontrolledLauncher.js +52 -0
- package/dist-tsc/index.d.ts +3 -0
- package/dist-tsc/index.d.ts.map +1 -0
- package/dist-tsc/index.js +2 -0
- package/dist-tsc/launcher-styles.d.ts +10 -0
- package/dist-tsc/launcher-styles.d.ts.map +1 -0
- package/dist-tsc/launcher-styles.js +69 -0
- package/dist-tsc/use-hash-param.d.ts +3 -0
- package/dist-tsc/use-hash-param.d.ts.map +1 -0
- package/dist-tsc/use-hash-param.js +110 -0
- package/dist-tsc/use-query-or-hash-param.d.ts +2 -0
- package/dist-tsc/use-query-or-hash-param.d.ts.map +1 -0
- package/dist-tsc/use-query-or-hash-param.js +51 -0
- package/package.json +51 -0
- package/src/ControlledLauncher.js +208 -0
- package/src/LauncherStart.js +351 -0
- package/src/UncontrolledLauncher.js +110 -0
- package/src/index.js +2 -0
- package/src/launcher-styles.js +71 -0
- package/src/use-hash-param.js +124 -0
- package/src/use-query-or-hash-param.js +63 -0
package/package.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@vitessce/launcher",
|
|
3
|
+
"version": "3.8.6",
|
|
4
|
+
"author": "HIDIVE Lab at HMS",
|
|
5
|
+
"homepage": "http://vitessce.io",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/vitessce/vitessce.git"
|
|
9
|
+
},
|
|
10
|
+
"license": "MIT",
|
|
11
|
+
"type": "module",
|
|
12
|
+
"main": "dist/index.js",
|
|
13
|
+
"files": [
|
|
14
|
+
"src",
|
|
15
|
+
"dist",
|
|
16
|
+
"dist-tsc"
|
|
17
|
+
],
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"clsx": "^1.1.1",
|
|
20
|
+
"use-query-params": "^1.1.9",
|
|
21
|
+
"react-dropzone": "^14.3.8",
|
|
22
|
+
"@tanstack/react-query": "^5.80.2",
|
|
23
|
+
"@vitessce/all": "3.8.6",
|
|
24
|
+
"@vitessce/constants-internal": "3.8.6",
|
|
25
|
+
"@vitessce/example-configs": "3.8.6",
|
|
26
|
+
"@vitessce/example-plugins": "3.8.6",
|
|
27
|
+
"@vitessce/globals": "3.8.6",
|
|
28
|
+
"@vitessce/styles": "3.8.6",
|
|
29
|
+
"@vitessce/vit-s": "3.8.6",
|
|
30
|
+
"@vitessce/config": "3.8.6"
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"react": "18.3.1",
|
|
34
|
+
"vite": "^7.0.0",
|
|
35
|
+
"vitest": "^3.1.4"
|
|
36
|
+
},
|
|
37
|
+
"peerDependencies": {
|
|
38
|
+
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
|
39
|
+
},
|
|
40
|
+
"scripts": {
|
|
41
|
+
"bundle": "pnpm exec vite build -c ../../scripts/vite.config.mjs",
|
|
42
|
+
"test": "pnpm exec vitest --run"
|
|
43
|
+
},
|
|
44
|
+
"module": "dist/index.js",
|
|
45
|
+
"exports": {
|
|
46
|
+
".": {
|
|
47
|
+
"types": "./dist-tsc/index.d.ts",
|
|
48
|
+
"import": "./dist/index.js"
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
/* eslint-disable no-unused-vars */
|
|
2
|
+
/* eslint-disable no-nested-ternary */
|
|
3
|
+
/* eslint-disable max-len */
|
|
4
|
+
/* eslint-disable no-console */
|
|
5
|
+
/* eslint-disable prefer-destructuring */
|
|
6
|
+
import React, { useMemo, useState, useEffect } from 'react';
|
|
7
|
+
import { Vitessce } from '@vitessce/all';
|
|
8
|
+
import { generateConfigAlt as generateConfig, parseUrls } from '@vitessce/config';
|
|
9
|
+
import { Alert } from '@vitessce/styles';
|
|
10
|
+
import clsx from 'clsx';
|
|
11
|
+
import { QueryClient, QueryClientProvider, useQuery } from '@tanstack/react-query';
|
|
12
|
+
import { useStyles } from './launcher-styles.js';
|
|
13
|
+
import { LauncherStart } from './LauncherStart.js';
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
function logConfigUpgrade(prevConfig, nextConfig) {
|
|
17
|
+
// eslint-disable-next-line no-console
|
|
18
|
+
console.log(`Upgrade view config schema from ${prevConfig.version} to ${nextConfig.version}`);
|
|
19
|
+
// eslint-disable-next-line no-console
|
|
20
|
+
console.log(prevConfig);
|
|
21
|
+
// eslint-disable-next-line no-console
|
|
22
|
+
console.log(nextConfig);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function ControlledLauncherInner(props) {
|
|
26
|
+
const {
|
|
27
|
+
isEditing,
|
|
28
|
+
setIsEditing,
|
|
29
|
+
setIsUsingLocalFiles,
|
|
30
|
+
configUrl,
|
|
31
|
+
setConfigUrl,
|
|
32
|
+
sourceUrlArr,
|
|
33
|
+
setSourceUrlArr,
|
|
34
|
+
marginTop = 0,
|
|
35
|
+
launcherError,
|
|
36
|
+
setLauncherError,
|
|
37
|
+
} = props;
|
|
38
|
+
|
|
39
|
+
const { classes } = useStyles();
|
|
40
|
+
|
|
41
|
+
const [validConfigFromDroppedData, setValidConfigFromDroppedData] = useState(null);
|
|
42
|
+
const [storesFromDroppedData, setStoresFromDroppedData] = useState(null);
|
|
43
|
+
|
|
44
|
+
const configUrlQueryEnabled = !isEditing && !!configUrl;
|
|
45
|
+
const configUrlResult = useQuery({
|
|
46
|
+
enabled: configUrlQueryEnabled,
|
|
47
|
+
queryKey: ['config-from-url', configUrl],
|
|
48
|
+
queryFn: async () => {
|
|
49
|
+
const resp = await fetch(configUrl);
|
|
50
|
+
if (!resp.ok) {
|
|
51
|
+
throw new Error(`Failed to fetch config from URL: ${resp.status} ${resp.statusText}`);
|
|
52
|
+
}
|
|
53
|
+
const configJson = await resp.json();
|
|
54
|
+
return configJson;
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
const sourceUrlQueryEnabled = !isEditing && Array.isArray(sourceUrlArr) && sourceUrlArr.length > 0;
|
|
59
|
+
const sourceUrlResult = useQuery({
|
|
60
|
+
enabled: sourceUrlQueryEnabled,
|
|
61
|
+
queryKey: ['data-metadata-from-urls', sourceUrlArr],
|
|
62
|
+
queryFn: async () => {
|
|
63
|
+
const { config, stores } = await generateConfig(parseUrls(sourceUrlArr));
|
|
64
|
+
return { config, stores };
|
|
65
|
+
},
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
let isLoading = false;
|
|
69
|
+
let validConfig = null;
|
|
70
|
+
let stores = null;
|
|
71
|
+
if (configUrlQueryEnabled) {
|
|
72
|
+
isLoading = configUrlResult.isLoading;
|
|
73
|
+
if (configUrlResult.isSuccess) {
|
|
74
|
+
validConfig = configUrlResult.data;
|
|
75
|
+
setLauncherError(null);
|
|
76
|
+
} else if (configUrlResult.isError) {
|
|
77
|
+
setIsEditing(true);
|
|
78
|
+
setLauncherError(`Error loading config from URL: ${configUrlResult.error.message}`);
|
|
79
|
+
}
|
|
80
|
+
} else if (sourceUrlQueryEnabled) {
|
|
81
|
+
isLoading = sourceUrlResult.isLoading;
|
|
82
|
+
if (sourceUrlResult.isSuccess) {
|
|
83
|
+
validConfig = sourceUrlResult.data.config.toJSON();
|
|
84
|
+
stores = sourceUrlResult.data.stores;
|
|
85
|
+
setLauncherError(null);
|
|
86
|
+
} else if (sourceUrlResult.isError) {
|
|
87
|
+
setIsEditing(true);
|
|
88
|
+
setLauncherError(`Error loading data/metadata from source URL(s): ${sourceUrlResult.error.message}`);
|
|
89
|
+
}
|
|
90
|
+
} else if (validConfigFromDroppedData) {
|
|
91
|
+
validConfig = validConfigFromDroppedData;
|
|
92
|
+
stores = storesFromDroppedData;
|
|
93
|
+
setLauncherError(null);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
// Possible states:
|
|
98
|
+
// - needsStart: No URL parameters; Show the launcher UI.
|
|
99
|
+
// - Awaiting URL parameter parsing.
|
|
100
|
+
// - Invalid/unrecognized example ID
|
|
101
|
+
// - Loading config or data/metadata from URL.
|
|
102
|
+
// - Loaded config but was invalid.
|
|
103
|
+
// - Could not identify a valid/supported data format from data file extension(s).
|
|
104
|
+
// - Suggest to append `$supportedFileType` to source URL(s) to specify the data format if file extension is non-standard.
|
|
105
|
+
// - Failed to load config or data/metadata from URL.
|
|
106
|
+
// - Error if BOTH config and source parameters are provided.
|
|
107
|
+
// - Error if BOTH hash and query parameters are provided.
|
|
108
|
+
|
|
109
|
+
const pageMode = false; // TODO
|
|
110
|
+
const PageComponent = null; // TODO
|
|
111
|
+
const isExpanded = true; // TODO
|
|
112
|
+
|
|
113
|
+
const onDropHandler = null; // TODO
|
|
114
|
+
const debug = false; // TODO
|
|
115
|
+
const theme = 'light2'; // TODO
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
return (isEditing ? (
|
|
119
|
+
<>
|
|
120
|
+
{launcherError ? (
|
|
121
|
+
<Alert severity="error" onClose={() => setLauncherError(null)}>
|
|
122
|
+
{launcherError}
|
|
123
|
+
</Alert>
|
|
124
|
+
) : null}
|
|
125
|
+
<LauncherStart
|
|
126
|
+
setIsEditing={setIsEditing}
|
|
127
|
+
setIsUsingLocalFiles={setIsUsingLocalFiles}
|
|
128
|
+
configUrl={configUrl}
|
|
129
|
+
setConfigUrl={setConfigUrl}
|
|
130
|
+
sourceUrlArr={sourceUrlArr}
|
|
131
|
+
setSourceUrlArr={setSourceUrlArr}
|
|
132
|
+
setValidConfigFromDroppedData={setValidConfigFromDroppedData}
|
|
133
|
+
setStoresFromDroppedData={setStoresFromDroppedData}
|
|
134
|
+
launcherError={launcherError}
|
|
135
|
+
setLauncherError={setLauncherError}
|
|
136
|
+
/>
|
|
137
|
+
</>
|
|
138
|
+
) : (validConfig ? (
|
|
139
|
+
<main
|
|
140
|
+
className={clsx(classes.vitessceApp, { 'vitessce-expanded': isExpanded, 'vitessce-page': pageMode })}
|
|
141
|
+
style={{
|
|
142
|
+
height: `calc(100vh - ${marginTop}px)`,
|
|
143
|
+
}}
|
|
144
|
+
>
|
|
145
|
+
{pageMode ? (
|
|
146
|
+
<style>{`
|
|
147
|
+
.vitessce-container {
|
|
148
|
+
height: max(100%,100vh);
|
|
149
|
+
width: 100%;
|
|
150
|
+
overflow: hidden;
|
|
151
|
+
position: relative;
|
|
152
|
+
left: 0;
|
|
153
|
+
}
|
|
154
|
+
`}
|
|
155
|
+
</style>
|
|
156
|
+
) : (
|
|
157
|
+
<style>{`
|
|
158
|
+
.vitessce-container {
|
|
159
|
+
min-height: calc(100vh - ${marginTop}px);
|
|
160
|
+
width: 100%;
|
|
161
|
+
overflow: hidden;
|
|
162
|
+
position: relative;
|
|
163
|
+
left: 0;
|
|
164
|
+
}
|
|
165
|
+
`}
|
|
166
|
+
</style>
|
|
167
|
+
)}
|
|
168
|
+
<Vitessce
|
|
169
|
+
theme={theme}
|
|
170
|
+
validateOnConfigChange={debug}
|
|
171
|
+
onConfigChange={debug ? console.log : undefined}
|
|
172
|
+
onConfigUpgrade={debug ? logConfigUpgrade : undefined}
|
|
173
|
+
config={validConfig}
|
|
174
|
+
height={isExpanded ? undefined : 800}
|
|
175
|
+
pageMode={pageMode}
|
|
176
|
+
stores={stores}
|
|
177
|
+
onDrop={onDropHandler}
|
|
178
|
+
>
|
|
179
|
+
{pageMode && PageComponent ? (<PageComponent />) : null}
|
|
180
|
+
</Vitessce>
|
|
181
|
+
</main>
|
|
182
|
+
) : (
|
|
183
|
+
isLoading ? (
|
|
184
|
+
<h2>Loading...</h2>
|
|
185
|
+
) : (
|
|
186
|
+
<p>Error: undefined state</p>
|
|
187
|
+
)
|
|
188
|
+
)));
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
export function ControlledLauncher(props) {
|
|
193
|
+
const queryClient = useMemo(() => new QueryClient({
|
|
194
|
+
// Reference: https://tanstack.com/query/latest/docs/react/guides/window-focus-refetching
|
|
195
|
+
defaultOptions: {
|
|
196
|
+
queries: {
|
|
197
|
+
refetchOnWindowFocus: false,
|
|
198
|
+
retry: 2,
|
|
199
|
+
},
|
|
200
|
+
},
|
|
201
|
+
}), []);
|
|
202
|
+
|
|
203
|
+
return (
|
|
204
|
+
<QueryClientProvider client={queryClient}>
|
|
205
|
+
<ControlledLauncherInner {...props} />
|
|
206
|
+
</QueryClientProvider>
|
|
207
|
+
);
|
|
208
|
+
}
|
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
/* eslint-disable no-unused-vars */
|
|
2
|
+
/* eslint-disable react/no-unknown-property */
|
|
3
|
+
/* eslint-disable max-len */
|
|
4
|
+
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
|
5
|
+
import { createOnDrop } from '@vitessce/vit-s';
|
|
6
|
+
import {
|
|
7
|
+
TextField,
|
|
8
|
+
Button,
|
|
9
|
+
Typography,
|
|
10
|
+
Card,
|
|
11
|
+
CardContent,
|
|
12
|
+
Accordion,
|
|
13
|
+
AccordionSummary,
|
|
14
|
+
AccordionDetails,
|
|
15
|
+
ExpandMore,
|
|
16
|
+
} from '@vitessce/styles';
|
|
17
|
+
import clsx from 'clsx';
|
|
18
|
+
import { useStyles } from './launcher-styles.js';
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
export function LauncherStart(props) {
|
|
22
|
+
const {
|
|
23
|
+
setIsEditing,
|
|
24
|
+
setIsUsingLocalFiles,
|
|
25
|
+
configUrl,
|
|
26
|
+
setConfigUrl,
|
|
27
|
+
sourceUrlArr,
|
|
28
|
+
setSourceUrlArr,
|
|
29
|
+
setValidConfigFromDroppedData,
|
|
30
|
+
setStoresFromDroppedData,
|
|
31
|
+
setLauncherError,
|
|
32
|
+
} = props;
|
|
33
|
+
const { classes } = useStyles();
|
|
34
|
+
|
|
35
|
+
const [spotlightCard, setSpotlightCard] = useState(null);
|
|
36
|
+
|
|
37
|
+
// Single dropzone for both data files/folders and config files.
|
|
38
|
+
const dropzoneRef = useRef(null);
|
|
39
|
+
const localDataInputFoldersRef = useRef(null);
|
|
40
|
+
const localDataInputFilesRef = useRef(null);
|
|
41
|
+
const localConfigInputRef = useRef(null);
|
|
42
|
+
|
|
43
|
+
// Handlers for drag-and-drop and file input changes.
|
|
44
|
+
const onDropHandler = useMemo(() => createOnDrop(
|
|
45
|
+
{ setViewConfig: setValidConfigFromDroppedData, setStores: setStoresFromDroppedData },
|
|
46
|
+
false, // isFileInput
|
|
47
|
+
true, // isConfigInput
|
|
48
|
+
), [setValidConfigFromDroppedData, setStoresFromDroppedData]);
|
|
49
|
+
const onFileInputHandler = useMemo(() => createOnDrop(
|
|
50
|
+
{ setViewConfig: setValidConfigFromDroppedData, setStores: setStoresFromDroppedData },
|
|
51
|
+
true, // isFileInput
|
|
52
|
+
true, // isConfigInput
|
|
53
|
+
), [setValidConfigFromDroppedData, setStoresFromDroppedData]);
|
|
54
|
+
|
|
55
|
+
// Effect for setting up drag-and-drop event listeners for data file/folder input.
|
|
56
|
+
useEffect(() => {
|
|
57
|
+
// Make the entire Launcher a dropzone,
|
|
58
|
+
// and then determine whether the dropped file(s) are config or data files
|
|
59
|
+
// (e.g., if a single .json file, assume config; else data).
|
|
60
|
+
const zone = dropzoneRef.current;
|
|
61
|
+
const zoneDataInput1 = localDataInputFoldersRef.current;
|
|
62
|
+
const zoneDataInput2 = localDataInputFilesRef.current;
|
|
63
|
+
const zoneConfigInput = localConfigInputRef.current;
|
|
64
|
+
|
|
65
|
+
const onDragEnter = (e) => {
|
|
66
|
+
e.preventDefault();
|
|
67
|
+
setSpotlightCard('data-local');
|
|
68
|
+
};
|
|
69
|
+
const onDragLeave = () => {
|
|
70
|
+
setSpotlightCard(null);
|
|
71
|
+
};
|
|
72
|
+
const onDragOver = (e) => {
|
|
73
|
+
e.preventDefault();
|
|
74
|
+
};
|
|
75
|
+
const onDrop = async (e) => {
|
|
76
|
+
e.preventDefault();
|
|
77
|
+
|
|
78
|
+
setIsEditing(false);
|
|
79
|
+
// Clear the other field values.
|
|
80
|
+
setConfigUrl(undefined);
|
|
81
|
+
setSourceUrlArr(undefined);
|
|
82
|
+
|
|
83
|
+
// Call onDrop handler passed in from parent of <VitS/Vitessce/> via prop.
|
|
84
|
+
try {
|
|
85
|
+
await onDropHandler(e);
|
|
86
|
+
setIsUsingLocalFiles(true);
|
|
87
|
+
} catch (err) {
|
|
88
|
+
setLauncherError(`Error processing dropped files: ${err.message}`);
|
|
89
|
+
setIsEditing(true);
|
|
90
|
+
}
|
|
91
|
+
setSpotlightCard(null);
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const onDataInputChange = async (e) => {
|
|
95
|
+
// Reference: https://developer.mozilla.org/en-US/docs/Web/API/HTML_Drag_and_Drop_API/File_drag_and_drop
|
|
96
|
+
e.preventDefault();
|
|
97
|
+
|
|
98
|
+
setIsEditing(false);
|
|
99
|
+
setSpotlightCard('data-local');
|
|
100
|
+
// Clear the other field values.
|
|
101
|
+
setConfigUrl(undefined);
|
|
102
|
+
setSourceUrlArr(undefined);
|
|
103
|
+
|
|
104
|
+
try {
|
|
105
|
+
await onFileInputHandler(e);
|
|
106
|
+
setIsUsingLocalFiles(true);
|
|
107
|
+
} catch (err) {
|
|
108
|
+
setLauncherError(`Error processing selected files: ${err.message}`);
|
|
109
|
+
setIsEditing(true);
|
|
110
|
+
}
|
|
111
|
+
setSpotlightCard(null);
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
const onConfigInputChange = async (e) => {
|
|
115
|
+
// Reference: https://developer.mozilla.org/en-US/docs/Web/API/HTML_Drag_and_Drop_API/File_drag_and_drop
|
|
116
|
+
e.preventDefault();
|
|
117
|
+
|
|
118
|
+
setIsEditing(false);
|
|
119
|
+
setSpotlightCard('config-local');
|
|
120
|
+
// Clear the other field values.
|
|
121
|
+
setConfigUrl(undefined);
|
|
122
|
+
setSourceUrlArr(undefined);
|
|
123
|
+
|
|
124
|
+
try {
|
|
125
|
+
await onFileInputHandler(e);
|
|
126
|
+
setIsUsingLocalFiles(true);
|
|
127
|
+
} catch (err) {
|
|
128
|
+
setLauncherError(`Error processing selected file: ${err.message}`);
|
|
129
|
+
setIsEditing(true);
|
|
130
|
+
}
|
|
131
|
+
setSpotlightCard(null);
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
// The dragenter event happens at the moment you drag something in to the target element,
|
|
135
|
+
// and then it stops.
|
|
136
|
+
// The dragover event happens during the time you are dragging something until you drop it.
|
|
137
|
+
zone.addEventListener('dragenter', onDragEnter);
|
|
138
|
+
zone.addEventListener('dragleave', onDragLeave);
|
|
139
|
+
zone.addEventListener('dragover', onDragOver);
|
|
140
|
+
zone.addEventListener('drop', onDrop);
|
|
141
|
+
zoneDataInput1.addEventListener('change', onDataInputChange);
|
|
142
|
+
zoneDataInput2.addEventListener('change', onDataInputChange);
|
|
143
|
+
zoneConfigInput.addEventListener('change', onConfigInputChange);
|
|
144
|
+
|
|
145
|
+
return () => {
|
|
146
|
+
zone.removeEventListener('dragenter', onDragEnter);
|
|
147
|
+
zone.removeEventListener('dragleave', onDragLeave);
|
|
148
|
+
zone.removeEventListener('dragover', onDragOver);
|
|
149
|
+
zone.removeEventListener('drop', onDrop);
|
|
150
|
+
zoneDataInput1.removeEventListener('change', onDataInputChange);
|
|
151
|
+
zoneDataInput2.removeEventListener('change', onDataInputChange);
|
|
152
|
+
zoneConfigInput.removeEventListener('change', onConfigInputChange);
|
|
153
|
+
};
|
|
154
|
+
}, [dropzoneRef, localDataInputFoldersRef, localDataInputFilesRef,
|
|
155
|
+
onDropHandler, setIsUsingLocalFiles, setIsEditing, setSpotlightCard,
|
|
156
|
+
onFileInputHandler, setConfigUrl, setSourceUrlArr, setLauncherError,
|
|
157
|
+
]);
|
|
158
|
+
|
|
159
|
+
return (
|
|
160
|
+
<div className={classes.launcher} ref={dropzoneRef}>
|
|
161
|
+
<div className={classes.launcherRow}>
|
|
162
|
+
<div className={clsx({ [classes.cardDim]: spotlightCard && (spotlightCard === 'config-local' || spotlightCard === 'config-remote') })}>
|
|
163
|
+
<Typography variant="h5">Begin with data</Typography>
|
|
164
|
+
{/* TODO: generalize to support both "begin with data" and "add data" flows? */}
|
|
165
|
+
<p>Let Vitessce <a href="https://vitessce.io/docs/default-config-json/">infer an initial configuration</a> based on the contents of your <a href="https://vitessce.io/docs/data-types-file-types/">data files.</a></p>
|
|
166
|
+
{/* TODO: eventually, link to tutorials/videos showing how to use each of the Begin with... options */}
|
|
167
|
+
</div>
|
|
168
|
+
<div className={classes.launcherCardRow}>
|
|
169
|
+
<Card className={clsx(classes.launcherCard, { [classes.cardDim]: spotlightCard && spotlightCard !== 'data-local' })}>
|
|
170
|
+
<span className={classes.cardDashed}>
|
|
171
|
+
<CardContent className={classes.cardContent}>
|
|
172
|
+
<Typography variant="h6">Local data <br />(Drag and drop)</Typography>
|
|
173
|
+
<p>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.</p>
|
|
174
|
+
<div className={classes.buttonSpacer} />
|
|
175
|
+
<div className={classes.dataButtonGroup}>
|
|
176
|
+
<Button component="label" htmlFor="data-local-input-directory" variant="outlined" fullWidth>
|
|
177
|
+
<span>Select folder(s)</span>
|
|
178
|
+
<input
|
|
179
|
+
id="data-local-input-directory"
|
|
180
|
+
type="file"
|
|
181
|
+
ref={localDataInputFoldersRef}
|
|
182
|
+
className={classes.hiddenFileInput}
|
|
183
|
+
multiple
|
|
184
|
+
directory="true"
|
|
185
|
+
webkitdirectory="true"
|
|
186
|
+
mozdirectory="true"
|
|
187
|
+
/>
|
|
188
|
+
</Button>
|
|
189
|
+
|
|
190
|
+
<Button component="label" htmlFor="data-local-input-files" variant="outlined" fullWidth>
|
|
191
|
+
<span>Select file(s)</span>
|
|
192
|
+
<input
|
|
193
|
+
id="data-local-input-files"
|
|
194
|
+
type="file"
|
|
195
|
+
ref={localDataInputFilesRef}
|
|
196
|
+
className={classes.hiddenFileInput}
|
|
197
|
+
multiple
|
|
198
|
+
/>
|
|
199
|
+
</Button>
|
|
200
|
+
</div>
|
|
201
|
+
<div className={classes.buttonSpacer} />
|
|
202
|
+
</CardContent>
|
|
203
|
+
</span>
|
|
204
|
+
</Card>
|
|
205
|
+
<Card className={clsx(classes.launcherCard, { [classes.cardDim]: spotlightCard && spotlightCard !== 'data-remote' })}>
|
|
206
|
+
<CardContent>
|
|
207
|
+
<Typography variant="h6">Remote data <br /> (Load from URL)</Typography>
|
|
208
|
+
<p>
|
|
209
|
+
Enter file (or folder) URLs to view them in Vitessce. Vitessce launches with a default configuration (based on file types and contents). See our <a href="https://vitessce.io/docs/data-hosting/">data hosting</a> documentation for assistance in making your data accessible to Vitessce.
|
|
210
|
+
{/* <span className="select-examples">
|
|
211
|
+
<label>Try an example: </label>
|
|
212
|
+
<select>
|
|
213
|
+
<option>TODO</option>
|
|
214
|
+
</select>
|
|
215
|
+
</span> */}
|
|
216
|
+
</p>
|
|
217
|
+
<div className={classes.textareaAndButton}>
|
|
218
|
+
<TextField
|
|
219
|
+
multiline
|
|
220
|
+
label="Data URL(s)"
|
|
221
|
+
placeholder="One or more file URLs (semicolon-separated)"
|
|
222
|
+
minRows={2}
|
|
223
|
+
className={classes.dataUrlTextarea}
|
|
224
|
+
value={sourceUrlArr ? sourceUrlArr.join(';') : ''}
|
|
225
|
+
onChange={(e) => {
|
|
226
|
+
// Clear existing state.
|
|
227
|
+
setConfigUrl(undefined);
|
|
228
|
+
setSourceUrlArr(e.target.value);
|
|
229
|
+
}}
|
|
230
|
+
onBlur={() => setSpotlightCard(null)}
|
|
231
|
+
onFocus={() => {
|
|
232
|
+
setSpotlightCard('data-remote');
|
|
233
|
+
// We need to set isEditing, otherwise the launcher UI will disappear
|
|
234
|
+
// as soon as there is a non-empty sourceUrlArr value.
|
|
235
|
+
setIsEditing(true);
|
|
236
|
+
}}
|
|
237
|
+
/>
|
|
238
|
+
<Button
|
|
239
|
+
variant="outlined"
|
|
240
|
+
onClick={() => {
|
|
241
|
+
setIsEditing(false);
|
|
242
|
+
}}
|
|
243
|
+
>Visualize
|
|
244
|
+
</Button>
|
|
245
|
+
</div>
|
|
246
|
+
<Accordion disableGutters style={{ marginTop: '5px' }}>
|
|
247
|
+
<AccordionSummary
|
|
248
|
+
expandIcon={<ExpandMore />}
|
|
249
|
+
aria-controls="panel1-content"
|
|
250
|
+
id="panel1-header"
|
|
251
|
+
>Specifying file extensions
|
|
252
|
+
</AccordionSummary>
|
|
253
|
+
<AccordionDetails>
|
|
254
|
+
<Typography variant="body2">
|
|
255
|
+
Vitessce automatically tries to infer the file type based on the file extension.
|
|
256
|
+
For URLs with non-standard extensions, the file type can be specified by appending <code><b>$</b>FILE_TYPE</code> to the URL.
|
|
257
|
+
For example, to specify that a folder at <code>https://example.com/my_data.zarr</code> is in SpatialData format, use the URL <code>https://example.com/my_data.zarr<b>$</b>spatialdata.zarr</code>.
|
|
258
|
+
See our <a href="https://vitessce.io/docs/data-types-file-types/">data types and file types documentation</a> for a list of supported file types.
|
|
259
|
+
</Typography>
|
|
260
|
+
</AccordionDetails>
|
|
261
|
+
</Accordion>
|
|
262
|
+
</CardContent>
|
|
263
|
+
</Card>
|
|
264
|
+
</div>
|
|
265
|
+
</div>
|
|
266
|
+
<div className={classes.launcherRow}>
|
|
267
|
+
<div className={clsx({ [classes.cardDim]: spotlightCard && (spotlightCard === 'data-local' || spotlightCard === 'data-remote') })}>
|
|
268
|
+
<Typography variant="h5">Begin with configuration</Typography>
|
|
269
|
+
<p>Use the options below to supply a Vitessce <a href="https://vitessce.io/docs/view-config-json/">configuration in JSON format.</a></p>
|
|
270
|
+
</div>
|
|
271
|
+
<div className={classes.launcherCardRow}>
|
|
272
|
+
{/* <div className={classes.launcherCard}>
|
|
273
|
+
<h3>Config Editor</h3>
|
|
274
|
+
<p>Use the online configuration editor to paste, create, or edit a Vitessce configuration using JSON or JS API syntax.</p>
|
|
275
|
+
<button>Launch JSON editor</button>
|
|
276
|
+
<button>Launch JS editor</button>
|
|
277
|
+
<button>Launch Python editor</button>
|
|
278
|
+
</div> */}
|
|
279
|
+
<Card className={clsx(classes.launcherCard, { [classes.cardDim]: spotlightCard && spotlightCard !== 'config-local' })}>
|
|
280
|
+
<span className={classes.cardDashed}>
|
|
281
|
+
<CardContent className={classes.cardContent}>
|
|
282
|
+
<Typography variant="h6">Local config file <br /> (Drag and drop)</Typography>
|
|
283
|
+
<p>View a configured Vitessce visualization by selecting or dropping a local JSON file.</p>
|
|
284
|
+
<div className={classes.buttonSpacer} />
|
|
285
|
+
<Button component="label" variant="outlined" fullWidth>
|
|
286
|
+
<span>Select file</span>
|
|
287
|
+
<input
|
|
288
|
+
type="file"
|
|
289
|
+
ref={localConfigInputRef}
|
|
290
|
+
className={classes.hiddenFileInput}
|
|
291
|
+
accept=".json,application/json"
|
|
292
|
+
/>
|
|
293
|
+
</Button>
|
|
294
|
+
<div className={classes.buttonSpacer} />
|
|
295
|
+
</CardContent>
|
|
296
|
+
</span>
|
|
297
|
+
</Card>
|
|
298
|
+
<Card className={clsx(classes.launcherCard, { [classes.cardDim]: spotlightCard && spotlightCard !== 'config-remote' })}>
|
|
299
|
+
<CardContent>
|
|
300
|
+
<Typography variant="h6">Remote config file <br /> (Load from URL)</Typography>
|
|
301
|
+
<p>
|
|
302
|
+
View a configured Vitessce visualization by specifying a URL to a JSON config file. See our <a href="https://vitessce.io/docs/data-hosting/">data hosting</a> documentation for assistance in making your config file accessible to Vitessce.
|
|
303
|
+
{/* <span className="select-examples">
|
|
304
|
+
<label>Try an example: </label>
|
|
305
|
+
<select>
|
|
306
|
+
<option>TODO</option>
|
|
307
|
+
</select>
|
|
308
|
+
</span> */}
|
|
309
|
+
</p>
|
|
310
|
+
<div className={classes.textareaAndButton}>
|
|
311
|
+
{/* TODO: add another textField in case the user wants to paste JSON and view it via a data URI? */}
|
|
312
|
+
<TextField
|
|
313
|
+
multiline
|
|
314
|
+
label="Config URL"
|
|
315
|
+
placeholder="Enter a URL"
|
|
316
|
+
minRows={2}
|
|
317
|
+
className={classes.dataUrlTextarea}
|
|
318
|
+
value={configUrl || ''}
|
|
319
|
+
onChange={(e) => {
|
|
320
|
+
// Clear existing state.
|
|
321
|
+
setSourceUrlArr(undefined);
|
|
322
|
+
setConfigUrl(e.target.value);
|
|
323
|
+
}}
|
|
324
|
+
onBlur={() => setSpotlightCard(null)}
|
|
325
|
+
onFocus={() => {
|
|
326
|
+
setSpotlightCard('config-remote');
|
|
327
|
+
// We need to set isEditing, otherwise the launcher UI will disappear
|
|
328
|
+
// as soon as there is a non-empty configUrl value.
|
|
329
|
+
setIsEditing(true);
|
|
330
|
+
}}
|
|
331
|
+
/>
|
|
332
|
+
<Button
|
|
333
|
+
variant="outlined"
|
|
334
|
+
onClick={() => {
|
|
335
|
+
setIsEditing(false);
|
|
336
|
+
}}
|
|
337
|
+
>Visualize
|
|
338
|
+
</Button>
|
|
339
|
+
</div>
|
|
340
|
+
</CardContent>
|
|
341
|
+
</Card>
|
|
342
|
+
</div>
|
|
343
|
+
{/* TODO: finish implementing the below row once a sidebar is available that allows for adding data post-launching */}
|
|
344
|
+
{/* <div className={classes.launcherRow}>
|
|
345
|
+
<Typography variant="h5">Begin with a view type</Typography>
|
|
346
|
+
<p>Select a view type to get started with configuring a new visualization.</p>
|
|
347
|
+
</div> */}
|
|
348
|
+
</div>
|
|
349
|
+
</div>
|
|
350
|
+
);
|
|
351
|
+
}
|