@vitessce/vit-s 3.1.3 → 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.
Files changed (46) hide show
  1. package/dist/index.js +1673 -130
  2. package/dist-tsc/data-hook-utils.d.ts +1 -0
  3. package/dist-tsc/data-hook-utils.d.ts.map +1 -1
  4. package/dist-tsc/data-hook-utils.js +12 -4
  5. package/dist-tsc/data-hooks-multilevel-utils.d.ts +77 -0
  6. package/dist-tsc/data-hooks-multilevel-utils.d.ts.map +1 -0
  7. package/dist-tsc/data-hooks-multilevel-utils.js +418 -0
  8. package/dist-tsc/data-hooks-multilevel-utils.test.d.ts +2 -0
  9. package/dist-tsc/data-hooks-multilevel-utils.test.d.ts.map +1 -0
  10. package/dist-tsc/data-hooks-multilevel-utils.test.js +101 -0
  11. package/dist-tsc/data-hooks-multilevel.d.ts +8 -0
  12. package/dist-tsc/data-hooks-multilevel.d.ts.map +1 -0
  13. package/dist-tsc/data-hooks-multilevel.js +125 -0
  14. package/dist-tsc/data-hooks.d.ts +7 -0
  15. package/dist-tsc/data-hooks.d.ts.map +1 -1
  16. package/dist-tsc/data-hooks.js +75 -6
  17. package/dist-tsc/hooks.js +1 -1
  18. package/dist-tsc/index.d.ts +3 -2
  19. package/dist-tsc/index.js +3 -2
  20. package/dist-tsc/shared-mui/components.d.ts.map +1 -1
  21. package/dist-tsc/shared-mui/components.js +3 -2
  22. package/dist-tsc/state/hooks.d.ts +80 -21
  23. package/dist-tsc/state/hooks.d.ts.map +1 -1
  24. package/dist-tsc/state/hooks.js +428 -103
  25. package/dist-tsc/state/hooks.test.d.ts +2 -0
  26. package/dist-tsc/state/hooks.test.d.ts.map +1 -0
  27. package/dist-tsc/state/hooks.test.js +149 -0
  28. package/dist-tsc/state/spatial-reducers.d.ts +25 -0
  29. package/dist-tsc/state/spatial-reducers.d.ts.map +1 -0
  30. package/dist-tsc/state/spatial-reducers.js +251 -0
  31. package/dist-tsc/state/spatial-reducers.test.d.ts +2 -0
  32. package/dist-tsc/state/spatial-reducers.test.d.ts.map +1 -0
  33. package/dist-tsc/state/spatial-reducers.test.js +1382 -0
  34. package/package.json +5 -5
  35. package/src/data-hook-utils.js +19 -3
  36. package/src/data-hooks-multilevel-utils.js +571 -0
  37. package/src/data-hooks-multilevel-utils.test.js +110 -0
  38. package/src/data-hooks-multilevel.js +226 -0
  39. package/src/data-hooks.js +156 -6
  40. package/src/hooks.js +1 -1
  41. package/src/index.js +25 -0
  42. package/src/shared-mui/components.js +9 -4
  43. package/src/state/hooks.js +485 -104
  44. package/src/state/hooks.test.js +182 -0
  45. package/src/state/spatial-reducers.js +291 -0
  46. package/src/state/spatial-reducers.test.js +1432 -0
@@ -1,11 +1,13 @@
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 { removeImageChannelInMetaCoordinationScopesHelper, addImageChannelInMetaCoordinationScopesHelper, } from './spatial-reducers.js';
9
11
  // Reference: https://github.com/pmndrs/zustand#react-context
10
12
  // Reference: https://github.com/pmndrs/zustand/blob/e47ea03/tests/context.test.tsx#L60
11
13
  const { Provider: ViewConfigProviderLocal, useStore: useViewConfigStoreLocal, useStoreApi: useViewConfigStoreApiLocal, } = createContext();
@@ -15,6 +17,85 @@ export const useViewConfigStoreApi = useViewConfigStoreApiLocal;
15
17
  const { Provider: AuxiliaryProviderLocal, useStore: useAuxiliaryStoreLocal, } = createContext();
16
18
  export const AuxiliaryProvider = AuxiliaryProviderLocal;
17
19
  export const useAuxiliaryStore = useAuxiliaryStoreLocal;
20
+ /**
21
+ * Get the "computed" coordinationScopes after accounting for
22
+ * meta-coordination.
23
+ * @param {*} coordinationScopes The coordinationScopes for a view.
24
+ * @param {*} coordinationSpace The coordinationSpace for a config.
25
+ * @returns {string|undefined} The coordinationScopesBy after meta-coordination.
26
+ */
27
+ export function getScopes(coordinationScopes, metaSpace) {
28
+ let result = { ...coordinationScopes };
29
+ // Check if there is a matching meta-scope.
30
+ if (metaSpace) {
31
+ // Determine if there is a meta-scope that would take precedence.
32
+ const metaScopes = coordinationScopes[CoordinationType.META_COORDINATION_SCOPES];
33
+ if (metaScopes && metaSpace) {
34
+ // The view.coordinationScopes.metaCoordinationScopes might be an array or a string.
35
+ // Convert to an array.
36
+ const metaScopesArr = Array.isArray(metaScopes) ? metaScopes : [metaScopes];
37
+ metaScopesArr.forEach((metaScope) => {
38
+ // Merge the original coordinationScopes with the matching meta-coordinationScopes
39
+ // from the coordinationSpace.
40
+ result = merge(result, metaSpace[metaScope]);
41
+ });
42
+ }
43
+ }
44
+ return result;
45
+ }
46
+ /**
47
+ * Get the "computed" coordinationScopesBy after accounting for
48
+ * meta-coordination.
49
+ * @param {*} coordinationScopes The coordinationScopes for a view.
50
+ * @param {*} coordinationScopesBy The coordinationScopesBy for a view.
51
+ * @param {*} coordinationSpace The coordinationSpace for a config.
52
+ * @returns {string|undefined} The coordinationScopesBy after meta-coordination.
53
+ */
54
+ export function getScopesBy(coordinationScopes, coordinationScopesBy, metaSpaceBy) {
55
+ let result = { ...coordinationScopesBy };
56
+ // Check if there is a matching meta-scope.
57
+ if (metaSpaceBy) {
58
+ // Determine if there is a meta-scope that would take precedence.
59
+ const metaScopesBy = coordinationScopes[CoordinationType.META_COORDINATION_SCOPES_BY];
60
+ if (metaSpaceBy && metaScopesBy) {
61
+ // The view.coordinationScopes.metaCoordinationScopes might be an array or a string.
62
+ // Convert to an array.
63
+ const metaScopesArr = Array.isArray(metaScopesBy) ? metaScopesBy : [metaScopesBy];
64
+ metaScopesArr.forEach((metaScope) => {
65
+ // Merge the original coordinationScopesBy with the matching meta-coordinationScopesBy
66
+ // from the coordinationSpace.
67
+ result = merge(result, metaSpaceBy[metaScope]);
68
+ });
69
+ }
70
+ }
71
+ return result;
72
+ }
73
+ /**
74
+ * Get the matching parameter scope.
75
+ * @param {string} parameter A coordination type.
76
+ * @param {*} coordinationScopes The coordinationScopes for a view.
77
+ * @returns {string|undefined} The coordination scope that matches.
78
+ */
79
+ export function getParameterScope(parameter, coordinationScopes) {
80
+ return coordinationScopes[parameter];
81
+ }
82
+ /**
83
+ * Get the matching parameter scope.
84
+ * @param {string} parameter A coordination type.
85
+ * @param {*} coordinationScopes The coordinationScopes for a view.
86
+ * @param {*} coordinationScopesBy The coordinationScopesBy for a view.
87
+ * @returns {string|undefined} The coordination scope that matches.
88
+ */
89
+ export function getParameterScopeBy(parameter, byType, typeScope, coordinationScopes, coordinationScopesBy) {
90
+ const parameterScopeGlobal = coordinationScopes[parameter];
91
+ // Set parameterScope to the non-meta-value first.
92
+ const parameterScopeByType = coordinationScopesBy?.[byType]?.[parameter];
93
+ if (parameterScopeByType && parameterScopeByType[typeScope]) {
94
+ return parameterScopeByType[typeScope];
95
+ }
96
+ // console.error(`coordination scope for ${parameter} was not found.`);
97
+ return parameterScopeGlobal;
98
+ }
18
99
  /**
19
100
  * The useViewConfigStore hook is initialized via the zustand
20
101
  * create() function, which sets up both the state variables
@@ -39,18 +120,50 @@ export const createViewConfigStore = (initialLoaders, initialConfig) => create(s
39
120
  // (although technically also part of state):
40
121
  setViewConfig: viewConfig => set({ viewConfig, initialViewConfig: viewConfig }),
41
122
  setLoaders: loaders => set({ loaders }),
42
- setCoordinationValue: ({ parameter, scope, value }) => set(state => ({
43
- viewConfig: {
44
- ...state.viewConfig,
45
- coordinationSpace: {
46
- ...state.viewConfig.coordinationSpace,
47
- [parameter]: {
48
- ...state.viewConfig.coordinationSpace[parameter],
49
- [scope]: value,
123
+ setCoordinationValue: ({ parameter, value, coordinationScopes, byType, typeScope, coordinationScopesBy, }) => set((state) => {
124
+ const { coordinationSpace } = state.viewConfig;
125
+ let scope;
126
+ if (!byType) {
127
+ scope = getParameterScope(parameter, coordinationScopes);
128
+ }
129
+ else {
130
+ scope = getParameterScopeBy(parameter, byType, typeScope, coordinationScopes, coordinationScopesBy);
131
+ if (!scope) {
132
+ // Fall back to using the view-level scope.
133
+ scope = getParameterScope(parameter, coordinationScopes);
134
+ }
135
+ }
136
+ return {
137
+ viewConfig: {
138
+ ...state.viewConfig,
139
+ coordinationSpace: {
140
+ ...coordinationSpace,
141
+ [parameter]: {
142
+ ...coordinationSpace[parameter],
143
+ [scope]: value,
144
+ },
50
145
  },
51
146
  },
52
- },
53
- })),
147
+ };
148
+ }),
149
+ removeImageChannelInMetaCoordinationScopes: (coordinationScopesRaw, layerScope, channelScope) => set((state) => {
150
+ const { coordinationSpace } = state.viewConfig;
151
+ return {
152
+ viewConfig: {
153
+ ...state.viewConfig,
154
+ coordinationSpace: removeImageChannelInMetaCoordinationScopesHelper(coordinationScopesRaw, layerScope, channelScope, coordinationSpace),
155
+ },
156
+ };
157
+ }),
158
+ addImageChannelInMetaCoordinationScopes: (coordinationScopesRaw, layerScope) => set((state) => {
159
+ const { coordinationSpace } = state.viewConfig;
160
+ return {
161
+ viewConfig: {
162
+ ...state.viewConfig,
163
+ coordinationSpace: addImageChannelInMetaCoordinationScopesHelper(coordinationScopesRaw, layerScope, coordinationSpace),
164
+ },
165
+ };
166
+ }),
54
167
  removeComponent: uid => set((state) => {
55
168
  const newLayout = state.viewConfig.layout.filter(c => c.uid !== uid);
56
169
  return {
@@ -203,9 +316,12 @@ export function useCoordination(parameters, coordinationScopes) {
203
316
  const values = useViewConfigStore((state) => {
204
317
  const { coordinationSpace } = state.viewConfig;
205
318
  return fromEntries(parameters.map((parameter) => {
206
- if (coordinationSpace && coordinationSpace[parameter]) {
207
- const value = coordinationSpace[parameter][coordinationScopes[parameter]];
208
- return [parameter, value];
319
+ if (coordinationSpace) {
320
+ const parameterScope = getParameterScope(parameter, coordinationScopes);
321
+ if (coordinationSpace && coordinationSpace[parameter]) {
322
+ const value = coordinationSpace[parameter][parameterScope];
323
+ return [parameter, value];
324
+ }
209
325
  }
210
326
  return [parameter, undefined];
211
327
  }));
@@ -214,7 +330,7 @@ export function useCoordination(parameters, coordinationScopes) {
214
330
  const setterName = `set${capitalize(parameter)}`;
215
331
  const setterFunc = value => setCoordinationValue({
216
332
  parameter,
217
- scope: coordinationScopes[parameter],
333
+ coordinationScopes,
218
334
  value,
219
335
  });
220
336
  return [setterName, setterFunc];
@@ -222,8 +338,125 @@ export function useCoordination(parameters, coordinationScopes) {
222
338
  })), [parameters, coordinationScopes]);
223
339
  return [values, setters];
224
340
  }
341
+ export function useMultiCoordinationScopes(parameter, coordinationScopes) {
342
+ return useMemo(() => {
343
+ const scopes = coordinationScopes[parameter];
344
+ return Array.isArray(scopes) ? scopes : [scopes];
345
+ }, [parameter, coordinationScopes]);
346
+ }
347
+ export function useMultiCoordinationScopesNonNull(parameter, coordinationScopes) {
348
+ const scopes = getParameterScope(parameter, coordinationScopes);
349
+ // Return array of coordination scopes,
350
+ // but filter out any whose value is null / falsey.
351
+ const parameterSpace = useViewConfigStore((state) => {
352
+ const { coordinationSpace } = state.viewConfig;
353
+ return coordinationSpace?.[parameter];
354
+ }, shallow);
355
+ const nonNullScopes = useMemo(() => {
356
+ // Convert a single scope to an array of scopes to be consistent.
357
+ const scopesArr = Array.isArray(scopes) ? scopes : [scopes];
358
+ return scopesArr.filter((scope) => {
359
+ if (parameterSpace) {
360
+ const value = parameterSpace[scope];
361
+ return value !== null;
362
+ }
363
+ return false;
364
+ });
365
+ }, [parameter, coordinationScopes, scopes, parameterSpace]);
366
+ return nonNullScopes;
367
+ }
368
+ export function useMultiCoordinationScopesSecondary(parameter, byType, coordinationScopes, coordinationScopesBy) {
369
+ return useMemo(() => {
370
+ const scopes = getParameterScope(byType, coordinationScopes);
371
+ if (scopes && coordinationScopesBy?.[byType]?.[parameter]) {
372
+ const scopesArr = Array.isArray(scopes) ? scopes : [scopes];
373
+ return [scopesArr, fromEntries(scopesArr.map((scope) => {
374
+ const secondaryScopes = coordinationScopesBy[byType][parameter][scope];
375
+ const secondaryScopesArr = Array.isArray(secondaryScopes)
376
+ ? secondaryScopes
377
+ : [secondaryScopes];
378
+ return [scope, secondaryScopesArr];
379
+ }))];
380
+ }
381
+ // Fallback from fine-grained to coarse-grained.
382
+ if (scopes && !coordinationScopesBy?.[byType]?.[parameter] && coordinationScopes?.[parameter]) {
383
+ const scopesArr = Array.isArray(scopes) ? scopes : [scopes];
384
+ return [scopesArr, fromEntries(scopesArr.map((scope) => {
385
+ const secondaryScopes = coordinationScopes?.[parameter];
386
+ const secondaryScopesArr = Array.isArray(secondaryScopes)
387
+ ? secondaryScopes
388
+ : [secondaryScopes];
389
+ return [scope, secondaryScopesArr];
390
+ }))];
391
+ }
392
+ return [[], {}];
393
+ }, [parameter, byType, coordinationScopes, coordinationScopesBy]);
394
+ }
395
+ export function useMultiCoordinationScopesSecondaryNonNull(parameter, byType, coordinationScopes, coordinationScopesBy) {
396
+ const scopes = getParameterScope(byType, coordinationScopes);
397
+ const parameterSpace = useViewConfigStore((state) => {
398
+ const { coordinationSpace } = state.viewConfig;
399
+ return coordinationSpace?.[parameter];
400
+ }, shallow);
401
+ const byTypeSpace = useViewConfigStore((state) => {
402
+ const { coordinationSpace } = state.viewConfig;
403
+ return coordinationSpace?.[byType];
404
+ }, shallow);
405
+ return useMemo(() => {
406
+ if (scopes && coordinationScopesBy?.[byType]?.[parameter]) {
407
+ const scopesArr = Array.isArray(scopes) ? scopes : [scopes];
408
+ const scopesNonNull = scopesArr.filter((scope) => {
409
+ if (byTypeSpace) {
410
+ const value = byTypeSpace[scope];
411
+ return value !== null;
412
+ }
413
+ return false;
414
+ });
415
+ return [scopesNonNull, fromEntries(scopesNonNull.map((scope) => {
416
+ const secondaryScopes = coordinationScopesBy[byType][parameter][scope];
417
+ const secondaryScopesArr = Array.isArray(secondaryScopes)
418
+ ? secondaryScopes
419
+ : [secondaryScopes];
420
+ const secondaryScopesNonNull = secondaryScopesArr.filter((innerScope) => {
421
+ if (parameterSpace) {
422
+ const value = parameterSpace[innerScope];
423
+ return value !== null;
424
+ }
425
+ return false;
426
+ });
427
+ return [scope, secondaryScopesNonNull];
428
+ }))];
429
+ }
430
+ // Fallback from fine-grained to coarse-grained.
431
+ if (scopes && !coordinationScopesBy?.[byType]?.[parameter] && coordinationScopes?.[parameter]) {
432
+ const scopesArr = Array.isArray(scopes) ? scopes : [scopes];
433
+ const scopesNonNull = scopesArr.filter((scope) => {
434
+ if (byTypeSpace) {
435
+ const value = byTypeSpace[scope];
436
+ return value !== null;
437
+ }
438
+ return false;
439
+ });
440
+ return [scopesNonNull, fromEntries(scopesNonNull.map((scope) => {
441
+ const secondaryScopes = coordinationScopes?.[parameter];
442
+ const secondaryScopesArr = Array.isArray(secondaryScopes)
443
+ ? secondaryScopes
444
+ : [secondaryScopes];
445
+ const secondaryScopesNonNull = secondaryScopesArr.filter((innerScope) => {
446
+ if (parameterSpace) {
447
+ const value = parameterSpace[innerScope];
448
+ return value !== null;
449
+ }
450
+ return false;
451
+ });
452
+ return [scope, secondaryScopesNonNull];
453
+ }))];
454
+ }
455
+ return [[], {}];
456
+ }, [parameter, byType, scopes, coordinationScopes, coordinationScopesBy, parameterSpace, byTypeSpace]);
457
+ }
225
458
  export function useMultiCoordinationValues(parameter, coordinationScopes) {
226
- const scopes = coordinationScopes[parameter];
459
+ const scopes = getParameterScope(parameter, coordinationScopes);
227
460
  // Mapping from dataset coordination scope name to dataset uid
228
461
  const vals = useViewConfigStore((state) => {
229
462
  const { coordinationSpace } = state.viewConfig;
@@ -264,64 +497,173 @@ export function useDatasetUids(coordinationScopes) {
264
497
  */
265
498
  export function useComplexCoordination(parameters, coordinationScopes, coordinationScopesBy, byType) {
266
499
  const setCoordinationValue = useViewConfigStore(state => state.setCoordinationValue);
267
- const typeScopes = coordinationScopes[byType];
268
- const byTypeScopes = coordinationScopesBy[byType];
269
- // Convert a single scope to an array of scopes to be consistent.
270
- const typeScopesArr = Array.isArray(typeScopes) ? typeScopes : [typeScopes];
271
- const values = useViewConfigStore((state) => {
500
+ const parameterSpaces = useViewConfigStore((state) => {
272
501
  const { coordinationSpace } = state.viewConfig;
273
- return fromEntries(typeScopesArr.map((datasetScope) => {
274
- const datasetValues = fromEntries(parameters.map((parameter) => {
275
- if (coordinationSpace && coordinationSpace[parameter]) {
276
- let value;
277
- const parameterSpace = coordinationSpace[parameter];
278
- const parameterScopeGlobal = coordinationScopes[parameter];
279
- const parameterScopeByDataset = byTypeScopes?.[parameter];
280
- if (parameterScopeByDataset && parameterScopeByDataset[datasetScope]) {
281
- value = parameterSpace[parameterScopeByDataset[datasetScope]];
282
- }
283
- else if (parameterScopeGlobal) {
284
- value = parameterSpace[parameterScopeGlobal];
285
- }
286
- else {
287
- console.error(`coordination scope for ${parameter} was not found.`);
502
+ return parameters.map(parameter => coordinationSpace[parameter]);
503
+ }, shallow);
504
+ const values = useMemo(() => {
505
+ const typeScopes = getParameterScope(byType, coordinationScopes);
506
+ if (typeScopes) {
507
+ // Convert a single scope to an array of scopes to be consistent.
508
+ const typeScopesArr = Array.isArray(typeScopes) ? typeScopes : [typeScopes];
509
+ return fromEntries(typeScopesArr.map((datasetScope) => {
510
+ const datasetValues = fromEntries(parameters.map((parameter, i) => {
511
+ if (parameterSpaces[i]) {
512
+ const parameterSpace = parameterSpaces[i];
513
+ const parameterScope = getParameterScopeBy(parameter, byType, datasetScope, coordinationScopes, coordinationScopesBy);
514
+ if (parameterScope) {
515
+ const value = parameterSpace[parameterScope];
516
+ return [parameter, value];
517
+ }
518
+ // Fall back to global scope for this parameter.
519
+ const globalParameterScope = getParameterScope(parameter, coordinationScopes);
520
+ const globalValue = parameterSpace[globalParameterScope];
521
+ return [parameter, globalValue];
288
522
  }
289
- return [parameter, value];
290
- }
291
- return [parameter, undefined];
523
+ return [parameter, undefined];
524
+ }));
525
+ return [datasetScope, datasetValues];
292
526
  }));
293
- return [datasetScope, datasetValues];
294
- }));
527
+ }
528
+ return {};
529
+ }, [byType, coordinationScopes, coordinationScopesBy, parameterSpaces]);
530
+ const setters = useMemo(() => {
531
+ const typeScopes = getParameterScope(byType, coordinationScopes);
532
+ if (typeScopes) {
533
+ // Convert a single scope to an array of scopes to be consistent.
534
+ const typeScopesArr = Array.isArray(typeScopes) ? typeScopes : [typeScopes];
535
+ return fromEntries(typeScopesArr.map((datasetScope) => {
536
+ const datasetSetters = fromEntries(parameters.map((parameter) => {
537
+ const setterName = `set${capitalize(parameter)}`;
538
+ const setterFunc = value => setCoordinationValue({
539
+ parameter,
540
+ byType,
541
+ typeScope: datasetScope,
542
+ coordinationScopes,
543
+ coordinationScopesBy,
544
+ value,
545
+ });
546
+ return [setterName, setterFunc];
547
+ }));
548
+ return [datasetScope, datasetSetters];
549
+ }));
550
+ }
551
+ return {};
552
+ // eslint-disable-next-line react-hooks/exhaustive-deps
553
+ // parameters is assumed to be a constant array.
554
+ }, [coordinationScopes]);
555
+ return [values, setters];
556
+ }
557
+ /**
558
+ * Get the "computed" (i.e., after accounting for meta-coordination)
559
+ * value for coordinationScopes.
560
+ * @param {object} coordinationScopes The original coordinationScopes passed to the view.
561
+ * @returns {object} The coordinationScopes after filling in with meta-coordinationScopes.
562
+ */
563
+ export function useCoordinationScopes(coordinationScopes) {
564
+ const metaSpace = useViewConfigStore((state) => {
565
+ const { coordinationSpace } = state.viewConfig;
566
+ return coordinationSpace?.[CoordinationType.META_COORDINATION_SCOPES];
567
+ }, shallow);
568
+ const vals = useMemo(() => {
569
+ const scopes = getScopes(coordinationScopes, metaSpace);
570
+ // Prevent infinite loop, delete metaCoordinationScopes now that they are computed.
571
+ delete scopes[CoordinationType.META_COORDINATION_SCOPES];
572
+ return scopes;
573
+ }, [coordinationScopes, metaSpace]);
574
+ return vals;
575
+ }
576
+ /**
577
+ * Get the "computed" (i.e., after accounting for meta-coordination)
578
+ * value for coordinationScopesBy.
579
+ * @param {object} coordinationScopes The original coordinationScopes passed to the view.
580
+ * @param {object} coordinationScopesBy The original coordinationScopesBy passed to the view.
581
+ * @returns {object} The coordinationScopesBy after filling in with meta-coordinationScopesBy.
582
+ */
583
+ export function useCoordinationScopesBy(coordinationScopes, coordinationScopesBy) {
584
+ const metaSpaceBy = useViewConfigStore((state) => {
585
+ const { coordinationSpace } = state.viewConfig;
586
+ return coordinationSpace?.[CoordinationType.META_COORDINATION_SCOPES_BY];
295
587
  }, shallow);
296
- const setters = useMemo(() => fromEntries(typeScopesArr.map((datasetScope) => {
297
- const datasetSetters = fromEntries(parameters.map((parameter) => {
298
- const setterName = `set${capitalize(parameter)}`;
299
- let setterFunc;
300
- const parameterScopeGlobal = coordinationScopes[parameter];
301
- const parameterScopeByDataset = byTypeScopes?.[parameter];
302
- if (parameterScopeByDataset && parameterScopeByDataset[datasetScope]) {
303
- setterFunc = value => setCoordinationValue({
304
- parameter,
305
- scope: parameterScopeByDataset[datasetScope],
306
- value,
588
+ const vals = useMemo(() => {
589
+ const scopesBy = getScopesBy(coordinationScopes, coordinationScopesBy, metaSpaceBy);
590
+ return scopesBy;
591
+ }, [coordinationScopes, coordinationScopesBy, metaSpaceBy]);
592
+ return vals;
593
+ }
594
+ /**
595
+ * Use a second level of complex coordination.
596
+ * @param {string[]} parameters Array of coordination types.
597
+ * @param {object} coordinationScopesBy The coordinationScopesBy object from the view definition.
598
+ * @param {string} primaryType The first-level coordination type, such as spatialImageLayer.
599
+ * @param {string} secondaryType The second-level coordination type, such as spatialImageChannel.
600
+ * @returns The results of useComplexCoordination.
601
+ */
602
+ export function useComplexCoordinationSecondary(parameters, coordinationScopes, coordinationScopesBy, primaryType, secondaryType) {
603
+ const coordinationScopesFake = useMemo(() => {
604
+ if (coordinationScopesBy?.[primaryType]?.[secondaryType]) {
605
+ return {
606
+ ...coordinationScopes,
607
+ [secondaryType]: Object.values(coordinationScopesBy[primaryType][secondaryType]).flat(),
608
+ };
609
+ }
610
+ // First fallback: try coarser (_Channel -> _Layer -> view)
611
+ // coordination values when finer ones are null/undefined.
612
+ if (coordinationScopes?.[secondaryType] && Array.isArray(coordinationScopes[secondaryType])) {
613
+ return {
614
+ ...coordinationScopes,
615
+ [secondaryType]: coordinationScopes[secondaryType],
616
+ };
617
+ }
618
+ // Finally, fall back to empty array.
619
+ return { [secondaryType]: [] };
620
+ }, [coordinationScopesBy, primaryType, secondaryType]);
621
+ const [flatValues, flatSetters] = useComplexCoordination(parameters, coordinationScopesFake, coordinationScopesBy, secondaryType);
622
+ const nestedValues = useMemo(() => {
623
+ // Re-nest
624
+ const result = {};
625
+ if (coordinationScopesBy?.[primaryType]?.[secondaryType]) {
626
+ Object.entries(coordinationScopesBy[primaryType][secondaryType])
627
+ .forEach(([layerScope, channelScopes]) => {
628
+ result[layerScope] = {};
629
+ channelScopes.forEach((channelScope) => {
630
+ result[layerScope][channelScope] = flatValues[channelScope];
307
631
  });
308
- }
309
- else if (parameterScopeGlobal) {
310
- setterFunc = value => setCoordinationValue({
311
- parameter,
312
- scope: parameterScopeGlobal,
313
- value,
632
+ });
633
+ }
634
+ else if (coordinationScopes?.[secondaryType] && Array.isArray(coordinationScopes[secondaryType])) {
635
+ // Re-nesting for fallback case.
636
+ const layerScopes = coordinationScopes[primaryType];
637
+ layerScopes.forEach((layerScope) => {
638
+ // eslint-disable-next-line prefer-destructuring
639
+ result[layerScope] = flatValues;
640
+ });
641
+ }
642
+ return result;
643
+ }, [flatValues]);
644
+ const nestedSetters = useMemo(() => {
645
+ // Re-nest
646
+ const result = {};
647
+ if (coordinationScopesBy?.[primaryType]?.[secondaryType]) {
648
+ Object.entries(coordinationScopesBy[primaryType][secondaryType])
649
+ .forEach(([layerScope, channelScopes]) => {
650
+ result[layerScope] = {};
651
+ channelScopes.forEach((channelScope) => {
652
+ result[layerScope][channelScope] = flatSetters[channelScope];
314
653
  });
315
- }
316
- else {
317
- console.error(`coordination scope for ${parameter} was not found.`);
318
- }
319
- return [setterName, setterFunc];
320
- }));
321
- return [datasetScope, datasetSetters];
322
- // eslint-disable-next-line react-hooks/exhaustive-deps
323
- })), [parameters, coordinationScopes]);
324
- return [values, setters];
654
+ });
655
+ }
656
+ else if (coordinationScopes?.[secondaryType] && Array.isArray(coordinationScopes[secondaryType])) {
657
+ // Re-nesting for fallback case.
658
+ const layerScopes = coordinationScopes[primaryType];
659
+ layerScopes.forEach((layerScope) => {
660
+ // eslint-disable-next-line prefer-destructuring
661
+ result[layerScope] = flatSetters;
662
+ });
663
+ }
664
+ return result;
665
+ }, [flatSetters]);
666
+ return [nestedValues, nestedSetters];
325
667
  }
326
668
  /**
327
669
  * Use coordination values and coordination setter functions corresponding to
@@ -458,41 +800,6 @@ export function getMatchingLoader(loaders, dataset, dataType, viewCoordinationVa
458
800
  export function useMatchingLoader(loaders, dataset, dataType, viewCoordinationValues) {
459
801
  return useMemo(() => getMatchingLoader(loaders, dataset, dataType, viewCoordinationValues), [loaders, dataset, dataType, viewCoordinationValues]);
460
802
  }
461
- /**
462
- * Find a specific loader instance for a particular dataset, data type, and view
463
- * coordination values (mapping from coordination types to coordination values).
464
- * Uses lodash/isMatch to perform matching against the file definition's
465
- * coordination value mapping.
466
- * TODO: can this function be removed?
467
- * @param {object} loaders The value returned by useLoaders.
468
- * @param {string} dataset The dataset UID.
469
- * @param {string} dataType The data type for the matching file.
470
- * @param {object} viewCoordinationValues Current coordination values
471
- * from the view. Match these against a subset of file definition coordination
472
- * values.
473
- * @returns The matching loader instance or `null`.
474
- */
475
- export function useMatchingLoaders(loaders, dataset, dataType, viewCoordinationValuesObj) {
476
- return useMemo(() => {
477
- if (!loaders[dataset]) {
478
- return null;
479
- }
480
- const loaderInternMap = loaders[dataset].loaders[dataType];
481
- if (!loaderInternMap) {
482
- return null;
483
- }
484
- const loaderKeys = Array.from(loaderInternMap.keys());
485
- function getLoader(viewCoordinationValues) {
486
- const matchingKey = loaderKeys
487
- .find(fileCoordinationValues => isMatch(fileCoordinationValues, viewCoordinationValues));
488
- return loaderInternMap.get(matchingKey);
489
- }
490
- return fromEntries(Object.entries(viewCoordinationValuesObj).map(([key, viewCoordinationValues]) => ([
491
- key,
492
- getLoader(viewCoordinationValues),
493
- ])));
494
- }, [loaders, dataset, dataType, viewCoordinationValuesObj]);
495
- }
496
803
  /**
497
804
  * Obtain the view config layout array from
498
805
  * the global app state.
@@ -511,6 +818,24 @@ export function useLayout() {
511
818
  export function useRemoveComponent() {
512
819
  return useViewConfigStore(state => state.removeComponent);
513
820
  }
821
+ /**
822
+ * Obtain the component removal function from
823
+ * the global app state.
824
+ * @returns {function} The remove component function
825
+ * in the `useViewInfoStore` store.
826
+ */
827
+ export function useRemoveImageChannelInMetaCoordinationScopes() {
828
+ return useViewConfigStore(state => state.removeImageChannelInMetaCoordinationScopes);
829
+ }
830
+ /**
831
+ * Obtain the component removal function from
832
+ * the global app state.
833
+ * @returns {function} The remove component function
834
+ * in the `useViewInfoStore` store.
835
+ */
836
+ export function useAddImageChannelInMetaCoordinationScopes() {
837
+ return useViewConfigStore(state => state.addImageChannelInMetaCoordinationScopes);
838
+ }
514
839
  /**
515
840
  * Obtain the component prop setter function from
516
841
  * the global app state.
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=hooks.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hooks.test.d.ts","sourceRoot":"","sources":["../../src/state/hooks.test.js"],"names":[],"mappings":""}