@vitessce/vit-s 3.1.2 → 3.2.0
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/dist/index.js +1673 -130
- package/dist-tsc/data-hook-utils.d.ts +1 -0
- package/dist-tsc/data-hook-utils.d.ts.map +1 -1
- package/dist-tsc/data-hook-utils.js +12 -4
- package/dist-tsc/data-hooks-multilevel-utils.d.ts +77 -0
- package/dist-tsc/data-hooks-multilevel-utils.d.ts.map +1 -0
- package/dist-tsc/data-hooks-multilevel-utils.js +418 -0
- package/dist-tsc/data-hooks-multilevel-utils.test.d.ts +2 -0
- package/dist-tsc/data-hooks-multilevel-utils.test.d.ts.map +1 -0
- package/dist-tsc/data-hooks-multilevel-utils.test.js +101 -0
- package/dist-tsc/data-hooks-multilevel.d.ts +8 -0
- package/dist-tsc/data-hooks-multilevel.d.ts.map +1 -0
- package/dist-tsc/data-hooks-multilevel.js +125 -0
- package/dist-tsc/data-hooks.d.ts +7 -0
- package/dist-tsc/data-hooks.d.ts.map +1 -1
- package/dist-tsc/data-hooks.js +75 -6
- package/dist-tsc/hooks.js +1 -1
- package/dist-tsc/index.d.ts +3 -2
- package/dist-tsc/index.js +3 -2
- package/dist-tsc/shared-mui/components.d.ts.map +1 -1
- package/dist-tsc/shared-mui/components.js +3 -2
- package/dist-tsc/state/hooks.d.ts +80 -21
- package/dist-tsc/state/hooks.d.ts.map +1 -1
- package/dist-tsc/state/hooks.js +428 -103
- package/dist-tsc/state/hooks.test.d.ts +2 -0
- package/dist-tsc/state/hooks.test.d.ts.map +1 -0
- package/dist-tsc/state/hooks.test.js +149 -0
- package/dist-tsc/state/spatial-reducers.d.ts +25 -0
- package/dist-tsc/state/spatial-reducers.d.ts.map +1 -0
- package/dist-tsc/state/spatial-reducers.js +251 -0
- package/dist-tsc/state/spatial-reducers.test.d.ts +2 -0
- package/dist-tsc/state/spatial-reducers.test.d.ts.map +1 -0
- package/dist-tsc/state/spatial-reducers.test.js +1382 -0
- package/package.json +5 -5
- package/src/data-hook-utils.js +19 -3
- package/src/data-hooks-multilevel-utils.js +571 -0
- package/src/data-hooks-multilevel-utils.test.js +110 -0
- package/src/data-hooks-multilevel.js +226 -0
- package/src/data-hooks.js +156 -6
- package/src/hooks.js +1 -1
- package/src/index.js +25 -0
- package/src/shared-mui/components.js +9 -4
- package/src/state/hooks.js +485 -104
- package/src/state/hooks.test.js +182 -0
- package/src/state/spatial-reducers.js +291 -0
- package/src/state/spatial-reducers.test.js +1432 -0
package/src/state/hooks.js
CHANGED
|
@@ -1,11 +1,16 @@
|
|
|
1
|
+
/* eslint-disable max-len */
|
|
1
2
|
/* eslint-disable react-refresh/only-export-components */
|
|
2
3
|
import { useRef, useCallback, useMemo } from 'react';
|
|
3
4
|
import create from 'zustand';
|
|
4
5
|
import createContext from 'zustand/context';
|
|
5
6
|
import shallow from 'zustand/shallow';
|
|
6
|
-
import { isMatch, cloneDeep } from 'lodash-es';
|
|
7
|
+
import { isMatch, merge, cloneDeep } from 'lodash-es';
|
|
7
8
|
import { CoordinationType } from '@vitessce/constants-internal';
|
|
8
9
|
import { fromEntries, capitalize } from '@vitessce/utils';
|
|
10
|
+
import {
|
|
11
|
+
removeImageChannelInMetaCoordinationScopesHelper,
|
|
12
|
+
addImageChannelInMetaCoordinationScopesHelper,
|
|
13
|
+
} from './spatial-reducers.js';
|
|
9
14
|
|
|
10
15
|
// Reference: https://github.com/pmndrs/zustand#react-context
|
|
11
16
|
// Reference: https://github.com/pmndrs/zustand/blob/e47ea03/tests/context.test.tsx#L60
|
|
@@ -27,6 +32,92 @@ const {
|
|
|
27
32
|
export const AuxiliaryProvider = AuxiliaryProviderLocal;
|
|
28
33
|
export const useAuxiliaryStore = useAuxiliaryStoreLocal;
|
|
29
34
|
|
|
35
|
+
/**
|
|
36
|
+
* Get the "computed" coordinationScopes after accounting for
|
|
37
|
+
* meta-coordination.
|
|
38
|
+
* @param {*} coordinationScopes The coordinationScopes for a view.
|
|
39
|
+
* @param {*} coordinationSpace The coordinationSpace for a config.
|
|
40
|
+
* @returns {string|undefined} The coordinationScopesBy after meta-coordination.
|
|
41
|
+
*/
|
|
42
|
+
export function getScopes(coordinationScopes, metaSpace) {
|
|
43
|
+
let result = { ...coordinationScopes };
|
|
44
|
+
// Check if there is a matching meta-scope.
|
|
45
|
+
if (metaSpace) {
|
|
46
|
+
// Determine if there is a meta-scope that would take precedence.
|
|
47
|
+
const metaScopes = coordinationScopes[CoordinationType.META_COORDINATION_SCOPES];
|
|
48
|
+
if (metaScopes && metaSpace) {
|
|
49
|
+
// The view.coordinationScopes.metaCoordinationScopes might be an array or a string.
|
|
50
|
+
// Convert to an array.
|
|
51
|
+
const metaScopesArr = Array.isArray(metaScopes) ? metaScopes : [metaScopes];
|
|
52
|
+
metaScopesArr.forEach((metaScope) => {
|
|
53
|
+
// Merge the original coordinationScopes with the matching meta-coordinationScopes
|
|
54
|
+
// from the coordinationSpace.
|
|
55
|
+
result = merge(result, metaSpace[metaScope]);
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return result;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Get the "computed" coordinationScopesBy after accounting for
|
|
64
|
+
* meta-coordination.
|
|
65
|
+
* @param {*} coordinationScopes The coordinationScopes for a view.
|
|
66
|
+
* @param {*} coordinationScopesBy The coordinationScopesBy for a view.
|
|
67
|
+
* @param {*} coordinationSpace The coordinationSpace for a config.
|
|
68
|
+
* @returns {string|undefined} The coordinationScopesBy after meta-coordination.
|
|
69
|
+
*/
|
|
70
|
+
export function getScopesBy(coordinationScopes, coordinationScopesBy, metaSpaceBy) {
|
|
71
|
+
let result = { ...coordinationScopesBy };
|
|
72
|
+
// Check if there is a matching meta-scope.
|
|
73
|
+
if (metaSpaceBy) {
|
|
74
|
+
// Determine if there is a meta-scope that would take precedence.
|
|
75
|
+
const metaScopesBy = coordinationScopes[CoordinationType.META_COORDINATION_SCOPES_BY];
|
|
76
|
+
if (metaSpaceBy && metaScopesBy) {
|
|
77
|
+
// The view.coordinationScopes.metaCoordinationScopes might be an array or a string.
|
|
78
|
+
// Convert to an array.
|
|
79
|
+
const metaScopesArr = Array.isArray(metaScopesBy) ? metaScopesBy : [metaScopesBy];
|
|
80
|
+
metaScopesArr.forEach((metaScope) => {
|
|
81
|
+
// Merge the original coordinationScopesBy with the matching meta-coordinationScopesBy
|
|
82
|
+
// from the coordinationSpace.
|
|
83
|
+
result = merge(result, metaSpaceBy[metaScope]);
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return result;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Get the matching parameter scope.
|
|
92
|
+
* @param {string} parameter A coordination type.
|
|
93
|
+
* @param {*} coordinationScopes The coordinationScopes for a view.
|
|
94
|
+
* @returns {string|undefined} The coordination scope that matches.
|
|
95
|
+
*/
|
|
96
|
+
export function getParameterScope(parameter, coordinationScopes) {
|
|
97
|
+
return coordinationScopes[parameter];
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Get the matching parameter scope.
|
|
102
|
+
* @param {string} parameter A coordination type.
|
|
103
|
+
* @param {*} coordinationScopes The coordinationScopes for a view.
|
|
104
|
+
* @param {*} coordinationScopesBy The coordinationScopesBy for a view.
|
|
105
|
+
* @returns {string|undefined} The coordination scope that matches.
|
|
106
|
+
*/
|
|
107
|
+
export function getParameterScopeBy(
|
|
108
|
+
parameter, byType, typeScope, coordinationScopes, coordinationScopesBy,
|
|
109
|
+
) {
|
|
110
|
+
const parameterScopeGlobal = coordinationScopes[parameter];
|
|
111
|
+
// Set parameterScope to the non-meta-value first.
|
|
112
|
+
const parameterScopeByType = coordinationScopesBy?.[byType]?.[parameter];
|
|
113
|
+
|
|
114
|
+
if (parameterScopeByType && parameterScopeByType[typeScope]) {
|
|
115
|
+
return parameterScopeByType[typeScope];
|
|
116
|
+
}
|
|
117
|
+
// console.error(`coordination scope for ${parameter} was not found.`);
|
|
118
|
+
return parameterScopeGlobal;
|
|
119
|
+
}
|
|
120
|
+
|
|
30
121
|
|
|
31
122
|
/**
|
|
32
123
|
* The useViewConfigStore hook is initialized via the zustand
|
|
@@ -52,18 +143,63 @@ export const createViewConfigStore = (initialLoaders, initialConfig) => create(s
|
|
|
52
143
|
// (although technically also part of state):
|
|
53
144
|
setViewConfig: viewConfig => set({ viewConfig, initialViewConfig: viewConfig }),
|
|
54
145
|
setLoaders: loaders => set({ loaders }),
|
|
55
|
-
setCoordinationValue: ({
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
146
|
+
setCoordinationValue: ({
|
|
147
|
+
parameter, value, coordinationScopes,
|
|
148
|
+
byType, typeScope, coordinationScopesBy,
|
|
149
|
+
}) => set((state) => {
|
|
150
|
+
const { coordinationSpace } = state.viewConfig;
|
|
151
|
+
let scope;
|
|
152
|
+
if (!byType) {
|
|
153
|
+
scope = getParameterScope(parameter, coordinationScopes);
|
|
154
|
+
} else {
|
|
155
|
+
scope = getParameterScopeBy(
|
|
156
|
+
parameter, byType, typeScope, coordinationScopes, coordinationScopesBy,
|
|
157
|
+
);
|
|
158
|
+
if (!scope) {
|
|
159
|
+
// Fall back to using the view-level scope.
|
|
160
|
+
scope = getParameterScope(parameter, coordinationScopes);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return {
|
|
164
|
+
viewConfig: {
|
|
165
|
+
...state.viewConfig,
|
|
166
|
+
coordinationSpace: {
|
|
167
|
+
...coordinationSpace,
|
|
168
|
+
[parameter]: {
|
|
169
|
+
...coordinationSpace[parameter],
|
|
170
|
+
[scope]: value,
|
|
171
|
+
},
|
|
63
172
|
},
|
|
64
173
|
},
|
|
65
|
-
}
|
|
66
|
-
})
|
|
174
|
+
};
|
|
175
|
+
}),
|
|
176
|
+
removeImageChannelInMetaCoordinationScopes: (coordinationScopesRaw, layerScope, channelScope) => set((state) => {
|
|
177
|
+
const { coordinationSpace } = state.viewConfig;
|
|
178
|
+
return {
|
|
179
|
+
viewConfig: {
|
|
180
|
+
...state.viewConfig,
|
|
181
|
+
coordinationSpace: removeImageChannelInMetaCoordinationScopesHelper(
|
|
182
|
+
coordinationScopesRaw,
|
|
183
|
+
layerScope,
|
|
184
|
+
channelScope,
|
|
185
|
+
coordinationSpace,
|
|
186
|
+
),
|
|
187
|
+
},
|
|
188
|
+
};
|
|
189
|
+
}),
|
|
190
|
+
addImageChannelInMetaCoordinationScopes: (coordinationScopesRaw, layerScope) => set((state) => {
|
|
191
|
+
const { coordinationSpace } = state.viewConfig;
|
|
192
|
+
return {
|
|
193
|
+
viewConfig: {
|
|
194
|
+
...state.viewConfig,
|
|
195
|
+
coordinationSpace: addImageChannelInMetaCoordinationScopesHelper(
|
|
196
|
+
coordinationScopesRaw,
|
|
197
|
+
layerScope,
|
|
198
|
+
coordinationSpace,
|
|
199
|
+
),
|
|
200
|
+
},
|
|
201
|
+
};
|
|
202
|
+
}),
|
|
67
203
|
removeComponent: uid => set((state) => {
|
|
68
204
|
const newLayout = state.viewConfig.layout.filter(c => c.uid !== uid);
|
|
69
205
|
return {
|
|
@@ -229,9 +365,12 @@ export function useCoordination(parameters, coordinationScopes) {
|
|
|
229
365
|
const values = useViewConfigStore((state) => {
|
|
230
366
|
const { coordinationSpace } = state.viewConfig;
|
|
231
367
|
return fromEntries(parameters.map((parameter) => {
|
|
232
|
-
if (coordinationSpace
|
|
233
|
-
const
|
|
234
|
-
|
|
368
|
+
if (coordinationSpace) {
|
|
369
|
+
const parameterScope = getParameterScope(parameter, coordinationScopes);
|
|
370
|
+
if (coordinationSpace && coordinationSpace[parameter]) {
|
|
371
|
+
const value = coordinationSpace[parameter][parameterScope];
|
|
372
|
+
return [parameter, value];
|
|
373
|
+
}
|
|
235
374
|
}
|
|
236
375
|
return [parameter, undefined];
|
|
237
376
|
}));
|
|
@@ -241,7 +380,7 @@ export function useCoordination(parameters, coordinationScopes) {
|
|
|
241
380
|
const setterName = `set${capitalize(parameter)}`;
|
|
242
381
|
const setterFunc = value => setCoordinationValue({
|
|
243
382
|
parameter,
|
|
244
|
-
|
|
383
|
+
coordinationScopes,
|
|
245
384
|
value,
|
|
246
385
|
});
|
|
247
386
|
return [setterName, setterFunc];
|
|
@@ -251,8 +390,136 @@ export function useCoordination(parameters, coordinationScopes) {
|
|
|
251
390
|
return [values, setters];
|
|
252
391
|
}
|
|
253
392
|
|
|
393
|
+
export function useMultiCoordinationScopes(parameter, coordinationScopes) {
|
|
394
|
+
return useMemo(() => {
|
|
395
|
+
const scopes = coordinationScopes[parameter];
|
|
396
|
+
return Array.isArray(scopes) ? scopes : [scopes];
|
|
397
|
+
}, [parameter, coordinationScopes]);
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
export function useMultiCoordinationScopesNonNull(parameter, coordinationScopes) {
|
|
401
|
+
const scopes = getParameterScope(parameter, coordinationScopes);
|
|
402
|
+
|
|
403
|
+
// Return array of coordination scopes,
|
|
404
|
+
// but filter out any whose value is null / falsey.
|
|
405
|
+
const parameterSpace = useViewConfigStore((state) => {
|
|
406
|
+
const { coordinationSpace } = state.viewConfig;
|
|
407
|
+
return coordinationSpace?.[parameter];
|
|
408
|
+
}, shallow);
|
|
409
|
+
const nonNullScopes = useMemo(() => {
|
|
410
|
+
// Convert a single scope to an array of scopes to be consistent.
|
|
411
|
+
const scopesArr = Array.isArray(scopes) ? scopes : [scopes];
|
|
412
|
+
return scopesArr.filter((scope) => {
|
|
413
|
+
if (parameterSpace) {
|
|
414
|
+
const value = parameterSpace[scope];
|
|
415
|
+
return value !== null;
|
|
416
|
+
}
|
|
417
|
+
return false;
|
|
418
|
+
});
|
|
419
|
+
}, [parameter, coordinationScopes, scopes, parameterSpace]);
|
|
420
|
+
return nonNullScopes;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
export function useMultiCoordinationScopesSecondary(
|
|
424
|
+
parameter, byType, coordinationScopes, coordinationScopesBy,
|
|
425
|
+
) {
|
|
426
|
+
return useMemo(() => {
|
|
427
|
+
const scopes = getParameterScope(byType, coordinationScopes);
|
|
428
|
+
if (scopes && coordinationScopesBy?.[byType]?.[parameter]) {
|
|
429
|
+
const scopesArr = Array.isArray(scopes) ? scopes : [scopes];
|
|
430
|
+
return [scopesArr, fromEntries(scopesArr.map((scope) => {
|
|
431
|
+
const secondaryScopes = coordinationScopesBy[byType][parameter][scope];
|
|
432
|
+
const secondaryScopesArr = Array.isArray(secondaryScopes)
|
|
433
|
+
? secondaryScopes
|
|
434
|
+
: [secondaryScopes];
|
|
435
|
+
return [scope, secondaryScopesArr];
|
|
436
|
+
}))];
|
|
437
|
+
}
|
|
438
|
+
// Fallback from fine-grained to coarse-grained.
|
|
439
|
+
if (scopes && !coordinationScopesBy?.[byType]?.[parameter] && coordinationScopes?.[parameter]) {
|
|
440
|
+
const scopesArr = Array.isArray(scopes) ? scopes : [scopes];
|
|
441
|
+
return [scopesArr, fromEntries(scopesArr.map((scope) => {
|
|
442
|
+
const secondaryScopes = coordinationScopes?.[parameter];
|
|
443
|
+
const secondaryScopesArr = Array.isArray(secondaryScopes)
|
|
444
|
+
? secondaryScopes
|
|
445
|
+
: [secondaryScopes];
|
|
446
|
+
return [scope, secondaryScopesArr];
|
|
447
|
+
}))];
|
|
448
|
+
}
|
|
449
|
+
return [[], {}];
|
|
450
|
+
}, [parameter, byType, coordinationScopes, coordinationScopesBy]);
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
export function useMultiCoordinationScopesSecondaryNonNull(
|
|
454
|
+
parameter, byType, coordinationScopes, coordinationScopesBy,
|
|
455
|
+
) {
|
|
456
|
+
const scopes = getParameterScope(byType, coordinationScopes);
|
|
457
|
+
|
|
458
|
+
const parameterSpace = useViewConfigStore((state) => {
|
|
459
|
+
const { coordinationSpace } = state.viewConfig;
|
|
460
|
+
return coordinationSpace?.[parameter];
|
|
461
|
+
}, shallow);
|
|
462
|
+
const byTypeSpace = useViewConfigStore((state) => {
|
|
463
|
+
const { coordinationSpace } = state.viewConfig;
|
|
464
|
+
return coordinationSpace?.[byType];
|
|
465
|
+
}, shallow);
|
|
466
|
+
|
|
467
|
+
return useMemo(() => {
|
|
468
|
+
if (scopes && coordinationScopesBy?.[byType]?.[parameter]) {
|
|
469
|
+
const scopesArr = Array.isArray(scopes) ? scopes : [scopes];
|
|
470
|
+
const scopesNonNull = scopesArr.filter((scope) => {
|
|
471
|
+
if (byTypeSpace) {
|
|
472
|
+
const value = byTypeSpace[scope];
|
|
473
|
+
return value !== null;
|
|
474
|
+
}
|
|
475
|
+
return false;
|
|
476
|
+
});
|
|
477
|
+
return [scopesNonNull, fromEntries(scopesNonNull.map((scope) => {
|
|
478
|
+
const secondaryScopes = coordinationScopesBy[byType][parameter][scope];
|
|
479
|
+
const secondaryScopesArr = Array.isArray(secondaryScopes)
|
|
480
|
+
? secondaryScopes
|
|
481
|
+
: [secondaryScopes];
|
|
482
|
+
const secondaryScopesNonNull = secondaryScopesArr.filter((innerScope) => {
|
|
483
|
+
if (parameterSpace) {
|
|
484
|
+
const value = parameterSpace[innerScope];
|
|
485
|
+
return value !== null;
|
|
486
|
+
}
|
|
487
|
+
return false;
|
|
488
|
+
});
|
|
489
|
+
return [scope, secondaryScopesNonNull];
|
|
490
|
+
}))];
|
|
491
|
+
}
|
|
492
|
+
// Fallback from fine-grained to coarse-grained.
|
|
493
|
+
if (scopes && !coordinationScopesBy?.[byType]?.[parameter] && coordinationScopes?.[parameter]) {
|
|
494
|
+
const scopesArr = Array.isArray(scopes) ? scopes : [scopes];
|
|
495
|
+
const scopesNonNull = scopesArr.filter((scope) => {
|
|
496
|
+
if (byTypeSpace) {
|
|
497
|
+
const value = byTypeSpace[scope];
|
|
498
|
+
return value !== null;
|
|
499
|
+
}
|
|
500
|
+
return false;
|
|
501
|
+
});
|
|
502
|
+
return [scopesNonNull, fromEntries(scopesNonNull.map((scope) => {
|
|
503
|
+
const secondaryScopes = coordinationScopes?.[parameter];
|
|
504
|
+
const secondaryScopesArr = Array.isArray(secondaryScopes)
|
|
505
|
+
? secondaryScopes
|
|
506
|
+
: [secondaryScopes];
|
|
507
|
+
const secondaryScopesNonNull = secondaryScopesArr.filter((innerScope) => {
|
|
508
|
+
if (parameterSpace) {
|
|
509
|
+
const value = parameterSpace[innerScope];
|
|
510
|
+
return value !== null;
|
|
511
|
+
}
|
|
512
|
+
return false;
|
|
513
|
+
});
|
|
514
|
+
return [scope, secondaryScopesNonNull];
|
|
515
|
+
}))];
|
|
516
|
+
}
|
|
517
|
+
return [[], {}];
|
|
518
|
+
}, [parameter, byType, scopes, coordinationScopes, coordinationScopesBy, parameterSpace, byTypeSpace]);
|
|
519
|
+
}
|
|
520
|
+
|
|
254
521
|
export function useMultiCoordinationValues(parameter, coordinationScopes) {
|
|
255
|
-
const scopes = coordinationScopes
|
|
522
|
+
const scopes = getParameterScope(parameter, coordinationScopes);
|
|
256
523
|
|
|
257
524
|
// Mapping from dataset coordination scope name to dataset uid
|
|
258
525
|
const vals = useViewConfigStore((state) => {
|
|
@@ -300,66 +567,198 @@ export function useComplexCoordination(
|
|
|
300
567
|
) {
|
|
301
568
|
const setCoordinationValue = useViewConfigStore(state => state.setCoordinationValue);
|
|
302
569
|
|
|
303
|
-
const
|
|
304
|
-
const byTypeScopes = coordinationScopesBy[byType];
|
|
305
|
-
|
|
306
|
-
// Convert a single scope to an array of scopes to be consistent.
|
|
307
|
-
const typeScopesArr = Array.isArray(typeScopes) ? typeScopes : [typeScopes];
|
|
308
|
-
|
|
309
|
-
const values = useViewConfigStore((state) => {
|
|
570
|
+
const parameterSpaces = useViewConfigStore((state) => {
|
|
310
571
|
const { coordinationSpace } = state.viewConfig;
|
|
311
|
-
return
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
572
|
+
return parameters.map(parameter => coordinationSpace[parameter]);
|
|
573
|
+
}, shallow);
|
|
574
|
+
|
|
575
|
+
const values = useMemo(() => {
|
|
576
|
+
const typeScopes = getParameterScope(byType, coordinationScopes);
|
|
577
|
+
if (typeScopes) {
|
|
578
|
+
// Convert a single scope to an array of scopes to be consistent.
|
|
579
|
+
const typeScopesArr = Array.isArray(typeScopes) ? typeScopes : [typeScopes];
|
|
580
|
+
return fromEntries(typeScopesArr.map((datasetScope) => {
|
|
581
|
+
const datasetValues = fromEntries(parameters.map((parameter, i) => {
|
|
582
|
+
if (parameterSpaces[i]) {
|
|
583
|
+
const parameterSpace = parameterSpaces[i];
|
|
584
|
+
const parameterScope = getParameterScopeBy(
|
|
585
|
+
parameter,
|
|
586
|
+
byType,
|
|
587
|
+
datasetScope,
|
|
588
|
+
coordinationScopes,
|
|
589
|
+
coordinationScopesBy,
|
|
590
|
+
);
|
|
591
|
+
if (parameterScope) {
|
|
592
|
+
const value = parameterSpace[parameterScope];
|
|
593
|
+
return [parameter, value];
|
|
594
|
+
}
|
|
595
|
+
// Fall back to global scope for this parameter.
|
|
596
|
+
const globalParameterScope = getParameterScope(parameter, coordinationScopes);
|
|
597
|
+
const globalValue = parameterSpace[globalParameterScope];
|
|
598
|
+
return [parameter, globalValue];
|
|
324
599
|
}
|
|
325
|
-
return [parameter,
|
|
326
|
-
}
|
|
327
|
-
return [
|
|
600
|
+
return [parameter, undefined];
|
|
601
|
+
}));
|
|
602
|
+
return [datasetScope, datasetValues];
|
|
328
603
|
}));
|
|
329
|
-
|
|
330
|
-
}
|
|
604
|
+
}
|
|
605
|
+
return {};
|
|
606
|
+
}, [byType, coordinationScopes, coordinationScopesBy, parameterSpaces]);
|
|
607
|
+
|
|
608
|
+
const setters = useMemo(() => {
|
|
609
|
+
const typeScopes = getParameterScope(byType, coordinationScopes);
|
|
610
|
+
if (typeScopes) {
|
|
611
|
+
// Convert a single scope to an array of scopes to be consistent.
|
|
612
|
+
const typeScopesArr = Array.isArray(typeScopes) ? typeScopes : [typeScopes];
|
|
613
|
+
return fromEntries(typeScopesArr.map((datasetScope) => {
|
|
614
|
+
const datasetSetters = fromEntries(parameters.map((parameter) => {
|
|
615
|
+
const setterName = `set${capitalize(parameter)}`;
|
|
616
|
+
const setterFunc = value => setCoordinationValue({
|
|
617
|
+
parameter,
|
|
618
|
+
byType,
|
|
619
|
+
typeScope: datasetScope,
|
|
620
|
+
coordinationScopes,
|
|
621
|
+
coordinationScopesBy,
|
|
622
|
+
value,
|
|
623
|
+
});
|
|
624
|
+
return [setterName, setterFunc];
|
|
625
|
+
}));
|
|
626
|
+
return [datasetScope, datasetSetters];
|
|
627
|
+
}));
|
|
628
|
+
}
|
|
629
|
+
return {};
|
|
630
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
631
|
+
// parameters is assumed to be a constant array.
|
|
632
|
+
}, [coordinationScopes]);
|
|
633
|
+
|
|
634
|
+
return [values, setters];
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
/**
|
|
638
|
+
* Get the "computed" (i.e., after accounting for meta-coordination)
|
|
639
|
+
* value for coordinationScopes.
|
|
640
|
+
* @param {object} coordinationScopes The original coordinationScopes passed to the view.
|
|
641
|
+
* @returns {object} The coordinationScopes after filling in with meta-coordinationScopes.
|
|
642
|
+
*/
|
|
643
|
+
export function useCoordinationScopes(coordinationScopes) {
|
|
644
|
+
const metaSpace = useViewConfigStore((state) => {
|
|
645
|
+
const { coordinationSpace } = state.viewConfig;
|
|
646
|
+
return coordinationSpace?.[CoordinationType.META_COORDINATION_SCOPES];
|
|
331
647
|
}, shallow);
|
|
648
|
+
const vals = useMemo(() => {
|
|
649
|
+
const scopes = getScopes(
|
|
650
|
+
coordinationScopes,
|
|
651
|
+
metaSpace,
|
|
652
|
+
);
|
|
653
|
+
// Prevent infinite loop, delete metaCoordinationScopes now that they are computed.
|
|
654
|
+
delete scopes[CoordinationType.META_COORDINATION_SCOPES];
|
|
655
|
+
return scopes;
|
|
656
|
+
}, [coordinationScopes, metaSpace]);
|
|
657
|
+
return vals;
|
|
658
|
+
}
|
|
332
659
|
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
660
|
+
/**
|
|
661
|
+
* Get the "computed" (i.e., after accounting for meta-coordination)
|
|
662
|
+
* value for coordinationScopesBy.
|
|
663
|
+
* @param {object} coordinationScopes The original coordinationScopes passed to the view.
|
|
664
|
+
* @param {object} coordinationScopesBy The original coordinationScopesBy passed to the view.
|
|
665
|
+
* @returns {object} The coordinationScopesBy after filling in with meta-coordinationScopesBy.
|
|
666
|
+
*/
|
|
667
|
+
export function useCoordinationScopesBy(coordinationScopes, coordinationScopesBy) {
|
|
668
|
+
const metaSpaceBy = useViewConfigStore((state) => {
|
|
669
|
+
const { coordinationSpace } = state.viewConfig;
|
|
670
|
+
return coordinationSpace?.[CoordinationType.META_COORDINATION_SCOPES_BY];
|
|
671
|
+
}, shallow);
|
|
672
|
+
const vals = useMemo(() => {
|
|
673
|
+
const scopesBy = getScopesBy(
|
|
674
|
+
coordinationScopes,
|
|
675
|
+
coordinationScopesBy,
|
|
676
|
+
metaSpaceBy,
|
|
677
|
+
);
|
|
678
|
+
return scopesBy;
|
|
679
|
+
}, [coordinationScopes, coordinationScopesBy, metaSpaceBy]);
|
|
680
|
+
return vals;
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
/**
|
|
684
|
+
* Use a second level of complex coordination.
|
|
685
|
+
* @param {string[]} parameters Array of coordination types.
|
|
686
|
+
* @param {object} coordinationScopesBy The coordinationScopesBy object from the view definition.
|
|
687
|
+
* @param {string} primaryType The first-level coordination type, such as spatialImageLayer.
|
|
688
|
+
* @param {string} secondaryType The second-level coordination type, such as spatialImageChannel.
|
|
689
|
+
* @returns The results of useComplexCoordination.
|
|
690
|
+
*/
|
|
691
|
+
export function useComplexCoordinationSecondary(
|
|
692
|
+
parameters, coordinationScopes, coordinationScopesBy, primaryType, secondaryType,
|
|
693
|
+
) {
|
|
694
|
+
const coordinationScopesFake = useMemo(() => {
|
|
695
|
+
if (coordinationScopesBy?.[primaryType]?.[secondaryType]) {
|
|
696
|
+
return {
|
|
697
|
+
...coordinationScopes, // TODO: this is needed for falling back to view-level coordination scopes, but does it affect performance?
|
|
698
|
+
[secondaryType]: Object.values(coordinationScopesBy[primaryType][secondaryType]).flat(),
|
|
699
|
+
};
|
|
700
|
+
}
|
|
701
|
+
// First fallback: try coarser (_Channel -> _Layer -> view)
|
|
702
|
+
// coordination values when finer ones are null/undefined.
|
|
703
|
+
if (coordinationScopes?.[secondaryType] && Array.isArray(coordinationScopes[secondaryType])) {
|
|
704
|
+
return {
|
|
705
|
+
...coordinationScopes, // TODO: this is needed for falling back to view-level coordination scopes, but does it affect performance?
|
|
706
|
+
[secondaryType]: coordinationScopes[secondaryType],
|
|
707
|
+
};
|
|
708
|
+
}
|
|
709
|
+
// Finally, fall back to empty array.
|
|
710
|
+
return { [secondaryType]: [] };
|
|
711
|
+
}, [coordinationScopesBy, primaryType, secondaryType]);
|
|
712
|
+
const [flatValues, flatSetters] = useComplexCoordination(
|
|
713
|
+
parameters, coordinationScopesFake, coordinationScopesBy, secondaryType,
|
|
714
|
+
);
|
|
715
|
+
const nestedValues = useMemo(() => {
|
|
716
|
+
// Re-nest
|
|
717
|
+
const result = {};
|
|
718
|
+
if (coordinationScopesBy?.[primaryType]?.[secondaryType]) {
|
|
719
|
+
Object.entries(coordinationScopesBy[primaryType][secondaryType])
|
|
720
|
+
.forEach(([layerScope, channelScopes]) => {
|
|
721
|
+
result[layerScope] = {};
|
|
722
|
+
channelScopes.forEach((channelScope) => {
|
|
723
|
+
result[layerScope][channelScope] = flatValues[channelScope];
|
|
724
|
+
});
|
|
344
725
|
});
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
726
|
+
} else if (coordinationScopes?.[secondaryType] && Array.isArray(coordinationScopes[secondaryType])) {
|
|
727
|
+
// Re-nesting for fallback case.
|
|
728
|
+
const layerScopes = coordinationScopes[primaryType];
|
|
729
|
+
layerScopes.forEach((layerScope) => {
|
|
730
|
+
// eslint-disable-next-line prefer-destructuring
|
|
731
|
+
result[layerScope] = flatValues;
|
|
732
|
+
});
|
|
733
|
+
}
|
|
734
|
+
return result;
|
|
735
|
+
}, [flatValues]);
|
|
736
|
+
const nestedSetters = useMemo(() => {
|
|
737
|
+
// Re-nest
|
|
738
|
+
const result = {};
|
|
739
|
+
if (coordinationScopesBy?.[primaryType]?.[secondaryType]) {
|
|
740
|
+
Object.entries(coordinationScopesBy[primaryType][secondaryType])
|
|
741
|
+
.forEach(([layerScope, channelScopes]) => {
|
|
742
|
+
result[layerScope] = {};
|
|
743
|
+
channelScopes.forEach((channelScope) => {
|
|
744
|
+
result[layerScope][channelScope] = flatSetters[channelScope];
|
|
745
|
+
});
|
|
350
746
|
});
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
747
|
+
} else if (coordinationScopes?.[secondaryType] && Array.isArray(coordinationScopes[secondaryType])) {
|
|
748
|
+
// Re-nesting for fallback case.
|
|
749
|
+
const layerScopes = coordinationScopes[primaryType];
|
|
750
|
+
layerScopes.forEach((layerScope) => {
|
|
751
|
+
// eslint-disable-next-line prefer-destructuring
|
|
752
|
+
result[layerScope] = flatSetters;
|
|
753
|
+
});
|
|
754
|
+
}
|
|
755
|
+
return result;
|
|
756
|
+
}, [flatSetters]);
|
|
359
757
|
|
|
360
|
-
return [
|
|
758
|
+
return [nestedValues, nestedSetters];
|
|
361
759
|
}
|
|
362
760
|
|
|
761
|
+
|
|
363
762
|
/**
|
|
364
763
|
* Use coordination values and coordination setter functions corresponding to
|
|
365
764
|
* dataset-specific coordination scopes for each coordination type.
|
|
@@ -510,44 +909,6 @@ export function useMatchingLoader(loaders, dataset, dataType, viewCoordinationVa
|
|
|
510
909
|
), [loaders, dataset, dataType, viewCoordinationValues]);
|
|
511
910
|
}
|
|
512
911
|
|
|
513
|
-
/**
|
|
514
|
-
* Find a specific loader instance for a particular dataset, data type, and view
|
|
515
|
-
* coordination values (mapping from coordination types to coordination values).
|
|
516
|
-
* Uses lodash/isMatch to perform matching against the file definition's
|
|
517
|
-
* coordination value mapping.
|
|
518
|
-
* TODO: can this function be removed?
|
|
519
|
-
* @param {object} loaders The value returned by useLoaders.
|
|
520
|
-
* @param {string} dataset The dataset UID.
|
|
521
|
-
* @param {string} dataType The data type for the matching file.
|
|
522
|
-
* @param {object} viewCoordinationValues Current coordination values
|
|
523
|
-
* from the view. Match these against a subset of file definition coordination
|
|
524
|
-
* values.
|
|
525
|
-
* @returns The matching loader instance or `null`.
|
|
526
|
-
*/
|
|
527
|
-
export function useMatchingLoaders(loaders, dataset, dataType, viewCoordinationValuesObj) {
|
|
528
|
-
return useMemo(() => {
|
|
529
|
-
if (!loaders[dataset]) {
|
|
530
|
-
return null;
|
|
531
|
-
}
|
|
532
|
-
const loaderInternMap = loaders[dataset].loaders[dataType];
|
|
533
|
-
if (!loaderInternMap) {
|
|
534
|
-
return null;
|
|
535
|
-
}
|
|
536
|
-
const loaderKeys = Array.from(loaderInternMap.keys());
|
|
537
|
-
function getLoader(viewCoordinationValues) {
|
|
538
|
-
const matchingKey = loaderKeys
|
|
539
|
-
.find(fileCoordinationValues => isMatch(fileCoordinationValues, viewCoordinationValues));
|
|
540
|
-
return loaderInternMap.get(matchingKey);
|
|
541
|
-
}
|
|
542
|
-
return fromEntries(
|
|
543
|
-
Object.entries(viewCoordinationValuesObj).map(([key, viewCoordinationValues]) => ([
|
|
544
|
-
key,
|
|
545
|
-
getLoader(viewCoordinationValues),
|
|
546
|
-
])),
|
|
547
|
-
);
|
|
548
|
-
}, [loaders, dataset, dataType, viewCoordinationValuesObj]);
|
|
549
|
-
}
|
|
550
|
-
|
|
551
912
|
/**
|
|
552
913
|
* Obtain the view config layout array from
|
|
553
914
|
* the global app state.
|
|
@@ -568,6 +929,26 @@ export function useRemoveComponent() {
|
|
|
568
929
|
return useViewConfigStore(state => state.removeComponent);
|
|
569
930
|
}
|
|
570
931
|
|
|
932
|
+
/**
|
|
933
|
+
* Obtain the component removal function from
|
|
934
|
+
* the global app state.
|
|
935
|
+
* @returns {function} The remove component function
|
|
936
|
+
* in the `useViewInfoStore` store.
|
|
937
|
+
*/
|
|
938
|
+
export function useRemoveImageChannelInMetaCoordinationScopes() {
|
|
939
|
+
return useViewConfigStore(state => state.removeImageChannelInMetaCoordinationScopes);
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
/**
|
|
943
|
+
* Obtain the component removal function from
|
|
944
|
+
* the global app state.
|
|
945
|
+
* @returns {function} The remove component function
|
|
946
|
+
* in the `useViewInfoStore` store.
|
|
947
|
+
*/
|
|
948
|
+
export function useAddImageChannelInMetaCoordinationScopes() {
|
|
949
|
+
return useViewConfigStore(state => state.addImageChannelInMetaCoordinationScopes);
|
|
950
|
+
}
|
|
951
|
+
|
|
571
952
|
/**
|
|
572
953
|
* Obtain the component prop setter function from
|
|
573
954
|
* the global app state.
|