@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
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/* eslint-disable no-unused-vars */
|
|
2
|
+
/* eslint-disable max-len */
|
|
3
|
+
import React, { useEffect, useState } from 'react';
|
|
4
|
+
import { QueryParamProvider } from 'use-query-params';
|
|
5
|
+
import { useHashOrQueryParam } from './use-query-or-hash-param.js';
|
|
6
|
+
import { ControlledLauncher } from './ControlledLauncher.js';
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
function UncontrolledLauncherInner(props) {
|
|
10
|
+
const {
|
|
11
|
+
marginTop = 0,
|
|
12
|
+
setIsFooterVisible,
|
|
13
|
+
configUrlParamName = 'config',
|
|
14
|
+
sourceUrlParamName = 'source',
|
|
15
|
+
exampleIdParamName = 'example',
|
|
16
|
+
isEditingParamName = 'edit',
|
|
17
|
+
} = props;
|
|
18
|
+
|
|
19
|
+
const [launcherError, setLauncherError] = useState(null);
|
|
20
|
+
|
|
21
|
+
// Logic for managing state and query/hash params.
|
|
22
|
+
const [exampleId, setExampleId, exampleIdParamError] = useHashOrQueryParam(exampleIdParamName, undefined, 'string');
|
|
23
|
+
// TODO: support vitessce-link code param
|
|
24
|
+
// const wsCodeValue = useHashOrQueryParam('session', undefined, 'string');
|
|
25
|
+
const [configUrl, setConfigUrl, configUrlParamError] = useHashOrQueryParam(configUrlParamName, undefined, 'string');
|
|
26
|
+
const [sourceUrlArr, setSourceUrlArr, sourceUrlArrParamError] = useHashOrQueryParam(sourceUrlParamName, undefined, 'string-array');
|
|
27
|
+
const [isEditingParam, setIsEditing, isEditingParamError] = useHashOrQueryParam(isEditingParamName, undefined, 'boolean');
|
|
28
|
+
|
|
29
|
+
// We need this here since the URL param will not indicate whether local data was dropped.
|
|
30
|
+
// TODO: Store in a cookie/localStorage? Store in a URL param?
|
|
31
|
+
// However if the page changes, we will lose the dropped data anyway...
|
|
32
|
+
const [isUsingLocalFiles, setIsUsingLocalFiles] = useState(false);
|
|
33
|
+
|
|
34
|
+
// Check for parameter errors (e.g., both hash+query for same value) and conflicting parameters.
|
|
35
|
+
const hasNoParamError = !exampleIdParamError && !configUrlParamError && !sourceUrlArrParamError && !isEditingParamError;
|
|
36
|
+
const hasNoConflictingParams = (
|
|
37
|
+
(exampleId ? 1 : 0)
|
|
38
|
+
+ (configUrl ? 1 : 0)
|
|
39
|
+
+ (sourceUrlArr && sourceUrlArr.length > 0 ? 1 : 0)
|
|
40
|
+
) <= 1;
|
|
41
|
+
const needsStart = !exampleId && !configUrl && (!sourceUrlArr || (Array.isArray(sourceUrlArr) && sourceUrlArr.length === 0));
|
|
42
|
+
|
|
43
|
+
const isEditing = !isUsingLocalFiles && (isEditingParam === true || needsStart);
|
|
44
|
+
|
|
45
|
+
useEffect(() => {
|
|
46
|
+
if (typeof setIsFooterVisible === 'function') {
|
|
47
|
+
setIsFooterVisible(isEditing);
|
|
48
|
+
}
|
|
49
|
+
}, [isEditing, setIsFooterVisible]);
|
|
50
|
+
|
|
51
|
+
return (
|
|
52
|
+
<ControlledLauncher
|
|
53
|
+
isEditing={isEditing}
|
|
54
|
+
setIsEditing={setIsEditing}
|
|
55
|
+
isUsingLocalFiles={isUsingLocalFiles}
|
|
56
|
+
setIsUsingLocalFiles={setIsUsingLocalFiles}
|
|
57
|
+
configUrl={configUrl}
|
|
58
|
+
setConfigUrl={setConfigUrl}
|
|
59
|
+
sourceUrlArr={sourceUrlArr}
|
|
60
|
+
setSourceUrlArr={setSourceUrlArr}
|
|
61
|
+
marginTop={marginTop}
|
|
62
|
+
launcherError={launcherError}
|
|
63
|
+
setLauncherError={setLauncherError}
|
|
64
|
+
/>
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
*
|
|
70
|
+
* @param {object} props
|
|
71
|
+
* @param {number} [props.marginTop=0] The distance in pixels from the top of the window.
|
|
72
|
+
* This is used to calculate the height of the Vitessce component (100vh minus marginTop).
|
|
73
|
+
* @param {function} [props.setIsFooterVisible] Callback to set whether the footer is visible.
|
|
74
|
+
* Used to hide the footer when Vitessce component is visible.
|
|
75
|
+
* @returns
|
|
76
|
+
*/
|
|
77
|
+
export function UncontrolledLauncher(props) {
|
|
78
|
+
const {
|
|
79
|
+
marginTop = 0,
|
|
80
|
+
setIsFooterVisible = null,
|
|
81
|
+
configUrlParamName = 'config',
|
|
82
|
+
sourceUrlParamName = 'source',
|
|
83
|
+
exampleIdParamName = 'example',
|
|
84
|
+
isEditingParamName = 'edit',
|
|
85
|
+
// TODO: Optional mapping from example IDs to their full JSON config, and potentially more values such as PageComponent, Plugins, etc.
|
|
86
|
+
// See https://github.com/vitessce/vitessce/blob/main/sites/demo/src/api.js
|
|
87
|
+
exampleConfigs = null,
|
|
88
|
+
} = props;
|
|
89
|
+
|
|
90
|
+
// TODO: wrap in MUI themeProvider?
|
|
91
|
+
return (
|
|
92
|
+
<QueryParamProvider>
|
|
93
|
+
<div style={{ display: 'none' }}>
|
|
94
|
+
{/* TEMP: links for quick testing */}
|
|
95
|
+
<a href="/">Reset</a>
|
|
96
|
+
<a href="/?source=https://storage.googleapis.com/vitessce-demo-data/maynard-2021/151673.sdata.zarr">SpatialData Example</a>
|
|
97
|
+
<a href="/#?source=https://uk1s3.embassy.ebi.ac.uk/idr/zarr/v0.4/idr0062A/6001240.zarr$image.ome-zarr">OME-Zarr Example</a>
|
|
98
|
+
</div>
|
|
99
|
+
<UncontrolledLauncherInner
|
|
100
|
+
marginTop={marginTop}
|
|
101
|
+
setIsFooterVisible={setIsFooterVisible}
|
|
102
|
+
|
|
103
|
+
isEditingParamName={isEditingParamName}
|
|
104
|
+
exampleIdParamName={exampleIdParamName}
|
|
105
|
+
configUrlParamName={configUrlParamName}
|
|
106
|
+
sourceUrlParamName={sourceUrlParamName}
|
|
107
|
+
/>
|
|
108
|
+
</QueryParamProvider>
|
|
109
|
+
);
|
|
110
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { makeStyles } from '@vitessce/styles';
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
export const useStyles = makeStyles()(() => ({
|
|
5
|
+
launcher: {
|
|
6
|
+
display: 'flex',
|
|
7
|
+
flexDirection: 'column',
|
|
8
|
+
margin: '0 10px',
|
|
9
|
+
},
|
|
10
|
+
launcherRow: {
|
|
11
|
+
marginTop: '15px',
|
|
12
|
+
marginBottom: '15px',
|
|
13
|
+
},
|
|
14
|
+
launcherRowTitle: {
|
|
15
|
+
marginBottom: 0,
|
|
16
|
+
},
|
|
17
|
+
launcherCardRow: {
|
|
18
|
+
display: 'grid',
|
|
19
|
+
gridTemplateColumns: '1fr 1fr',
|
|
20
|
+
gap: '10px',
|
|
21
|
+
},
|
|
22
|
+
launcherCard: {
|
|
23
|
+
transition: 'opacity 0.3s',
|
|
24
|
+
},
|
|
25
|
+
cardTitle: {
|
|
26
|
+
marginTop: 0,
|
|
27
|
+
},
|
|
28
|
+
cardDashed: {
|
|
29
|
+
display: 'inline-block',
|
|
30
|
+
boxSizing: 'border-box',
|
|
31
|
+
height: '100%',
|
|
32
|
+
width: '100%',
|
|
33
|
+
borderRadius: '4px',
|
|
34
|
+
border: '2px dashed rgba(128, 128, 128, 0.2)',
|
|
35
|
+
},
|
|
36
|
+
cardDim: {
|
|
37
|
+
opacity: 0.2,
|
|
38
|
+
},
|
|
39
|
+
cardContent: {
|
|
40
|
+
display: 'flex',
|
|
41
|
+
flexDirection: 'column',
|
|
42
|
+
height: '100%',
|
|
43
|
+
boxSizing: 'border-box',
|
|
44
|
+
},
|
|
45
|
+
buttonSpacer: {
|
|
46
|
+
flexGrow: 1,
|
|
47
|
+
},
|
|
48
|
+
dataButtonGroup: {
|
|
49
|
+
width: '100%',
|
|
50
|
+
display: 'flex',
|
|
51
|
+
flexDirection: 'row',
|
|
52
|
+
gap: '5px',
|
|
53
|
+
},
|
|
54
|
+
vitessceApp: {
|
|
55
|
+
position: 'relative',
|
|
56
|
+
display: 'flex',
|
|
57
|
+
width: '100%',
|
|
58
|
+
},
|
|
59
|
+
dataUrlTextarea: {
|
|
60
|
+
width: '100%',
|
|
61
|
+
boxSizing: 'border-box',
|
|
62
|
+
},
|
|
63
|
+
textareaAndButton: {
|
|
64
|
+
display: 'flex',
|
|
65
|
+
flexDirection: 'row',
|
|
66
|
+
gap: '5px',
|
|
67
|
+
},
|
|
68
|
+
hiddenFileInput: {
|
|
69
|
+
display: 'none',
|
|
70
|
+
},
|
|
71
|
+
}));
|
|
@@ -0,0 +1,124 @@
|
|
|
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
|
+
|
|
5
|
+
const getHashSearchParams = (location) => {
|
|
6
|
+
const hash = location.hash.slice(1);
|
|
7
|
+
const [prefix, query] = hash.split('?');
|
|
8
|
+
|
|
9
|
+
return [prefix, new URLSearchParams(query)];
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const getHashParam = (key, location = window.location) => {
|
|
13
|
+
// eslint-disable-next-line no-unused-vars
|
|
14
|
+
const [_, searchParams] = getHashSearchParams(location);
|
|
15
|
+
return searchParams.get(key);
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const getAllHashParams = (key, location = window.location) => {
|
|
19
|
+
// eslint-disable-next-line no-unused-vars
|
|
20
|
+
const [_, searchParams] = getHashSearchParams(location);
|
|
21
|
+
return searchParams.getAll(key);
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const getTypedHashParam = (key, varType) => {
|
|
25
|
+
if (varType === 'string-array') {
|
|
26
|
+
return getAllHashParams(key);
|
|
27
|
+
}
|
|
28
|
+
const val = getHashParam(key);
|
|
29
|
+
let typedVal = val;
|
|
30
|
+
if (varType === 'boolean') {
|
|
31
|
+
try {
|
|
32
|
+
typedVal = JSON.parse(val);
|
|
33
|
+
} catch (e) {
|
|
34
|
+
typedVal = false;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return typedVal;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const setHashParam = (key, value, location = window.location) => {
|
|
41
|
+
const [prefix, searchParams] = getHashSearchParams(location);
|
|
42
|
+
|
|
43
|
+
if (typeof value === 'undefined' || value === '') {
|
|
44
|
+
searchParams.delete(key);
|
|
45
|
+
} else {
|
|
46
|
+
searchParams.set(key, value);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const search = searchParams.toString();
|
|
50
|
+
location.hash = search ? `${prefix}?${search}` : prefix;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
// There is no "pushState" or "replaceState" event on the
|
|
54
|
+
// window, only a "popstate" event. We can add our own
|
|
55
|
+
// pushState and replaceState events by wrapping the
|
|
56
|
+
// methods of the window.history object and dispatching
|
|
57
|
+
// as custom events.
|
|
58
|
+
// Reference: https://stackoverflow.com/a/25673911
|
|
59
|
+
const wrapHistoryMethod = (methodName) => {
|
|
60
|
+
const orig = window.history[methodName];
|
|
61
|
+
window.history.wrapped = true;
|
|
62
|
+
// eslint-disable-next-line func-names
|
|
63
|
+
return function () {
|
|
64
|
+
// eslint-disable-next-line prefer-rest-params
|
|
65
|
+
const rv = orig.apply(this, arguments);
|
|
66
|
+
const e = new Event(methodName);
|
|
67
|
+
// eslint-disable-next-line prefer-rest-params
|
|
68
|
+
e.arguments = arguments;
|
|
69
|
+
window.dispatchEvent(e);
|
|
70
|
+
return rv;
|
|
71
|
+
};
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
export const useHashParam = (key, defaultValue, varType) => {
|
|
75
|
+
const [innerValue, setInnerValue] = useState(getTypedHashParam(key, varType));
|
|
76
|
+
|
|
77
|
+
useEffect(() => {
|
|
78
|
+
let unmounted = false;
|
|
79
|
+
|
|
80
|
+
if (window.history && !window.history.wrapped) {
|
|
81
|
+
window.history.pushState = wrapHistoryMethod('pushState');
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const handleHashChange = () => {
|
|
85
|
+
if (unmounted) {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
const nextValue = getTypedHashParam(key, varType);
|
|
89
|
+
setInnerValue(nextValue);
|
|
90
|
+
};
|
|
91
|
+
window.addEventListener('hashchange', handleHashChange);
|
|
92
|
+
window.addEventListener('pushState', handleHashChange);
|
|
93
|
+
return () => {
|
|
94
|
+
unmounted = true;
|
|
95
|
+
window.removeEventListener('hashchange', handleHashChange);
|
|
96
|
+
window.removeEventListener('pushState', handleHashChange);
|
|
97
|
+
};
|
|
98
|
+
}, [key, varType]);
|
|
99
|
+
|
|
100
|
+
const setValue = useCallback((value) => {
|
|
101
|
+
if (typeof value === 'function') {
|
|
102
|
+
setHashParam(key, value(getHashParam(key)));
|
|
103
|
+
} else {
|
|
104
|
+
setHashParam(key, value);
|
|
105
|
+
}
|
|
106
|
+
}, [key]);
|
|
107
|
+
|
|
108
|
+
return [innerValue === undefined ? defaultValue : innerValue, setValue];
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
export const useSetHashParams = () => (values, location = window.location) => {
|
|
112
|
+
const [prefix, searchParams] = getHashSearchParams(location);
|
|
113
|
+
|
|
114
|
+
Object.entries(values).forEach(([key, value]) => {
|
|
115
|
+
if (typeof value === 'undefined' || value === '') {
|
|
116
|
+
searchParams.delete(key);
|
|
117
|
+
} else {
|
|
118
|
+
searchParams.set(key, value);
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
const search = searchParams.toString();
|
|
123
|
+
location.hash = search ? `${prefix}?${search}` : prefix;
|
|
124
|
+
};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import {
|
|
2
|
+
useQueryParam,
|
|
3
|
+
StringParam as StringQueryParam,
|
|
4
|
+
ArrayParam as StringArrayQueryParam,
|
|
5
|
+
} from 'use-query-params';
|
|
6
|
+
import { useHashParam } from './use-hash-param.js';
|
|
7
|
+
|
|
8
|
+
const DtypeToParamType = {
|
|
9
|
+
string: StringQueryParam,
|
|
10
|
+
'string-array': StringArrayQueryParam,
|
|
11
|
+
// Use string param for boolean since use-query-params only supports 0/1.
|
|
12
|
+
boolean: StringQueryParam,
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
function isTruthyString(v) {
|
|
16
|
+
return ['1', 'true'].includes(String(v).toLowerCase());
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function useHashOrQueryParamAux(paramName, defaultValue, dtype) {
|
|
20
|
+
const [valueQ, setValueQ] = useQueryParam(paramName, DtypeToParamType[dtype]);
|
|
21
|
+
const [valueH, setValueH] = useHashParam(paramName, undefined, dtype);
|
|
22
|
+
|
|
23
|
+
if (dtype === 'boolean') {
|
|
24
|
+
const boolValueQ = valueQ !== undefined ? isTruthyString(valueQ) : undefined;
|
|
25
|
+
const boolValueH = valueH !== undefined ? isTruthyString(valueH) : undefined;
|
|
26
|
+
|
|
27
|
+
return valueQ ? [boolValueQ, setValueQ] : [boolValueH, setValueH];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (dtype === 'string-array') {
|
|
31
|
+
// eslint-disable-next-line max-len
|
|
32
|
+
if (Array.isArray(valueH) && valueH.length > 0 && (!Array.isArray(valueQ) || valueQ.length === 0)) {
|
|
33
|
+
// Check for ; delimiter in individual array values, and split if necessary.
|
|
34
|
+
const splitValues = valueH.map(item => item.split(';')).flat();
|
|
35
|
+
return [splitValues, setValueH];
|
|
36
|
+
// eslint-disable-next-line max-len
|
|
37
|
+
} if (Array.isArray(valueQ) && valueQ.length > 0 && (!Array.isArray(valueH) || valueH.length === 0)) {
|
|
38
|
+
// Check for ; delimiter in individual array values, and split if necessary.
|
|
39
|
+
const splitValues = valueQ.map(item => item.split(';')).flat();
|
|
40
|
+
return [splitValues, setValueQ];
|
|
41
|
+
}
|
|
42
|
+
return [null, setValueH];
|
|
43
|
+
}
|
|
44
|
+
if (Array.isArray(valueQ) || Array.isArray(valueH)) {
|
|
45
|
+
throw new Error(`Expected non-array values for "${paramName}".`);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
if (valueQ && valueH) {
|
|
50
|
+
throw new Error(`Both query and hash parameters provided for "${paramName}". Please provide only one.`);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return valueQ ? [valueQ, setValueQ] : [valueH, setValueH];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function useHashOrQueryParam(paramName, defaultValue, dtype) {
|
|
57
|
+
const [value, setValue] = useHashOrQueryParamAux(paramName, defaultValue, dtype);
|
|
58
|
+
try {
|
|
59
|
+
return [value, setValue, false];
|
|
60
|
+
} catch (error) {
|
|
61
|
+
return [value, setValue, error.message];
|
|
62
|
+
}
|
|
63
|
+
}
|