@vitessce/vit-s 2.0.3 → 3.0.1

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 (215) hide show
  1. package/dist/index.js +44072 -0
  2. package/dist-tsc/CallbackPublisher.d.ts +18 -0
  3. package/dist-tsc/CallbackPublisher.d.ts.map +1 -0
  4. package/dist-tsc/CallbackPublisher.js +62 -0
  5. package/dist-tsc/LoadingIndicator.d.ts +2 -0
  6. package/dist-tsc/LoadingIndicator.d.ts.map +1 -0
  7. package/dist-tsc/LoadingIndicator.js +26 -0
  8. package/dist-tsc/TitleInfo.d.ts +2 -0
  9. package/dist-tsc/TitleInfo.d.ts.map +1 -0
  10. package/dist-tsc/TitleInfo.js +74 -0
  11. package/dist-tsc/VitS.d.ts +50 -0
  12. package/dist-tsc/VitS.d.ts.map +1 -0
  13. package/dist-tsc/VitS.js +139 -0
  14. package/dist-tsc/VitessceGrid.d.ts +23 -0
  15. package/dist-tsc/VitessceGrid.d.ts.map +1 -0
  16. package/dist-tsc/VitessceGrid.js +63 -0
  17. package/dist-tsc/VitessceGrid.test.d.ts +2 -0
  18. package/dist-tsc/VitessceGrid.test.d.ts.map +1 -0
  19. package/dist-tsc/VitessceGrid.test.js +43 -0
  20. package/dist-tsc/Warning.d.ts +2 -0
  21. package/dist-tsc/Warning.d.ts.map +1 -0
  22. package/dist-tsc/Warning.js +45 -0
  23. package/dist-tsc/classNames.d.ts +8 -0
  24. package/dist-tsc/classNames.d.ts.map +1 -0
  25. package/dist-tsc/classNames.js +8 -0
  26. package/dist-tsc/data/AbstractLoader.d.ts +22 -0
  27. package/dist-tsc/data/AbstractLoader.d.ts.map +1 -0
  28. package/dist-tsc/data/AbstractLoader.js +18 -0
  29. package/dist-tsc/data/AbstractTwoStepLoader.d.ts +6 -0
  30. package/dist-tsc/data/AbstractTwoStepLoader.d.ts.map +1 -0
  31. package/dist-tsc/data/AbstractTwoStepLoader.js +7 -0
  32. package/dist-tsc/data/LoaderResult.d.ts +12 -0
  33. package/dist-tsc/data/LoaderResult.d.ts.map +1 -0
  34. package/dist-tsc/data/LoaderResult.js +12 -0
  35. package/dist-tsc/data/index.d.ts +4 -0
  36. package/dist-tsc/data/index.d.ts.map +1 -0
  37. package/dist-tsc/data/index.js +3 -0
  38. package/dist-tsc/data/loader-registry.d.ts +9 -0
  39. package/dist-tsc/data/loader-registry.d.ts.map +1 -0
  40. package/dist-tsc/data/loader-registry.js +27 -0
  41. package/dist-tsc/data-hook-utils.d.ts +65 -0
  42. package/dist-tsc/data-hook-utils.d.ts.map +1 -0
  43. package/dist-tsc/data-hook-utils.js +191 -0
  44. package/dist-tsc/data-hooks.d.ts +73 -0
  45. package/dist-tsc/data-hooks.d.ts.map +1 -0
  46. package/dist-tsc/data-hooks.js +254 -0
  47. package/dist-tsc/errors/AbstractLoaderError.d.ts +10 -0
  48. package/dist-tsc/errors/AbstractLoaderError.d.ts.map +1 -0
  49. package/dist-tsc/errors/AbstractLoaderError.js +13 -0
  50. package/dist-tsc/errors/DataSourceFetchError.d.ts +8 -0
  51. package/dist-tsc/errors/DataSourceFetchError.d.ts.map +1 -0
  52. package/dist-tsc/errors/DataSourceFetchError.js +13 -0
  53. package/dist-tsc/errors/DatasetNotFoundError.d.ts +6 -0
  54. package/dist-tsc/errors/DatasetNotFoundError.d.ts.map +1 -0
  55. package/dist-tsc/errors/DatasetNotFoundError.js +17 -0
  56. package/dist-tsc/errors/LoaderNotFoundError.d.ts +10 -0
  57. package/dist-tsc/errors/LoaderNotFoundError.d.ts.map +1 -0
  58. package/dist-tsc/errors/LoaderNotFoundError.js +17 -0
  59. package/dist-tsc/errors/LoaderValidationError.d.ts +10 -0
  60. package/dist-tsc/errors/LoaderValidationError.d.ts.map +1 -0
  61. package/dist-tsc/errors/LoaderValidationError.js +15 -0
  62. package/dist-tsc/errors/index.d.ts +6 -0
  63. package/dist-tsc/errors/index.d.ts.map +1 -0
  64. package/dist-tsc/errors/index.js +5 -0
  65. package/dist-tsc/hooks.d.ts +76 -0
  66. package/dist-tsc/hooks.d.ts.map +1 -0
  67. package/dist-tsc/hooks.js +262 -0
  68. package/dist-tsc/index.d.ts +12 -0
  69. package/dist-tsc/index.d.ts.map +1 -0
  70. package/dist-tsc/index.js +11 -17
  71. package/dist-tsc/mui-utils.d.ts +2 -0
  72. package/dist-tsc/mui-utils.d.ts.map +1 -0
  73. package/dist-tsc/mui-utils.js +44 -0
  74. package/dist-tsc/plugins.test.fixtures.d.ts +61 -0
  75. package/dist-tsc/plugins.test.fixtures.d.ts.map +1 -0
  76. package/dist-tsc/plugins.test.fixtures.js +76 -0
  77. package/dist-tsc/shared-mui/components.d.ts +2 -0
  78. package/dist-tsc/shared-mui/components.d.ts.map +1 -0
  79. package/dist-tsc/shared-mui/components.js +29 -0
  80. package/dist-tsc/shared-mui/container.d.ts +2 -0
  81. package/dist-tsc/shared-mui/container.d.ts.map +1 -0
  82. package/dist-tsc/shared-mui/container.js +211 -0
  83. package/dist-tsc/shared-mui/styles.d.ts +5 -0
  84. package/dist-tsc/shared-mui/styles.d.ts.map +1 -0
  85. package/dist-tsc/shared-mui/styles.js +109 -0
  86. package/dist-tsc/shared-plot-options/CellColorEncodingOption.d.ts +2 -0
  87. package/dist-tsc/shared-plot-options/CellColorEncodingOption.d.ts.map +1 -0
  88. package/dist-tsc/shared-plot-options/CellColorEncodingOption.js +17 -0
  89. package/dist-tsc/shared-plot-options/OptionSelect.d.ts +2 -0
  90. package/dist-tsc/shared-plot-options/OptionSelect.d.ts.map +1 -0
  91. package/dist-tsc/shared-plot-options/OptionSelect.js +12 -0
  92. package/dist-tsc/shared-plot-options/OptionsContainer.d.ts +2 -0
  93. package/dist-tsc/shared-plot-options/OptionsContainer.d.ts.map +1 -0
  94. package/dist-tsc/shared-plot-options/OptionsContainer.js +9 -0
  95. package/dist-tsc/shared-plot-options/index.d.ts +5 -0
  96. package/dist-tsc/shared-plot-options/index.d.ts.map +1 -0
  97. package/dist-tsc/shared-plot-options/index.js +4 -0
  98. package/dist-tsc/shared-plot-options/styles.d.ts +2 -0
  99. package/dist-tsc/shared-plot-options/styles.d.ts.map +1 -0
  100. package/dist-tsc/shared-plot-options/styles.js +48 -0
  101. package/dist-tsc/state/hooks.d.ts +242 -0
  102. package/dist-tsc/state/hooks.d.ts.map +1 -0
  103. package/dist-tsc/state/hooks.js +615 -0
  104. package/dist-tsc/title-styles.d.ts +2 -0
  105. package/dist-tsc/title-styles.d.ts.map +1 -0
  106. package/dist-tsc/title-styles.js +72 -0
  107. package/dist-tsc/view-config-utils.d.ts +31 -0
  108. package/dist-tsc/view-config-utils.d.ts.map +1 -0
  109. package/dist-tsc/view-config-utils.js +240 -0
  110. package/dist-tsc/view-config-utils.test.d.ts +2 -0
  111. package/dist-tsc/view-config-utils.test.d.ts.map +1 -0
  112. package/dist-tsc/view-config-utils.test.fixtures.d.ts +1049 -0
  113. package/dist-tsc/view-config-utils.test.fixtures.d.ts.map +1 -0
  114. package/dist-tsc/view-config-utils.test.fixtures.js +883 -0
  115. package/dist-tsc/view-config-utils.test.js +210 -0
  116. package/dist-tsc/vitessce-grid-layout/VitessceGridLayout.d.ts +8 -0
  117. package/dist-tsc/vitessce-grid-layout/VitessceGridLayout.d.ts.map +1 -0
  118. package/dist-tsc/vitessce-grid-layout/VitessceGridLayout.js +114 -0
  119. package/dist-tsc/vitessce-grid-layout/VitessceGridLayout.test.d.ts +2 -0
  120. package/dist-tsc/vitessce-grid-layout/VitessceGridLayout.test.d.ts.map +1 -0
  121. package/dist-tsc/vitessce-grid-layout/VitessceGridLayout.test.js +48 -0
  122. package/dist-tsc/vitessce-grid-layout/index.d.ts +2 -0
  123. package/dist-tsc/vitessce-grid-layout/index.d.ts.map +1 -0
  124. package/dist-tsc/vitessce-grid-layout/index.js +1 -0
  125. package/dist-tsc/vitessce-grid-layout/layout-utils.d.ts +15 -0
  126. package/dist-tsc/vitessce-grid-layout/layout-utils.d.ts.map +1 -0
  127. package/dist-tsc/vitessce-grid-layout/layout-utils.js +61 -0
  128. package/dist-tsc/vitessce-grid-layout/layout-utils.test.d.ts +2 -0
  129. package/dist-tsc/vitessce-grid-layout/layout-utils.test.d.ts.map +1 -0
  130. package/dist-tsc/vitessce-grid-layout/layout-utils.test.js +149 -0
  131. package/dist-tsc/vitessce-grid-utils.d.ts +13 -0
  132. package/dist-tsc/vitessce-grid-utils.d.ts.map +1 -0
  133. package/dist-tsc/vitessce-grid-utils.js +141 -0
  134. package/package.json +25 -11
  135. package/src/CallbackPublisher.js +7 -11
  136. package/src/LoadingIndicator.js +1 -2
  137. package/src/TitleInfo.js +12 -85
  138. package/src/VitS.js +143 -85
  139. package/src/VitessceGrid.js +35 -21
  140. package/src/VitessceGrid.test.jsx +23 -8
  141. package/src/Warning.js +4 -4
  142. package/src/data/AbstractLoader.js +2 -25
  143. package/src/data/AbstractTwoStepLoader.js +1 -1
  144. package/src/data/LoaderResult.js +5 -0
  145. package/src/data/index.js +3 -3
  146. package/src/data/loader-registry.js +22 -13
  147. package/src/data-hook-utils.js +119 -86
  148. package/src/data-hooks.js +150 -131
  149. package/src/errors/DataSourceFetchError.js +2 -2
  150. package/src/errors/DatasetNotFoundError.js +1 -1
  151. package/src/errors/LoaderNotFoundError.js +1 -1
  152. package/src/errors/LoaderValidationError.js +1 -1
  153. package/src/errors/index.js +5 -6
  154. package/src/hooks.js +81 -26
  155. package/src/index.js +23 -46
  156. package/src/mui-utils.js +52 -0
  157. package/src/shared-mui/components.js +10 -8
  158. package/src/shared-mui/container.js +1 -1
  159. package/src/shared-mui/styles.js +5 -2
  160. package/src/shared-plot-options/CellColorEncodingOption.js +3 -4
  161. package/src/shared-plot-options/OptionSelect.js +3 -3
  162. package/src/shared-plot-options/OptionsContainer.js +2 -5
  163. package/src/shared-plot-options/index.js +4 -4
  164. package/src/shared-plot-options/styles.js +2 -2
  165. package/src/state/hooks.js +75 -23
  166. package/src/title-styles.js +73 -0
  167. package/src/view-config-utils.js +54 -98
  168. package/src/view-config-utils.test.jsx +223 -0
  169. package/src/vitessce-grid-layout/VitessceGridLayout.js +51 -30
  170. package/src/vitessce-grid-layout/VitessceGridLayout.test.jsx +17 -9
  171. package/src/vitessce-grid-layout/index.js +1 -1
  172. package/src/vitessce-grid-layout/layout-utils.js +1 -1
  173. package/src/vitessce-grid-layout/layout-utils.test.js +2 -1
  174. package/src/vitessce-grid-utils.js +27 -15
  175. package/dist/index.mjs +0 -84801
  176. package/src/component-registry.js +0 -21
  177. package/src/errors/OptionsValidationError.js +0 -25
  178. package/src/file-options-schemas-legacy.js +0 -139
  179. package/src/file-options-schemas-legacy.test.js +0 -138
  180. package/src/file-options-schemas.js +0 -327
  181. package/src/file-options-schemas.test.js +0 -26
  182. package/src/joint-file-types-legacy.js +0 -298
  183. package/src/joint-file-types-legacy.test.js +0 -412
  184. package/src/joint-file-types.js +0 -171
  185. package/src/joint-file-types.test.js +0 -144
  186. package/src/json-schemas.js +0 -9
  187. package/src/plugins.js +0 -186
  188. package/src/plugins.test.js +0 -42
  189. package/src/schemas/config-0.1.0.schema.json +0 -85
  190. package/src/schemas/config-1.0.0.schema.json +0 -733
  191. package/src/schemas/config-1.0.1.schema.json +0 -909
  192. package/src/schemas/config-1.0.10.schema.json +0 -969
  193. package/src/schemas/config-1.0.11.schema.json +0 -990
  194. package/src/schemas/config-1.0.12.schema.json +0 -997
  195. package/src/schemas/config-1.0.13.schema.json +0 -1013
  196. package/src/schemas/config-1.0.14.schema.json +0 -1048
  197. package/src/schemas/config-1.0.15.schema.json +0 -1048
  198. package/src/schemas/config-1.0.16.schema.json +0 -1069
  199. package/src/schemas/config-1.0.16.schema.test.js +0 -32
  200. package/src/schemas/config-1.0.2.schema.json +0 -911
  201. package/src/schemas/config-1.0.3.schema.json +0 -911
  202. package/src/schemas/config-1.0.4.schema.json +0 -949
  203. package/src/schemas/config-1.0.5.schema.json +0 -949
  204. package/src/schemas/config-1.0.6.schema.json +0 -950
  205. package/src/schemas/config-1.0.7.schema.json +0 -950
  206. package/src/schemas/config-1.0.8.schema.json +0 -966
  207. package/src/schemas/config-1.0.9.schema.json +0 -965
  208. package/src/schemas/obsSets.schema.js +0 -195
  209. package/src/schemas/obsSetsTabular.schema.js +0 -39
  210. package/src/schemas/raster.schema.js +0 -121
  211. package/src/schemas/schema-schema.sh +0 -34
  212. package/src/schemas/schema.test-old.js +0 -49
  213. package/src/view-config-upgraders.js +0 -628
  214. package/src/view-config-utils.test.js +0 -95
  215. package/src/view-config-versions.js +0 -68
@@ -1,20 +1,18 @@
1
1
  /* eslint-disable no-plusplus */
2
2
  /* eslint-disable camelcase */
3
- import difference from 'lodash/difference';
4
- import cloneDeep from 'lodash/cloneDeep';
5
- import { getNextScope } from '@vitessce/utils';
3
+ import { cloneDeep } from 'lodash-es';
4
+ import { fromEntries, getNextScope } from '@vitessce/utils';
6
5
  import {
7
6
  AUTO_INDEPENDENT_COORDINATION_TYPES,
7
+ META_VERSION,
8
8
  } from '@vitessce/constants-internal';
9
- import { getViewTypes } from './component-registry';
10
- import {
11
- getComponentCoordinationTypes,
12
- getDefaultCoordinationValues,
13
- getCoordinationTypes,
14
- getFileTypes,
15
- getJointFileTypes,
16
- } from './plugins';
17
- import { SCHEMA_HANDLERS } from './view-config-versions';
9
+
10
+ export function logConfig(config, name) {
11
+ console.groupCollapsed(`🚄 VitS (${META_VERSION.version}) ${name}`);
12
+ console.info(`data:,${JSON.stringify(config)}`);
13
+ console.info(JSON.stringify(config, null, 2));
14
+ console.groupEnd();
15
+ }
18
16
 
19
17
  /**
20
18
  * Get a list of all unique scope names for a
@@ -40,8 +38,10 @@ export function getExistingScopesForCoordinationType(config, coordinationType) {
40
38
  * to set in the coordination space.
41
39
  * @returns {object} The new view config.
42
40
  */
43
- function coordinateComponentsTogether(config, coordinationType, scopeValue) {
44
- const componentCoordinationTypes = getComponentCoordinationTypes();
41
+ function coordinateComponentsTogether(config, coordinationType, scopeValue, viewTypes) {
42
+ const componentCoordinationTypes = fromEntries(
43
+ viewTypes.map(vt => ([vt.name, vt.coordinationTypes])),
44
+ );
45
45
  const scopeName = getNextScope(getExistingScopesForCoordinationType(config, coordinationType));
46
46
  const newConfig = {
47
47
  ...config,
@@ -82,8 +82,10 @@ function coordinateComponentsTogether(config, coordinationType, scopeValue) {
82
82
  * to set in the coordination space.
83
83
  * @returns {object} The new view config.
84
84
  */
85
- function coordinateComponentsIndependent(config, coordinationType, scopeValue) {
86
- const componentCoordinationTypes = getComponentCoordinationTypes();
85
+ function coordinateComponentsIndependent(config, coordinationType, scopeValue, viewTypes) {
86
+ const componentCoordinationTypes = fromEntries(
87
+ viewTypes.map(vt => ([vt.name, vt.coordinationTypes])),
88
+ );
87
89
  const newConfig = {
88
90
  ...config,
89
91
  layout: [...config.layout],
@@ -120,13 +122,24 @@ function coordinateComponentsIndependent(config, coordinationType, scopeValue) {
120
122
  return newConfig;
121
123
  }
122
124
 
123
- function initializeAuto(config) {
125
+ /**
126
+ * Perform initialization using initStrategy: "auto".
127
+ * @param {object} config The view config.
128
+ * @param {PluginCoordinationType[]} coordinationTypeObjs
129
+ * @param {PluginViewType[]} viewTypeObjs
130
+ * @returns {object} The config.
131
+ */
132
+ function initializeAuto(config, coordinationTypeObjs, viewTypeObjs) {
124
133
  let newConfig = config;
125
134
  const { layout, datasets } = newConfig;
126
135
 
127
- const componentCoordinationTypes = getComponentCoordinationTypes();
128
- const defaultCoordinationValues = getDefaultCoordinationValues();
129
- const coordinationTypes = getCoordinationTypes();
136
+ const componentCoordinationTypes = fromEntries(
137
+ viewTypeObjs.map(vt => ([vt.name, vt.coordinationTypes])),
138
+ );
139
+ const defaultCoordinationValues = fromEntries(
140
+ coordinationTypeObjs.map(ct => ([ct.name, ct.defaultValue])),
141
+ );
142
+ const coordinationTypes = coordinationTypeObjs.map(ct => ct.name);
130
143
 
131
144
  // For each coordination type, check whether it requires initialization.
132
145
  coordinationTypes.forEach((coordinationType) => {
@@ -153,9 +166,13 @@ function initializeAuto(config) {
153
166
  // a unique scope for every component ("independent")
154
167
  // vs. the same scope for every component ("together").
155
168
  if (AUTO_INDEPENDENT_COORDINATION_TYPES.includes(coordinationType)) {
156
- newConfig = coordinateComponentsIndependent(newConfig, coordinationType, defaultValue);
169
+ newConfig = coordinateComponentsIndependent(
170
+ newConfig, coordinationType, defaultValue, viewTypeObjs,
171
+ );
157
172
  } else {
158
- newConfig = coordinateComponentsTogether(newConfig, coordinationType, defaultValue);
173
+ newConfig = coordinateComponentsTogether(
174
+ newConfig, coordinationType, defaultValue, viewTypeObjs,
175
+ );
159
176
  }
160
177
  }
161
178
  });
@@ -163,35 +180,6 @@ function initializeAuto(config) {
163
180
  return newConfig;
164
181
  }
165
182
 
166
- export function checkTypes(config) {
167
- // Add a log message when there are additionalProperties in the coordination space that
168
- // do not appear in the view config JSON schema,
169
- // with a note that this indicates either a mistake or custom coordination type usage.
170
- const coordinationTypesInConfig = Object.keys(config.coordinationSpace || {});
171
- const allCoordinationTypes = getCoordinationTypes();
172
- const unknownCoordinationTypes = difference(coordinationTypesInConfig, allCoordinationTypes);
173
- if (unknownCoordinationTypes.length > 0) {
174
- return [false, `The following coordination types are not recognized: [${unknownCoordinationTypes}].\nIf these are plugin coordination types, ensure that they have been properly registered.`];
175
- }
176
- // Add a log message when there are views in the layout that are neither
177
- // core views nor registered plugin views.
178
- const viewTypesInConfig = config.layout.map(c => c.component);
179
- const allViewTypes = getViewTypes();
180
- const unknownViewTypes = difference(viewTypesInConfig, allViewTypes);
181
- if (unknownViewTypes.length > 0) {
182
- return [false, `The following view types are not recognized: [${unknownViewTypes}].\nIf these are plugin view types, ensure that they have been properly registered.`];
183
- }
184
- // Add a log message when there are file definitions with neither
185
- // core nor registered plugin file types.
186
- const fileTypesInConfig = config.datasets.flatMap(d => d.files.map(f => f.fileType));
187
- const allFileTypes = getFileTypes();
188
- const unknownFileTypes = difference(fileTypesInConfig, allFileTypes);
189
- if (unknownFileTypes.length > 0) {
190
- return [false, `The following file types are not recognized: [${unknownFileTypes}].\nIf these are plugin file types, ensure that they have been properly registered.`];
191
- }
192
- return [true, 'All view types, coordination types, and file types that appear in the view config are recognized.'];
193
- }
194
-
195
183
  /**
196
184
  * Assign unique ids for view definitions where
197
185
  * they are missing a value for the uid property
@@ -200,7 +188,7 @@ export function checkTypes(config) {
200
188
  * @returns The updated view config.
201
189
  */
202
190
  function assignViewUids(config) {
203
- const { layout } = config;
191
+ const { uid, layout } = config;
204
192
  const usedIds = layout.map(view => view.uid);
205
193
  layout.forEach((view, i) => {
206
194
  // Assign uids for views where they are not present.
@@ -210,8 +198,10 @@ function assignViewUids(config) {
210
198
  usedIds.push(nextUid);
211
199
  }
212
200
  });
201
+ const newUid = uid || getNextScope([]);
213
202
  return {
214
203
  ...config,
204
+ uid: newUid,
215
205
  layout,
216
206
  };
217
207
  }
@@ -224,8 +214,10 @@ function assignViewUids(config) {
224
214
  * convenience file types.
225
215
  * @returns The view config containing expanded minimal file types.
226
216
  */
227
- function expandConvenienceFileDefs(config) {
228
- const convenienceFileTypes = getJointFileTypes();
217
+ function expandConvenienceFileDefs(config, jointFileTypes) {
218
+ const convenienceFileTypes = fromEntries(
219
+ jointFileTypes.map(ft => ([ft.name, ft.expandFunction])),
220
+ );
229
221
  const { datasets: currDatasets } = config;
230
222
  const datasets = cloneDeep(currDatasets);
231
223
  currDatasets.forEach((dataset, i) => {
@@ -262,54 +254,18 @@ function expandConvenienceFileDefs(config) {
262
254
  * Should be "stable": if run on the same view config twice, the return value the second
263
255
  * time should be identical to the return value the first time.
264
256
  * @param {object} config The view config prop.
257
+ * @param {PluginJointFileType[]} jointFileTypes
258
+ * @param {PluginCoordinationType[]} coordinationTypes
259
+ * @param {PluginViewType[]} viewTypes
265
260
  * @returns The initialized view config.
266
261
  */
267
- export function initialize(config) {
262
+ export function initialize(config, jointFileTypes, coordinationTypes, viewTypes) {
268
263
  let newConfig = cloneDeep(config);
269
264
  if (newConfig.initStrategy === 'auto') {
270
- newConfig = initializeAuto(config);
265
+ // TODO: pass coordination types with defaults
266
+ // TODO: pass view types with per-view coordination type lists
267
+ newConfig = initializeAuto(config, coordinationTypes, viewTypes);
271
268
  }
272
- newConfig = expandConvenienceFileDefs(newConfig);
269
+ newConfig = expandConvenienceFileDefs(newConfig, jointFileTypes);
273
270
  return assignViewUids(newConfig);
274
271
  }
275
-
276
- export function upgradeAndValidate(oldConfig, onConfigUpgrade = null) {
277
- // oldConfig object must have a `version` property.
278
- let nextConfig = oldConfig;
279
- let fromVersion;
280
- let upgradeFunction; let
281
- validateFunction;
282
-
283
- do {
284
- fromVersion = nextConfig.version;
285
-
286
- if (!Object.keys(SCHEMA_HANDLERS).includes(fromVersion)) {
287
- return [{
288
- title: 'Config validation failed',
289
- preformatted: 'Unknown config version.',
290
- }, false];
291
- }
292
-
293
- [validateFunction, upgradeFunction] = SCHEMA_HANDLERS[fromVersion];
294
-
295
- // Validate under the legacy schema before upgrading.
296
- const validLegacy = validateFunction(nextConfig);
297
- if (!validLegacy) {
298
- const failureReason = JSON.stringify(validateFunction.errors, null, 2);
299
- return [{
300
- title: 'Config validation failed',
301
- preformatted: failureReason,
302
- }, false];
303
- }
304
-
305
- if (upgradeFunction) {
306
- const prevConfig = nextConfig;
307
- nextConfig = upgradeFunction(nextConfig);
308
- if (onConfigUpgrade) {
309
- onConfigUpgrade(prevConfig, nextConfig);
310
- }
311
- }
312
- } while (upgradeFunction);
313
-
314
- return [nextConfig, true];
315
- }
@@ -0,0 +1,223 @@
1
+ /* eslint-disable camelcase */
2
+ import { describe, it, expect } from 'vitest';
3
+ import React from 'react';
4
+ import { z } from '@vitessce/schemas';
5
+ import { PluginViewType, PluginCoordinationType } from '@vitessce/plugins';
6
+ import {
7
+ getExistingScopesForCoordinationType,
8
+ initialize,
9
+ } from './view-config-utils.js';
10
+
11
+ function FakeComponent(props) {
12
+ const { text } = props;
13
+ return <span>{text}</span>;
14
+ }
15
+
16
+ const jointFileTypes = [];
17
+ const coordinationTypes = [
18
+ new PluginCoordinationType('dataset', null, z.string().nullable()),
19
+ new PluginCoordinationType('embeddingTargetX', 0, z.number()),
20
+ new PluginCoordinationType('embeddingTargetY', 0, z.number()),
21
+ new PluginCoordinationType('embeddingZoom', 3, z.number()),
22
+ new PluginCoordinationType('embeddingType', null, z.string().nullable()),
23
+ ];
24
+ const viewTypes = [
25
+ new PluginViewType('description', FakeComponent, ['dataset']),
26
+ new PluginViewType('scatterplot', FakeComponent, ['dataset', 'embeddingType', 'embeddingZoom', 'embeddingTargetX', 'embeddingTargetY']),
27
+ ];
28
+
29
+ describe('src/app/view-config-utils.js', () => {
30
+ describe('getExistingScopesForCoordinationType', () => {
31
+ it('gets all scope names for a particular coordination type', () => {
32
+ const config = {
33
+ coordinationSpace: {
34
+ dataset: {
35
+ A: 'my-dataset-1',
36
+ B: 'my-dataset-2',
37
+ },
38
+ },
39
+ layout: [
40
+ {
41
+ coordinationScopes: {
42
+ dataset: 'A',
43
+ },
44
+ },
45
+ {
46
+ coordinationScopes: {
47
+ dataset: 'C',
48
+ },
49
+ },
50
+ ],
51
+ };
52
+ expect(getExistingScopesForCoordinationType(config, 'dataset')).toEqual(['A', 'B', 'C']);
53
+ });
54
+ });
55
+
56
+ describe('initialize', () => {
57
+ it('initializes coordination space and component coordination scopes when initStrategy is auto', () => {
58
+ const preInitializationConfig = {
59
+ version: '1.0.16',
60
+ name: 'My config name',
61
+ description: 'My config description',
62
+ initStrategy: 'auto',
63
+ coordinationSpace: {
64
+ embeddingTargetX: {
65
+ A: 0,
66
+ },
67
+ embeddingTargetY: {
68
+ A: 0,
69
+ },
70
+ embeddingType: {
71
+ 't-SNE': 't-SNE',
72
+ },
73
+ },
74
+ datasets: [
75
+ {
76
+ files: [],
77
+ name: 'A',
78
+ uid: 'A',
79
+ },
80
+ ],
81
+ layout: [
82
+ {
83
+ component: 'description',
84
+ coordinationScopes: {},
85
+ h: 2,
86
+ w: 3,
87
+ x: 9,
88
+ y: 0,
89
+ },
90
+ {
91
+ component: 'scatterplot',
92
+ coordinationScopes: {
93
+ embeddingTargetX: 'A',
94
+ embeddingTargetY: 'A',
95
+ embeddingType: 't-SNE',
96
+ },
97
+ h: 4,
98
+ w: 5,
99
+ x: 0,
100
+ y: 2,
101
+ },
102
+ ],
103
+ };
104
+
105
+ const initializedViewConfig = {
106
+ version: '1.0.16',
107
+ uid: 'A',
108
+ name: 'My config name',
109
+ description: 'My config description',
110
+ initStrategy: 'auto',
111
+ coordinationSpace: {
112
+ dataset: {
113
+ A: 'A',
114
+ },
115
+ embeddingTargetX: {
116
+ A: 0,
117
+ },
118
+ embeddingTargetY: {
119
+ A: 0,
120
+ },
121
+ embeddingType: {
122
+ 't-SNE': 't-SNE',
123
+ },
124
+ embeddingZoom: {
125
+ A: 3,
126
+ },
127
+ },
128
+ datasets: [
129
+ {
130
+ files: [],
131
+ name: 'A',
132
+ uid: 'A',
133
+ },
134
+ ],
135
+ layout: [
136
+ {
137
+ component: 'description',
138
+ coordinationScopes: {
139
+ dataset: 'A',
140
+ },
141
+ h: 2,
142
+ uid: 'A',
143
+ w: 3,
144
+ x: 9,
145
+ y: 0,
146
+ },
147
+ {
148
+ component: 'scatterplot',
149
+ coordinationScopes: {
150
+ dataset: 'A',
151
+ embeddingTargetX: 'A',
152
+ embeddingTargetY: 'A',
153
+ embeddingType: 't-SNE',
154
+ embeddingZoom: 'A',
155
+ },
156
+ h: 4,
157
+ uid: 'B',
158
+ w: 5,
159
+ x: 0,
160
+ y: 2,
161
+ },
162
+ ],
163
+ };
164
+
165
+ expect(initialize(preInitializationConfig, jointFileTypes, coordinationTypes, viewTypes))
166
+ .toEqual(initializedViewConfig);
167
+ });
168
+
169
+ it('does not initialize when initStrategy is none', () => {
170
+ const preInitializationConfig = {
171
+ version: '1.0.16',
172
+ name: 'My config name',
173
+ description: 'My config description',
174
+ initStrategy: 'none', // initStrategy is none
175
+ coordinationSpace: {
176
+ embeddingTargetX: {
177
+ A: 0,
178
+ },
179
+ embeddingTargetY: {
180
+ A: 0,
181
+ },
182
+ embeddingType: {
183
+ 't-SNE': 't-SNE',
184
+ },
185
+ },
186
+ datasets: [
187
+ {
188
+ files: [],
189
+ name: 'A',
190
+ uid: 'A',
191
+ },
192
+ ],
193
+ layout: [
194
+ {
195
+ component: 'description',
196
+ coordinationScopes: {},
197
+ h: 2,
198
+ w: 3,
199
+ x: 9,
200
+ y: 0,
201
+ },
202
+ {
203
+ component: 'scatterplot',
204
+ coordinationScopes: {
205
+ embeddingTargetX: 'A',
206
+ embeddingTargetY: 'A',
207
+ embeddingType: 't-SNE',
208
+ },
209
+ h: 4,
210
+ w: 5,
211
+ x: 0,
212
+ y: 2,
213
+ },
214
+ ],
215
+ };
216
+ expect(
217
+ initialize(
218
+ preInitializationConfig, jointFileTypes, coordinationTypes, viewTypes,
219
+ ).coordinationSpace,
220
+ ).toEqual(preInitializationConfig.coordinationSpace);
221
+ });
222
+ });
223
+ });
@@ -1,7 +1,7 @@
1
- import React, { useState } from 'react';
1
+ import React, { useState, useMemo, useCallback } from 'react';
2
2
  import { Responsive, WidthProvider } from 'react-grid-layout-with-lodash';
3
- import isEqual from 'lodash/isEqual';
4
- import { getMaxRows, resolveLayout } from './layout-utils';
3
+ import { isEqual } from 'lodash-es';
4
+ import { getMaxRows, resolveLayout } from './layout-utils.js';
5
5
 
6
6
  const ResponsiveGridLayout = WidthProvider(Responsive);
7
7
 
@@ -16,18 +16,31 @@ class ResponsiveHeightGridLayout extends ResponsiveGridLayout {
16
16
  export function VitessceGridLayout(props) {
17
17
  const {
18
18
  layout,
19
- getComponent, padding, margin, draggableHandle: draggableHandleClass,
19
+ viewTypes, padding, margin: marginProp, draggableHandle: draggableHandleClass,
20
20
  onResize, onResizeStop, rowHeight, theme, height,
21
21
  onRemoveComponent, onLayoutChange: onLayoutChangeProp,
22
22
  isBounded,
23
23
  } = props;
24
24
 
25
+ const getComponent = useCallback((viewType) => {
26
+ if (Array.isArray(viewTypes)) {
27
+ const matchingViewType = viewTypes.find(v => v.name === viewType);
28
+ if (matchingViewType) {
29
+ return matchingViewType.component;
30
+ }
31
+ }
32
+ return () => null;
33
+ }, [viewTypes]);
34
+
25
35
  const draggableHandle = `.${draggableHandleClass}`;
26
36
 
27
37
  // If layout changes, update grid components.
28
38
  const {
29
39
  cols: gridCols, layouts: gridLayouts, breakpoints: gridBreakpoints, components: gridComponents,
30
- } = resolveLayout(layout);
40
+ } = useMemo(() => resolveLayout(layout), [layout]);
41
+
42
+ const containerPadding = useMemo(() => ([padding, padding]), [padding]);
43
+ const margin = useMemo(() => ([marginProp, marginProp]), [marginProp]);
31
44
 
32
45
  const maxRows = getMaxRows(gridLayouts);
33
46
 
@@ -53,16 +66,16 @@ export function VitessceGridLayout(props) {
53
66
  //
54
67
  // Additionally, react-grid-layout doesn't revert if you don't save a new/changed layouts. If you
55
68
  // wish to do this, first you have to save the new layouts and render the grid with it, and then
56
- // you can revert to the original layouts. Thus, we need one state for the current grid layouts,
69
+ // you can revert to the original layouts. Thus, we need one state for a temporary grid layout,
57
70
  // which gets called on every onLayoutChange. If the grid height is still valid, we then call
58
- // onValidLayoutChange, otherwise we reset currentGridLayouts to lastValidGridLayouts.
71
+ // onValidLayoutChange, otherwise we call onValidLayoutChange with lastValidGridLayouts.
59
72
  //
60
73
  // See the following GitHub issue for more information.
61
74
  // https://github.com/react-grid-layout/react-grid-layout/issues/1104#issuecomment-827785217
62
- const [currentGridLayouts, setCurrentGridLayouts] = useState(gridLayouts);
75
+ const [tempGridLayouts, setTempGridLayouts] = useState(null);
63
76
  const [lastValidGridLayouts, setLastValidGridLayouts] = useState(gridLayouts);
64
77
 
65
- const onValidLayoutChange = (newLayout) => {
78
+ const onValidLayoutChange = useCallback((newLayout) => {
66
79
  if (newLayout.length === Object.entries(gridComponents).length) {
67
80
  const newComponentProps = {};
68
81
  newLayout.forEach((nextC) => {
@@ -84,24 +97,31 @@ export function VitessceGridLayout(props) {
84
97
  onLayoutChangeProp(newComponentProps);
85
98
  }
86
99
  }
87
- };
88
-
89
- const onLayoutChange = (newLayout, allLayouts) => {
90
- setCurrentGridLayouts(allLayouts);
91
- if (!isBounded || getMaxRows({ ID: newLayout }) <= maxRows) {
92
- onValidLayoutChange(newLayout);
93
- setLastValidGridLayouts(allLayouts);
94
- } else {
95
- setCurrentGridLayouts(lastValidGridLayouts);
96
- }
97
- };
98
-
99
- const saveCurrentLayouts = () => {
100
- setLastValidGridLayouts(currentGridLayouts);
101
- };
100
+ }, [gridComponents, onLayoutChangeProp]);
101
+
102
+ const onLayoutChange = useCallback((newLayout, allLayouts) => {
103
+ // We first need to set the new layout to the potentially-invalid allLayouts
104
+ // (see comments above about react-grid-layout limitations).
105
+ setTempGridLayouts(allLayouts);
106
+ // Then we wait 50ms and validate/set the new layout.
107
+ setTimeout(() => {
108
+ if (!isBounded || getMaxRows({ ID: newLayout }) <= maxRows) {
109
+ // Good, new layout was valid with respect to isBounded, so set in parent.
110
+ onValidLayoutChange(newLayout);
111
+ setLastValidGridLayouts(allLayouts);
112
+ } else {
113
+ // Bad, new layout was not valid with respect to isBounded.
114
+ onValidLayoutChange(lastValidGridLayouts);
115
+ }
116
+ setTempGridLayouts(null);
117
+ }, 50);
118
+ }, [isBounded, lastValidGridLayouts, maxRows, onValidLayoutChange]);
102
119
 
120
+ const saveCurrentLayouts = useCallback(() => {
121
+ setLastValidGridLayouts(gridLayouts);
122
+ }, [gridLayouts]);
103
123
 
104
- const layoutChildren = Object.values(gridComponents).map((v) => {
124
+ const layoutChildren = useMemo(() => Object.values(gridComponents).map((v) => {
105
125
  const Component = getComponent(v.component);
106
126
 
107
127
  const removeGridComponent = () => {
@@ -120,14 +140,15 @@ export function VitessceGridLayout(props) {
120
140
  />
121
141
  </div>
122
142
  );
123
- });
124
- return (currentGridLayouts && gridComponents && gridBreakpoints && gridCols) && (
143
+ }), [gridComponents, getComponent, onRemoveComponent, theme]);
144
+
145
+ return (gridLayouts && gridComponents && gridBreakpoints && gridCols) && (
125
146
  <>
126
147
  {style}
127
148
  <ResponsiveHeightGridLayout
128
149
  className="layout"
129
150
  cols={gridCols}
130
- layouts={currentGridLayouts}
151
+ layouts={tempGridLayouts || gridLayouts}
131
152
  breakpoints={gridBreakpoints}
132
153
  height={height}
133
154
  rowHeight={
@@ -136,8 +157,8 @@ export function VitessceGridLayout(props) {
136
157
  (window.innerHeight - 2 * padding - (maxRows - 1) * margin)
137
158
  / maxRows
138
159
  )}
139
- containerPadding={[padding, padding]}
140
- margin={[margin, margin]}
160
+ containerPadding={containerPadding}
161
+ margin={margin}
141
162
  draggableHandle={draggableHandle}
142
163
  onLayoutChange={onLayoutChange}
143
164
  isBounded={isBounded}
@@ -1,11 +1,13 @@
1
+ import { describe, it, expect, afterEach } from 'vitest';
1
2
  import '@testing-library/jest-dom';
2
- import { cleanup, render, screen, waitForElement } from '@testing-library/react'
3
- import { afterEach } from 'vitest'
3
+ import { cleanup, render, screen } from '@testing-library/react';
4
4
 
5
- import { VitessceGridLayout } from './VitessceGridLayout';
5
+ import React from 'react';
6
+ import { PluginViewType } from '@vitessce/plugins';
7
+ import { VitessceGridLayout } from './VitessceGridLayout.js';
6
8
 
7
9
  afterEach(() => {
8
- cleanup()
10
+ cleanup();
9
11
  });
10
12
 
11
13
  function FakeComponent(props) {
@@ -17,19 +19,24 @@ const layoutJson = {
17
19
  600: [0, 2, 4, 8],
18
20
  },
19
21
  components: [
20
- { component: 'FakeComponent',
22
+ {
23
+ component: 'FakeComponent',
21
24
  uid: 'fake',
22
25
  props: { text: 'Hello World' },
23
- x: 0, y: 0, w: 2 },
26
+ x: 0,
27
+ y: 0,
28
+ w: 2,
29
+ },
24
30
  ],
25
31
  };
26
32
 
27
33
  describe('VitessceGridLayout.js', () => {
28
34
  describe('<VitessceGridLayout />', () => {
29
35
  it('mount() works', async () => {
36
+ const viewTypes = [new PluginViewType('FakeComponent', FakeComponent, [])];
30
37
  const { container } = render(<VitessceGridLayout
31
38
  layout={layoutJson}
32
- getComponent={() => FakeComponent}
39
+ viewTypes={viewTypes}
33
40
  draggableHandle=".my-handle"
34
41
  />);
35
42
 
@@ -43,9 +50,10 @@ describe('VitessceGridLayout.js', () => {
43
50
  });
44
51
 
45
52
  it('rowHeight works', () => {
53
+ const viewTypes = [new PluginViewType('FakeComponent', FakeComponent, [])];
46
54
  const { container } = render(<VitessceGridLayout
47
55
  layout={layoutJson}
48
- getComponent={() => FakeComponent}
56
+ viewTypes={viewTypes}
49
57
  draggableHandle=".my-handle"
50
58
  rowHeight={123}
51
59
  />);
@@ -53,4 +61,4 @@ describe('VitessceGridLayout.js', () => {
53
61
  expect(container.querySelector('.react-grid-item').style.height).toEqual('123px');
54
62
  });
55
63
  });
56
- });
64
+ });
@@ -1 +1 @@
1
- export { VitessceGridLayout } from './VitessceGridLayout';
1
+ export { VitessceGridLayout } from './VitessceGridLayout.js';
@@ -1,4 +1,4 @@
1
- import range from 'lodash/range';
1
+ import { range } from 'lodash-es';
2
2
 
3
3
  function sum(a) {
4
4
  return a.reduce((x, y) => x + y, 0);
@@ -1,6 +1,7 @@
1
+ import { describe, it, expect } from 'vitest';
1
2
  import {
2
3
  makeGridLayout, getMaxRows, resolveLayout,
3
- } from './layout-utils';
4
+ } from './layout-utils.js';
4
5
 
5
6
  describe('layout-utils.js', () => {
6
7
  describe('makeGridLayout', () => {