@genome-spy/core 0.14.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 (226) hide show
  1. package/dist/index.js +224 -0
  2. package/dist/style.css +1 -0
  3. package/package.json +54 -0
  4. package/src/data/collector.js +178 -0
  5. package/src/data/collector.test.js +82 -0
  6. package/src/data/dataFlow.js +109 -0
  7. package/src/data/dataFlow.test.js +3 -0
  8. package/src/data/facetNode.js +17 -0
  9. package/src/data/flow.test.js +71 -0
  10. package/src/data/flowBatch.d.ts +40 -0
  11. package/src/data/flowNode.js +283 -0
  12. package/src/data/flowNode.test.js +49 -0
  13. package/src/data/flowOptimizer.js +117 -0
  14. package/src/data/flowOptimizer.test.js +192 -0
  15. package/src/data/flowTestUtils.js +63 -0
  16. package/src/data/formats/fasta.js +32 -0
  17. package/src/data/formats/fasta.test.js +26 -0
  18. package/src/data/sources/dataSource.js +22 -0
  19. package/src/data/sources/dataSourceFactory.js +24 -0
  20. package/src/data/sources/dataUtils.js +31 -0
  21. package/src/data/sources/dynamicCallbackSource.js +56 -0
  22. package/src/data/sources/dynamicSource.js +36 -0
  23. package/src/data/sources/inlineSource.js +69 -0
  24. package/src/data/sources/inlineSource.test.js +55 -0
  25. package/src/data/sources/namedSource.js +74 -0
  26. package/src/data/sources/sequenceSource.js +46 -0
  27. package/src/data/sources/sequenceSource.test.js +45 -0
  28. package/src/data/sources/urlSource.js +74 -0
  29. package/src/data/transforms/aggregate.js +69 -0
  30. package/src/data/transforms/clone.js +40 -0
  31. package/src/data/transforms/clone.test.js +10 -0
  32. package/src/data/transforms/coverage.js +187 -0
  33. package/src/data/transforms/coverage.test.js +122 -0
  34. package/src/data/transforms/filter.js +37 -0
  35. package/src/data/transforms/filter.test.js +17 -0
  36. package/src/data/transforms/filterScoredLabels.js +134 -0
  37. package/src/data/transforms/flattenCompressedExons.js +57 -0
  38. package/src/data/transforms/flattenDelimited.js +68 -0
  39. package/src/data/transforms/flattenDelimited.test.js +86 -0
  40. package/src/data/transforms/flattenSequence.js +39 -0
  41. package/src/data/transforms/flattenSequence.test.js +33 -0
  42. package/src/data/transforms/formula.js +39 -0
  43. package/src/data/transforms/formula.test.js +18 -0
  44. package/src/data/transforms/identifier.js +108 -0
  45. package/src/data/transforms/identifier.test.js +82 -0
  46. package/src/data/transforms/linearizeGenomicCoordinate.js +101 -0
  47. package/src/data/transforms/measureText.js +44 -0
  48. package/src/data/transforms/pileup.js +128 -0
  49. package/src/data/transforms/pileup.test.js +69 -0
  50. package/src/data/transforms/project.js +41 -0
  51. package/src/data/transforms/project.test.js +31 -0
  52. package/src/data/transforms/regexExtract.js +61 -0
  53. package/src/data/transforms/regexExtract.test.js +66 -0
  54. package/src/data/transforms/regexFold.js +141 -0
  55. package/src/data/transforms/regexFold.test.js +159 -0
  56. package/src/data/transforms/sample.js +101 -0
  57. package/src/data/transforms/sample.test.js +37 -0
  58. package/src/data/transforms/stack.js +137 -0
  59. package/src/data/transforms/stack.test.js +90 -0
  60. package/src/data/transforms/transformFactory.js +60 -0
  61. package/src/encoder/accessor.js +82 -0
  62. package/src/encoder/accessor.test.js +46 -0
  63. package/src/encoder/encoder.js +369 -0
  64. package/src/encoder/encoder.test.js +97 -0
  65. package/src/fonts/Lato-Regular.json +1267 -0
  66. package/src/fonts/Lato-Regular.png +0 -0
  67. package/src/fonts/OFL.txt +93 -0
  68. package/src/fonts/README.md +3 -0
  69. package/src/fonts/bmFont.d.ts +58 -0
  70. package/src/fonts/bmFontManager.js +357 -0
  71. package/src/fonts/bmFontMetrics.js +108 -0
  72. package/src/genome/genome.js +305 -0
  73. package/src/genome/genome.test.js +152 -0
  74. package/src/genome/genomeStore.js +54 -0
  75. package/src/genome/locusFormat.js +31 -0
  76. package/src/genome/scaleIndex.js +199 -0
  77. package/src/genome/scaleIndex.test.js +61 -0
  78. package/src/genome/scaleLocus.js +112 -0
  79. package/src/genome/scaleLocus.test.js +3 -0
  80. package/src/genomeSpy.js +753 -0
  81. package/src/gl/arrayBuilder.js +199 -0
  82. package/src/gl/dataToVertices.js +621 -0
  83. package/src/gl/includes/common.glsl +63 -0
  84. package/src/gl/includes/fp64-arithmetic.glsl +187 -0
  85. package/src/gl/includes/fp64-utils.js +132 -0
  86. package/src/gl/includes/picking.fragment.glsl +3 -0
  87. package/src/gl/includes/picking.vertex.glsl +29 -0
  88. package/src/gl/includes/sampleFacet.glsl +107 -0
  89. package/src/gl/includes/scales.glsl +79 -0
  90. package/src/gl/includes/scales_fp64.glsl +30 -0
  91. package/src/gl/link.fragment.glsl +18 -0
  92. package/src/gl/link.vertex.glsl +111 -0
  93. package/src/gl/point.fragment.glsl +123 -0
  94. package/src/gl/point.vertex.glsl +128 -0
  95. package/src/gl/rect.fragment.glsl +51 -0
  96. package/src/gl/rect.vertex.glsl +114 -0
  97. package/src/gl/rule.fragment.glsl +52 -0
  98. package/src/gl/rule.vertex.glsl +89 -0
  99. package/src/gl/text.fragment.glsl +31 -0
  100. package/src/gl/text.vertex.glsl +246 -0
  101. package/src/gl/webGLHelper.js +490 -0
  102. package/src/img/bowtie.svg +1 -0
  103. package/src/img/genomespy-favicon.svg +34 -0
  104. package/src/index.html +11 -0
  105. package/src/index.js +151 -0
  106. package/src/marks/link.js +189 -0
  107. package/src/marks/mark.js +867 -0
  108. package/src/marks/markUtils.js +109 -0
  109. package/src/marks/pointMark.js +279 -0
  110. package/src/marks/rectMark.js +236 -0
  111. package/src/marks/rule.js +231 -0
  112. package/src/marks/text.js +274 -0
  113. package/src/options.d.ts +9 -0
  114. package/src/scale/colorUtils.js +184 -0
  115. package/src/scale/glslScaleGenerator.js +462 -0
  116. package/src/scale/scale.js +441 -0
  117. package/src/scale/scale.test.js +323 -0
  118. package/src/scale/ticks.js +198 -0
  119. package/src/scale/ticks.test.js +39 -0
  120. package/src/singlePageApp.js +13 -0
  121. package/src/spec/axis.d.ts +296 -0
  122. package/src/spec/channel.d.ts +127 -0
  123. package/src/spec/data.d.ts +185 -0
  124. package/src/spec/font.d.ts +15 -0
  125. package/src/spec/genome.d.ts +35 -0
  126. package/src/spec/mark.d.ts +432 -0
  127. package/src/spec/root.d.ts +22 -0
  128. package/src/spec/scale.d.ts +265 -0
  129. package/src/spec/tooltip.d.ts +9 -0
  130. package/src/spec/transform.d.ts +479 -0
  131. package/src/spec/view.d.ts +215 -0
  132. package/src/styles/genome-spy.scss +153 -0
  133. package/src/tooltip/dataTooltipHandler.js +59 -0
  134. package/src/tooltip/refseqGeneTooltipHandler.js +77 -0
  135. package/src/tooltip/tooltipHandler.ts +12 -0
  136. package/src/types/filetypes.d.ts +4 -0
  137. package/src/types/flatqueue.d.ts +53 -0
  138. package/src/types/glsl.d.ts +4 -0
  139. package/src/types/object.d.ts +21 -0
  140. package/src/types/vega-scale.d.ts +60 -0
  141. package/src/utils/animator.js +83 -0
  142. package/src/utils/arrayUtils.js +55 -0
  143. package/src/utils/binnedRangeIndex.js +83 -0
  144. package/src/utils/clamp.js +8 -0
  145. package/src/utils/cloner.js +32 -0
  146. package/src/utils/cloner.test.js +23 -0
  147. package/src/utils/coalesce.js +11 -0
  148. package/src/utils/coalesce.test.js +15 -0
  149. package/src/utils/concatIterables.js +26 -0
  150. package/src/utils/concatIterables.test.js +7 -0
  151. package/src/utils/debounce.js +37 -0
  152. package/src/utils/domainArray.js +224 -0
  153. package/src/utils/domainArray.test.js +129 -0
  154. package/src/utils/eerp.js +13 -0
  155. package/src/utils/expression.js +32 -0
  156. package/src/utils/field.js +28 -0
  157. package/src/utils/fisheye.js +60 -0
  158. package/src/utils/formatObject.js +31 -0
  159. package/src/utils/html.js +23 -0
  160. package/src/utils/html.test.js +13 -0
  161. package/src/utils/indexer.js +43 -0
  162. package/src/utils/indexer.test.js +46 -0
  163. package/src/utils/inertia.js +124 -0
  164. package/src/utils/interactionEvent.js +33 -0
  165. package/src/utils/iterateNestedMaps.js +21 -0
  166. package/src/utils/iterateNestedMaps.test.js +32 -0
  167. package/src/utils/kWayMerge.js +42 -0
  168. package/src/utils/kWayMerge.test.js +25 -0
  169. package/src/utils/layout/flexLayout.js +336 -0
  170. package/src/utils/layout/flexLayout.test.js +296 -0
  171. package/src/utils/layout/padding.js +107 -0
  172. package/src/utils/layout/point.js +23 -0
  173. package/src/utils/layout/rectangle.js +282 -0
  174. package/src/utils/layout/rectangle.test.js +171 -0
  175. package/src/utils/mergeObjects.js +99 -0
  176. package/src/utils/mergeObjects.test.js +41 -0
  177. package/src/utils/numberExtractor.js +24 -0
  178. package/src/utils/numberExtractor.test.js +5 -0
  179. package/src/utils/point.js +14 -0
  180. package/src/utils/propertyCacher.js +70 -0
  181. package/src/utils/propertyCacher.test.js +84 -0
  182. package/src/utils/propertyCoalescer.js +37 -0
  183. package/src/utils/propertyCoalescer.test.js +21 -0
  184. package/src/utils/reservationMap.js +103 -0
  185. package/src/utils/reservationMap.test.js +19 -0
  186. package/src/utils/scaleNull.js +19 -0
  187. package/src/utils/setOperations.js +75 -0
  188. package/src/utils/smoothstep.js +10 -0
  189. package/src/utils/throttle.js +34 -0
  190. package/src/utils/topK.js +76 -0
  191. package/src/utils/topK.test.js +63 -0
  192. package/src/utils/transition.js +74 -0
  193. package/src/utils/ui/tooltip.js +189 -0
  194. package/src/utils/url.js +22 -0
  195. package/src/utils/variableTools.js +24 -0
  196. package/src/utils/variableTools.test.js +12 -0
  197. package/src/view/axisResolution.js +135 -0
  198. package/src/view/axisResolution.test.js +200 -0
  199. package/src/view/axisView.js +746 -0
  200. package/src/view/channel.js +5 -0
  201. package/src/view/concatView.js +296 -0
  202. package/src/view/containerView.js +141 -0
  203. package/src/view/decoratorView.js +510 -0
  204. package/src/view/facetView.js +488 -0
  205. package/src/view/flowBuilder.js +362 -0
  206. package/src/view/flowBuilder.test.js +124 -0
  207. package/src/view/importView.js +19 -0
  208. package/src/view/layerView.js +60 -0
  209. package/src/view/rendering.d.ts +44 -0
  210. package/src/view/renderingContext/compositeViewRenderingContext.js +51 -0
  211. package/src/view/renderingContext/deferredViewRenderingContext.js +174 -0
  212. package/src/view/renderingContext/layoutRecorderViewRenderingContext.js +128 -0
  213. package/src/view/renderingContext/simpleViewRenderingContext.js +62 -0
  214. package/src/view/renderingContext/svgViewRenderingContext.js +121 -0
  215. package/src/view/renderingContext/viewRenderingContext.js +41 -0
  216. package/src/view/scaleResolution.js +756 -0
  217. package/src/view/scaleResolution.test.js +571 -0
  218. package/src/view/scaleResolutionApi.d.ts +40 -0
  219. package/src/view/testUtils.js +48 -0
  220. package/src/view/unitView.js +368 -0
  221. package/src/view/view.js +589 -0
  222. package/src/view/view.test.js +213 -0
  223. package/src/view/viewContext.d.ts +57 -0
  224. package/src/view/viewFactory.js +179 -0
  225. package/src/view/viewFactory.test.js +16 -0
  226. package/src/view/viewUtils.js +420 -0
@@ -0,0 +1,420 @@
1
+ import { isObject, isString } from "vega-util";
2
+ import { loader as vegaLoader } from "vega-loader";
3
+
4
+ import UnitView from "./unitView";
5
+ import ImportView from "./importView";
6
+ import LayerView from "./layerView";
7
+ import DecoratorView from "./decoratorView";
8
+ // eslint-disable-next-line no-unused-vars
9
+ import View, { VISIT_SKIP, VISIT_STOP } from "./view";
10
+ import { buildDataFlow } from "./flowBuilder";
11
+ import { optimizeDataFlow } from "../data/flowOptimizer";
12
+ import {
13
+ isFieldDef,
14
+ isValueDef,
15
+ primaryPositionalChannels,
16
+ } from "../encoder/encoder";
17
+ import ContainerView from "./containerView";
18
+ import { peek } from "../utils/arrayUtils";
19
+ import { rollup } from "d3-array";
20
+
21
+ /**
22
+ * @typedef {import("./viewContext").default} ViewContext
23
+ * @typedef {import("../spec/mark").MarkConfig} MarkConfig
24
+ * @typedef {import("../spec/channel").ChannelDef} ChannelDef
25
+ * @typedef {import("../spec/view").ContainerSpec} ContainerSpec
26
+ * @typedef {import("../spec/view").ViewSpec} ViewSpec
27
+ * @typedef {import("../spec/view").LayerSpec} LayerSpec
28
+ * @typedef {import("../spec/view").FacetSpec} FacetSpec
29
+ * @typedef {import("../spec/view").SampleSpec} SampleSpec
30
+ * @typedef {import("../spec/view").UnitSpec} UnitSpec
31
+ * @typedef {import("../spec/view").VConcatSpec} VConcatSpec
32
+ * @typedef {import("../spec/view").HConcatSpec} HConcatSpec
33
+ * @typedef {import("../spec/view").ConcatSpec} ConcatSpec
34
+ * @typedef {VConcatSpec | HConcatSpec | ConcatSpec} AnyConcatSpec
35
+ * @typedef {import("../spec/view").ImportSpec} ImportSpec
36
+ * @typedef {import("../spec/view").ImportConfig} ImportConfig
37
+ * @typedef {import("../spec/root").RootSpec} RootSpec
38
+ * @typedef {import("../spec/root").RootConfig} RootConfig
39
+ *
40
+ * @typedef {import("../spec/channel").FacetFieldDef} FacetFieldDef
41
+ * @typedef {import("../spec/view").FacetMapping} FacetMapping
42
+ */
43
+
44
+ /**
45
+ *
46
+ * @param {ChannelDef | FacetMapping} def
47
+ * @returns {spec is FacetFieldDef}
48
+ */
49
+ export function isFacetFieldDef(def) {
50
+ return def && "field" in def && isString(def.field);
51
+ }
52
+
53
+ /**
54
+ *
55
+ * @param {FacetFieldDef | FacetMapping} def
56
+ * @returns {spec is FacetMapping}
57
+ */
58
+ export function isFacetMapping(def) {
59
+ return (
60
+ ("row" in def && isObject(def.row)) ||
61
+ ("column" in def && isObject(def.column))
62
+ );
63
+ }
64
+
65
+ /**
66
+ *
67
+ * @param {object} config
68
+ * @returns {config is ImportConfig}
69
+ */
70
+ export function isImportConfig(config) {
71
+ return "name" in config || "url" in config;
72
+ }
73
+
74
+ /**
75
+ * Returns all marks in the order (DFS) they are rendered
76
+ * @param {View} root
77
+ */
78
+ export function getMarks(root) {
79
+ return getFlattenedViews(root)
80
+ .filter((view) => view instanceof UnitView)
81
+ .map((view) => /** @type {UnitView} */ (view).mark);
82
+ }
83
+
84
+ /**
85
+ * Returns the nodes of the view hierarchy in depth-first order.
86
+ *
87
+ * @param {View} root
88
+ */
89
+ export function getFlattenedViews(root) {
90
+ /** @type {View[]} */
91
+ const views = [];
92
+ root.visit((view) => {
93
+ views.push(view);
94
+ });
95
+ return views;
96
+ }
97
+
98
+ /**
99
+ * @param {View} root
100
+ */
101
+ export function resolveScalesAndAxes(root) {
102
+ root.visit((view) => {
103
+ if (view instanceof UnitView) {
104
+ view.resolve("scale");
105
+ }
106
+ });
107
+
108
+ // Check that each scale resolution has a unique name
109
+ /** @type {Set<string>} */
110
+ const scaleNames = new Set();
111
+ root.visit((view) => {
112
+ for (const resolution of Object.values(view.resolutions.scale)) {
113
+ const name = resolution.name;
114
+ if (name && scaleNames.has(name)) {
115
+ throw new Error(
116
+ `The same scale name "${name}" occurs in multiple scale resolutions!`
117
+ );
118
+ }
119
+ scaleNames.add(name);
120
+ }
121
+ });
122
+
123
+ root.visit((view) => {
124
+ if (view instanceof UnitView) {
125
+ view.resolve("axis");
126
+ }
127
+ });
128
+ root.visit((view) => view.onScalesResolved());
129
+ }
130
+
131
+ /**
132
+ * Gives names to zoomable scales that have been pulled to the root. This allows
133
+ * the zoomed domains to be bookmarked without explicitly specifying the names.
134
+ * This only affects the trivial but common cases, e.g., a genome-browser-like
135
+ * view with a shared x scale.
136
+ *
137
+ * @param {View} root
138
+ */
139
+ export function setImplicitScaleNames(root) {
140
+ for (const channel of primaryPositionalChannels) {
141
+ const resolution = root.getScaleResolution(channel);
142
+ if (resolution && !resolution.name && resolution.isZoomable()) {
143
+ // TODO: Should actually check that the name is not already reserved
144
+ resolution.name = `${channel}_at_root`;
145
+ }
146
+ }
147
+ }
148
+
149
+ /**
150
+ * @param {View} root
151
+ */
152
+ export function addDecorators(root) {
153
+ let newRoot = root; // If the root is wrapped...
154
+
155
+ /** @param {ChannelDef} channelDef */
156
+ const hasDomain = (channelDef) => channelDef && !isValueDef(channelDef);
157
+
158
+ root.visit((view) => {
159
+ if (view instanceof LayerView || view instanceof UnitView) {
160
+ const encoding = view.getEncoding();
161
+ if (
162
+ view instanceof UnitView &&
163
+ !hasDomain(encoding.x) &&
164
+ !hasDomain(encoding.y)
165
+ ) {
166
+ // Don't wrap views that have no positional channels
167
+ // TODO: However, in future, views with borders or backgrounds should be wrapped always
168
+ // TODO: Also, views with "axis: null" need no wrapping.
169
+ // TODO: Handle LayerViews, they may have children with positional domains
170
+ return VISIT_SKIP;
171
+ }
172
+
173
+ const originalParent = view.parent;
174
+ const decorator = new DecoratorView(view.context, originalParent);
175
+ view.parent = decorator;
176
+ decorator.child = view;
177
+ decorator.name = view.name + "_decorator";
178
+
179
+ if (originalParent) {
180
+ if (originalParent instanceof ContainerView) {
181
+ originalParent.replaceChild(view, decorator);
182
+ } else {
183
+ // The situation is likely related to summaries of SampleView and the
184
+ // hierarchy is inconsistent. Let's try to find the SampleView.
185
+
186
+ /** @type {view} */
187
+ let parent;
188
+ root.visit(
189
+ stackifyVisitor((needle, stack) => {
190
+ if (needle === view) {
191
+ parent = peek(stack);
192
+ return VISIT_STOP;
193
+ }
194
+ })
195
+ );
196
+
197
+ if (parent instanceof ContainerView) {
198
+ parent.replaceChild(view, decorator);
199
+ } else {
200
+ throw new Error(
201
+ "Cannot find parent while decorating: " +
202
+ view.getPathString()
203
+ );
204
+ }
205
+ }
206
+ }
207
+
208
+ decorator.resolutions = view.resolutions;
209
+ view.resolutions = { scale: {}, axis: {} };
210
+
211
+ decorator.spec.height = view.spec.height;
212
+ view.spec.height = "container";
213
+
214
+ decorator.spec.width = view.spec.width;
215
+ view.spec.width = "container";
216
+
217
+ decorator.spec.padding = view.spec.padding;
218
+ view.spec.padding = undefined;
219
+
220
+ if (view === root) {
221
+ newRoot = decorator;
222
+ }
223
+
224
+ decorator.initialize();
225
+
226
+ return VISIT_SKIP;
227
+ }
228
+ });
229
+
230
+ return newRoot;
231
+ }
232
+
233
+ /**
234
+ * @param {View} root
235
+ * @param {import("../data/dataFlow").default<View>} [existingFlow] Add data flow
236
+ * graphs to an existing DataFlow object.
237
+ */
238
+ export async function initializeData(root, existingFlow) {
239
+ const flow = buildDataFlow(root, existingFlow);
240
+ optimizeDataFlow(flow);
241
+ flow.initialize();
242
+
243
+ /** @type {Promise<void>[]} */
244
+ const promises = flow.dataSources.map((dataSource) => dataSource.load());
245
+
246
+ await Promise.all(promises);
247
+
248
+ return flow;
249
+ }
250
+
251
+ /**
252
+ *
253
+ * @param {View} view
254
+ */
255
+ export function findEncodedFields(view) {
256
+ /** @type {{view: UnitView, channel: string, field: string, type: string}[]} */
257
+ const fieldInfos = [];
258
+
259
+ view.visit((view) => {
260
+ if (view instanceof UnitView) {
261
+ const encoding = view.getEncoding();
262
+ for (const [channel, def] of Object.entries(encoding)) {
263
+ if (isFieldDef(def)) {
264
+ fieldInfos.push({
265
+ view,
266
+ channel,
267
+ field: def.field,
268
+ type: def.type,
269
+ });
270
+ }
271
+ }
272
+ return VISIT_SKIP; // Skip sample summaries
273
+ }
274
+ });
275
+
276
+ return fieldInfos;
277
+ }
278
+
279
+ /**
280
+ * @param {import("../spec/view").ImportSpec} spec
281
+ * @param {string} baseUrl
282
+ * @param {ViewContext} viewContext
283
+ */
284
+ async function loadExternalViewSpec(spec, baseUrl, viewContext) {
285
+ if (!spec.import.url) {
286
+ throw new Error(
287
+ "Cannot import, not an import spec: " + JSON.stringify(spec)
288
+ );
289
+ }
290
+
291
+ const loader = vegaLoader({ baseURL: baseUrl });
292
+ const url = spec.import.url;
293
+
294
+ const importedSpec = JSON.parse(
295
+ await loader.load(url).catch((e) => {
296
+ throw new Error(
297
+ `Could not load imported view spec: ${url} \nReason: ${e.message}`
298
+ );
299
+ })
300
+ );
301
+
302
+ if (viewContext.isViewSpec(importedSpec)) {
303
+ importedSpec.baseUrl = url.match(/^[^?#]*\//)?.[0];
304
+ return importedSpec;
305
+ } else {
306
+ throw new Error(
307
+ `The imported spec "${url}" is not a view spec: ${JSON.stringify(
308
+ spec
309
+ )}`
310
+ );
311
+ }
312
+ }
313
+
314
+ /**
315
+ * @param {import("./view").default} viewRoot
316
+ */
317
+ export async function processImports(viewRoot) {
318
+ /** @type {ImportView[]} */
319
+ const importViews = [];
320
+
321
+ viewRoot.visit((view) => {
322
+ if (view instanceof ImportView) {
323
+ importViews.push(view);
324
+ return VISIT_SKIP;
325
+ }
326
+ });
327
+
328
+ for (const view of importViews) {
329
+ const context = view.context;
330
+
331
+ // TODO: Parallelize using promises, don't use await
332
+ const loadedSpec = await loadExternalViewSpec(
333
+ view.spec,
334
+ view.getBaseUrl(),
335
+ context
336
+ );
337
+
338
+ // TODO: Let importSpec have a name
339
+ const importedView = context.createView(
340
+ loadedSpec,
341
+ view.parent,
342
+ view.name
343
+ );
344
+ view.parent.replaceChild(view, importedView);
345
+
346
+ // Import recursively
347
+ await processImports(importedView);
348
+ }
349
+ }
350
+
351
+ /**
352
+ * @param {function(View, View[]):void} visitor
353
+ */
354
+ export function stackifyVisitor(visitor) {
355
+ /** @type {View[]} */
356
+ const stack = [];
357
+
358
+ /** @type {import("./view").Visitor} */
359
+ const stackified = (view) => visitor(view, stack);
360
+
361
+ stackified.beforeChildren = (view) => {
362
+ stack.push(view);
363
+ };
364
+
365
+ stackified.afterChildren = (view) => {
366
+ stack.pop();
367
+ };
368
+
369
+ return stackified;
370
+ }
371
+
372
+ /**
373
+ * Finds the descendants having the given name. The root is included in the search.
374
+ *
375
+ * @param {View} root
376
+ * @param {string} name View name
377
+ * @returns {View[]}
378
+ */
379
+ export function findDescendantsByPath(root, name) {
380
+ /** @type {View[]} */
381
+ const descendants = [];
382
+
383
+ root.visit((view) => {
384
+ if (view.name == name) {
385
+ descendants.push(view);
386
+ }
387
+ });
388
+
389
+ return descendants;
390
+ }
391
+
392
+ /**
393
+ *
394
+ * @param {View} root
395
+ */
396
+ export function findUniqueViewNames(root) {
397
+ /** @type {View[]} */
398
+ const descendants = [];
399
+
400
+ root.visit((view) => {
401
+ descendants.push(view);
402
+ });
403
+
404
+ return new Set(
405
+ [
406
+ ...rollup(
407
+ descendants,
408
+ (views) => views.length,
409
+ (view) => view.name
410
+ ),
411
+ ]
412
+ .filter(([name, count]) => count == 1 && name !== undefined)
413
+ .map(([name, count]) => name)
414
+ );
415
+ }
416
+
417
+ /**
418
+ * @param {string} name
419
+ */
420
+ export const isCustomViewName = (name) => !/^(layer|concat)\d+$/.test(name);