@vitessce/config 2.0.3-beta.0 → 2.0.3
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.mjs +9365 -0
- package/{dist → dist-tsc}/index.js +0 -0
- package/package.json +7 -7
- package/src/VitessceConfig.js +502 -0
- package/src/VitessceConfig.test.js +490 -0
- package/src/index.js +1 -0
- package/src/utils.js +48 -0
- package/dist/VitessceConfig.js +0 -468
- package/dist/VitessceConfig.test.js +0 -447
- package/dist/utils.js +0 -47
|
@@ -0,0 +1,490 @@
|
|
|
1
|
+
import { CoordinationType } from '@vitessce/constants-internal';
|
|
2
|
+
import {
|
|
3
|
+
VitessceConfig,
|
|
4
|
+
hconcat,
|
|
5
|
+
vconcat,
|
|
6
|
+
} from './VitessceConfig';
|
|
7
|
+
|
|
8
|
+
describe('src/api/VitessceConfig.js', () => {
|
|
9
|
+
describe('VitessceConfig', () => {
|
|
10
|
+
it('can be instantiated in the old way for backwards compatibility', () => {
|
|
11
|
+
const config = new VitessceConfig('My config');
|
|
12
|
+
|
|
13
|
+
const configJSON = config.toJSON();
|
|
14
|
+
expect(configJSON).toEqual({
|
|
15
|
+
coordinationSpace: {},
|
|
16
|
+
datasets: [],
|
|
17
|
+
initStrategy: 'auto',
|
|
18
|
+
layout: [],
|
|
19
|
+
name: 'My config',
|
|
20
|
+
version: '1.0.7',
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
it('can be instantiated', () => {
|
|
24
|
+
const config = new VitessceConfig({ schemaVersion: '1.0.4', name: 'My config' });
|
|
25
|
+
|
|
26
|
+
const configJSON = config.toJSON();
|
|
27
|
+
expect(configJSON).toEqual({
|
|
28
|
+
coordinationSpace: {},
|
|
29
|
+
datasets: [],
|
|
30
|
+
initStrategy: 'auto',
|
|
31
|
+
layout: [],
|
|
32
|
+
name: 'My config',
|
|
33
|
+
version: '1.0.4',
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
it('can add a dataset', () => {
|
|
37
|
+
const config = new VitessceConfig({ schemaVersion: '1.0.4', name: 'My config' });
|
|
38
|
+
config.addDataset('My dataset');
|
|
39
|
+
|
|
40
|
+
const configJSON = config.toJSON();
|
|
41
|
+
expect(configJSON).toEqual({
|
|
42
|
+
coordinationSpace: {
|
|
43
|
+
dataset: {
|
|
44
|
+
A: 'A',
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
datasets: [{
|
|
48
|
+
name: 'My dataset',
|
|
49
|
+
uid: 'A',
|
|
50
|
+
files: [],
|
|
51
|
+
}],
|
|
52
|
+
initStrategy: 'auto',
|
|
53
|
+
layout: [],
|
|
54
|
+
name: 'My config',
|
|
55
|
+
version: '1.0.4',
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
it('can add a file to a dataset in the old way for backwards compatibility', () => {
|
|
59
|
+
const config = new VitessceConfig({
|
|
60
|
+
schemaVersion: '1.0.4',
|
|
61
|
+
name: 'My config',
|
|
62
|
+
description: 'My config description',
|
|
63
|
+
});
|
|
64
|
+
// Positional arguments.
|
|
65
|
+
config.addDataset('My dataset', 'My dataset description').addFile(
|
|
66
|
+
'http://example.com/cells.json',
|
|
67
|
+
'cells',
|
|
68
|
+
'cells.json',
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
const configJSON = config.toJSON();
|
|
72
|
+
expect(configJSON).toEqual({
|
|
73
|
+
coordinationSpace: {
|
|
74
|
+
dataset: {
|
|
75
|
+
A: 'A',
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
datasets: [{
|
|
79
|
+
name: 'My dataset',
|
|
80
|
+
description: 'My dataset description',
|
|
81
|
+
uid: 'A',
|
|
82
|
+
files: [{
|
|
83
|
+
url: 'http://example.com/cells.json',
|
|
84
|
+
fileType: 'cells.json',
|
|
85
|
+
}],
|
|
86
|
+
}],
|
|
87
|
+
description: 'My config description',
|
|
88
|
+
initStrategy: 'auto',
|
|
89
|
+
layout: [],
|
|
90
|
+
name: 'My config',
|
|
91
|
+
version: '1.0.4',
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
it('can add a file to a dataset', () => {
|
|
95
|
+
const config = new VitessceConfig({
|
|
96
|
+
schemaVersion: '1.0.4',
|
|
97
|
+
name: 'My config',
|
|
98
|
+
description: 'My config description',
|
|
99
|
+
});
|
|
100
|
+
// Named arguments.
|
|
101
|
+
config.addDataset('My dataset', 'My dataset description').addFile({
|
|
102
|
+
url: 'http://example.com/cells.json',
|
|
103
|
+
fileType: 'cells.json',
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
const configJSON = config.toJSON();
|
|
107
|
+
expect(configJSON).toEqual({
|
|
108
|
+
coordinationSpace: {
|
|
109
|
+
dataset: {
|
|
110
|
+
A: 'A',
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
datasets: [{
|
|
114
|
+
name: 'My dataset',
|
|
115
|
+
description: 'My dataset description',
|
|
116
|
+
uid: 'A',
|
|
117
|
+
files: [{
|
|
118
|
+
url: 'http://example.com/cells.json',
|
|
119
|
+
fileType: 'cells.json',
|
|
120
|
+
}],
|
|
121
|
+
}],
|
|
122
|
+
description: 'My config description',
|
|
123
|
+
initStrategy: 'auto',
|
|
124
|
+
layout: [],
|
|
125
|
+
name: 'My config',
|
|
126
|
+
version: '1.0.4',
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it('can add a view', () => {
|
|
131
|
+
const config = new VitessceConfig({
|
|
132
|
+
schemaVersion: '1.0.4',
|
|
133
|
+
name: 'My config',
|
|
134
|
+
});
|
|
135
|
+
const dataset = config.addDataset('My dataset');
|
|
136
|
+
config.addView(dataset, 'description');
|
|
137
|
+
config.addView(dataset, 'scatterplot', { mapping: 'PCA' });
|
|
138
|
+
|
|
139
|
+
const configJSON = config.toJSON();
|
|
140
|
+
expect(configJSON).toEqual({
|
|
141
|
+
coordinationSpace: {
|
|
142
|
+
dataset: {
|
|
143
|
+
A: 'A',
|
|
144
|
+
},
|
|
145
|
+
embeddingType: {
|
|
146
|
+
A: 'PCA',
|
|
147
|
+
},
|
|
148
|
+
},
|
|
149
|
+
datasets: [{
|
|
150
|
+
name: 'My dataset',
|
|
151
|
+
uid: 'A',
|
|
152
|
+
files: [],
|
|
153
|
+
}],
|
|
154
|
+
initStrategy: 'auto',
|
|
155
|
+
layout: [
|
|
156
|
+
{
|
|
157
|
+
component: 'description',
|
|
158
|
+
coordinationScopes: {
|
|
159
|
+
dataset: 'A',
|
|
160
|
+
},
|
|
161
|
+
x: 0,
|
|
162
|
+
y: 0,
|
|
163
|
+
w: 1,
|
|
164
|
+
h: 1,
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
component: 'scatterplot',
|
|
168
|
+
coordinationScopes: {
|
|
169
|
+
dataset: 'A',
|
|
170
|
+
embeddingType: 'A',
|
|
171
|
+
},
|
|
172
|
+
x: 0,
|
|
173
|
+
y: 0,
|
|
174
|
+
w: 1,
|
|
175
|
+
h: 1,
|
|
176
|
+
},
|
|
177
|
+
],
|
|
178
|
+
name: 'My config',
|
|
179
|
+
version: '1.0.4',
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
it('can add a coordination scope', () => {
|
|
183
|
+
const config = new VitessceConfig({
|
|
184
|
+
schemaVersion: '1.0.4',
|
|
185
|
+
name: 'My config',
|
|
186
|
+
});
|
|
187
|
+
const dataset = config.addDataset('My dataset');
|
|
188
|
+
const pca = config.addView(dataset, 'scatterplot', { mapping: 'PCA' });
|
|
189
|
+
const tsne = config.addView(dataset, 'scatterplot', { mapping: 't-SNE' });
|
|
190
|
+
|
|
191
|
+
const [ezScope, etxScope, etyScope] = config.addCoordination(
|
|
192
|
+
CoordinationType.EMBEDDING_ZOOM,
|
|
193
|
+
CoordinationType.EMBEDDING_TARGET_X,
|
|
194
|
+
CoordinationType.EMBEDDING_TARGET_Y,
|
|
195
|
+
);
|
|
196
|
+
pca.useCoordination(ezScope, etxScope, etyScope);
|
|
197
|
+
tsne.useCoordination(ezScope, etxScope, etyScope);
|
|
198
|
+
|
|
199
|
+
ezScope.setValue(10);
|
|
200
|
+
etxScope.setValue(11);
|
|
201
|
+
etyScope.setValue(12);
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
const configJSON = config.toJSON();
|
|
205
|
+
expect(configJSON).toEqual({
|
|
206
|
+
coordinationSpace: {
|
|
207
|
+
dataset: {
|
|
208
|
+
A: 'A',
|
|
209
|
+
},
|
|
210
|
+
embeddingType: {
|
|
211
|
+
A: 'PCA',
|
|
212
|
+
B: 't-SNE',
|
|
213
|
+
},
|
|
214
|
+
embeddingZoom: {
|
|
215
|
+
A: 10,
|
|
216
|
+
},
|
|
217
|
+
embeddingTargetX: {
|
|
218
|
+
A: 11,
|
|
219
|
+
},
|
|
220
|
+
embeddingTargetY: {
|
|
221
|
+
A: 12,
|
|
222
|
+
},
|
|
223
|
+
},
|
|
224
|
+
datasets: [{
|
|
225
|
+
name: 'My dataset',
|
|
226
|
+
uid: 'A',
|
|
227
|
+
files: [],
|
|
228
|
+
}],
|
|
229
|
+
initStrategy: 'auto',
|
|
230
|
+
layout: [
|
|
231
|
+
{
|
|
232
|
+
component: 'scatterplot',
|
|
233
|
+
coordinationScopes: {
|
|
234
|
+
dataset: 'A',
|
|
235
|
+
embeddingType: 'A',
|
|
236
|
+
embeddingTargetX: 'A',
|
|
237
|
+
embeddingTargetY: 'A',
|
|
238
|
+
embeddingZoom: 'A',
|
|
239
|
+
},
|
|
240
|
+
x: 0,
|
|
241
|
+
y: 0,
|
|
242
|
+
w: 1,
|
|
243
|
+
h: 1,
|
|
244
|
+
},
|
|
245
|
+
{
|
|
246
|
+
component: 'scatterplot',
|
|
247
|
+
coordinationScopes: {
|
|
248
|
+
dataset: 'A',
|
|
249
|
+
embeddingType: 'B',
|
|
250
|
+
embeddingTargetX: 'A',
|
|
251
|
+
embeddingTargetY: 'A',
|
|
252
|
+
embeddingZoom: 'A',
|
|
253
|
+
},
|
|
254
|
+
x: 0,
|
|
255
|
+
y: 0,
|
|
256
|
+
w: 1,
|
|
257
|
+
h: 1,
|
|
258
|
+
},
|
|
259
|
+
],
|
|
260
|
+
name: 'My config',
|
|
261
|
+
version: '1.0.4',
|
|
262
|
+
});
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
it('can add a coordination scope using the link views convenience function', () => {
|
|
266
|
+
const config = new VitessceConfig({
|
|
267
|
+
schemaVersion: '1.0.4',
|
|
268
|
+
name: 'My config',
|
|
269
|
+
});
|
|
270
|
+
const dataset = config.addDataset('My dataset');
|
|
271
|
+
const pca = config.addView(dataset, 'scatterplot', { mapping: 'PCA' });
|
|
272
|
+
const tsne = config.addView(dataset, 'scatterplot', { mapping: 't-SNE' });
|
|
273
|
+
|
|
274
|
+
config.linkViews(
|
|
275
|
+
[pca, tsne],
|
|
276
|
+
[
|
|
277
|
+
CoordinationType.EMBEDDING_ZOOM,
|
|
278
|
+
],
|
|
279
|
+
);
|
|
280
|
+
|
|
281
|
+
config.linkViews(
|
|
282
|
+
[pca, tsne],
|
|
283
|
+
[
|
|
284
|
+
CoordinationType.EMBEDDING_TARGET_X,
|
|
285
|
+
CoordinationType.EMBEDDING_TARGET_Y,
|
|
286
|
+
],
|
|
287
|
+
[
|
|
288
|
+
2,
|
|
289
|
+
3,
|
|
290
|
+
],
|
|
291
|
+
);
|
|
292
|
+
|
|
293
|
+
const configJSON = config.toJSON();
|
|
294
|
+
expect(configJSON).toEqual({
|
|
295
|
+
coordinationSpace: {
|
|
296
|
+
dataset: {
|
|
297
|
+
A: 'A',
|
|
298
|
+
},
|
|
299
|
+
embeddingType: {
|
|
300
|
+
A: 'PCA',
|
|
301
|
+
B: 't-SNE',
|
|
302
|
+
},
|
|
303
|
+
embeddingZoom: {
|
|
304
|
+
A: null,
|
|
305
|
+
},
|
|
306
|
+
embeddingTargetX: {
|
|
307
|
+
A: 2,
|
|
308
|
+
},
|
|
309
|
+
embeddingTargetY: {
|
|
310
|
+
A: 3,
|
|
311
|
+
},
|
|
312
|
+
},
|
|
313
|
+
datasets: [{
|
|
314
|
+
name: 'My dataset',
|
|
315
|
+
uid: 'A',
|
|
316
|
+
files: [],
|
|
317
|
+
}],
|
|
318
|
+
initStrategy: 'auto',
|
|
319
|
+
layout: [
|
|
320
|
+
{
|
|
321
|
+
component: 'scatterplot',
|
|
322
|
+
coordinationScopes: {
|
|
323
|
+
dataset: 'A',
|
|
324
|
+
embeddingType: 'A',
|
|
325
|
+
embeddingTargetX: 'A',
|
|
326
|
+
embeddingTargetY: 'A',
|
|
327
|
+
embeddingZoom: 'A',
|
|
328
|
+
},
|
|
329
|
+
x: 0,
|
|
330
|
+
y: 0,
|
|
331
|
+
w: 1,
|
|
332
|
+
h: 1,
|
|
333
|
+
},
|
|
334
|
+
{
|
|
335
|
+
component: 'scatterplot',
|
|
336
|
+
coordinationScopes: {
|
|
337
|
+
dataset: 'A',
|
|
338
|
+
embeddingType: 'B',
|
|
339
|
+
embeddingTargetX: 'A',
|
|
340
|
+
embeddingTargetY: 'A',
|
|
341
|
+
embeddingZoom: 'A',
|
|
342
|
+
},
|
|
343
|
+
x: 0,
|
|
344
|
+
y: 0,
|
|
345
|
+
w: 1,
|
|
346
|
+
h: 1,
|
|
347
|
+
},
|
|
348
|
+
],
|
|
349
|
+
name: 'My config',
|
|
350
|
+
version: '1.0.4',
|
|
351
|
+
});
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
it('can create a layout', () => {
|
|
355
|
+
const config = new VitessceConfig({
|
|
356
|
+
schemaVersion: '1.0.4',
|
|
357
|
+
name: 'My config',
|
|
358
|
+
});
|
|
359
|
+
const dataset = config.addDataset('My dataset');
|
|
360
|
+
const v1 = config.addView(dataset, 'spatial');
|
|
361
|
+
const v2 = config.addView(dataset, 'scatterplot', { mapping: 'PCA' });
|
|
362
|
+
const v3 = config.addView(dataset, 'status');
|
|
363
|
+
|
|
364
|
+
config.layout(hconcat(v1, vconcat(v2, v3)));
|
|
365
|
+
|
|
366
|
+
const configJSON = config.toJSON();
|
|
367
|
+
expect(configJSON).toEqual({
|
|
368
|
+
coordinationSpace: {
|
|
369
|
+
dataset: {
|
|
370
|
+
A: 'A',
|
|
371
|
+
},
|
|
372
|
+
embeddingType: {
|
|
373
|
+
A: 'PCA',
|
|
374
|
+
},
|
|
375
|
+
},
|
|
376
|
+
datasets: [{
|
|
377
|
+
name: 'My dataset',
|
|
378
|
+
uid: 'A',
|
|
379
|
+
files: [],
|
|
380
|
+
}],
|
|
381
|
+
initStrategy: 'auto',
|
|
382
|
+
layout: [
|
|
383
|
+
{
|
|
384
|
+
component: 'spatial',
|
|
385
|
+
coordinationScopes: {
|
|
386
|
+
dataset: 'A',
|
|
387
|
+
},
|
|
388
|
+
x: 0,
|
|
389
|
+
y: 0,
|
|
390
|
+
w: 6,
|
|
391
|
+
h: 12,
|
|
392
|
+
},
|
|
393
|
+
{
|
|
394
|
+
component: 'scatterplot',
|
|
395
|
+
coordinationScopes: {
|
|
396
|
+
dataset: 'A',
|
|
397
|
+
embeddingType: 'A',
|
|
398
|
+
},
|
|
399
|
+
x: 6,
|
|
400
|
+
y: 0,
|
|
401
|
+
w: 6,
|
|
402
|
+
h: 6,
|
|
403
|
+
},
|
|
404
|
+
{
|
|
405
|
+
component: 'status',
|
|
406
|
+
coordinationScopes: {
|
|
407
|
+
dataset: 'A',
|
|
408
|
+
},
|
|
409
|
+
x: 6,
|
|
410
|
+
y: 6,
|
|
411
|
+
w: 6,
|
|
412
|
+
h: 6,
|
|
413
|
+
},
|
|
414
|
+
],
|
|
415
|
+
name: 'My config',
|
|
416
|
+
version: '1.0.4',
|
|
417
|
+
});
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
it('can load a view config from JSON', () => {
|
|
421
|
+
const config = new VitessceConfig({
|
|
422
|
+
schemaVersion: '1.0.4',
|
|
423
|
+
name: 'My config',
|
|
424
|
+
});
|
|
425
|
+
const dataset = config.addDataset('My dataset');
|
|
426
|
+
const v1 = config.addView(dataset, 'spatial');
|
|
427
|
+
const v2 = config.addView(dataset, 'scatterplot', { mapping: 'PCA' });
|
|
428
|
+
const v3 = config.addView(dataset, 'status');
|
|
429
|
+
|
|
430
|
+
config.layout(hconcat(v1, vconcat(v2, v3)));
|
|
431
|
+
|
|
432
|
+
const origConfigJSON = config.toJSON();
|
|
433
|
+
|
|
434
|
+
const loadedConfig = VitessceConfig.fromJSON(origConfigJSON);
|
|
435
|
+
const loadedConfigJSON = loadedConfig.toJSON();
|
|
436
|
+
|
|
437
|
+
expect(loadedConfigJSON).toEqual({
|
|
438
|
+
coordinationSpace: {
|
|
439
|
+
dataset: {
|
|
440
|
+
A: 'A',
|
|
441
|
+
},
|
|
442
|
+
embeddingType: {
|
|
443
|
+
A: 'PCA',
|
|
444
|
+
},
|
|
445
|
+
},
|
|
446
|
+
datasets: [{
|
|
447
|
+
name: 'My dataset',
|
|
448
|
+
uid: 'A',
|
|
449
|
+
files: [],
|
|
450
|
+
}],
|
|
451
|
+
initStrategy: 'auto',
|
|
452
|
+
layout: [
|
|
453
|
+
{
|
|
454
|
+
component: 'spatial',
|
|
455
|
+
coordinationScopes: {
|
|
456
|
+
dataset: 'A',
|
|
457
|
+
},
|
|
458
|
+
x: 0,
|
|
459
|
+
y: 0,
|
|
460
|
+
w: 6,
|
|
461
|
+
h: 12,
|
|
462
|
+
},
|
|
463
|
+
{
|
|
464
|
+
component: 'scatterplot',
|
|
465
|
+
coordinationScopes: {
|
|
466
|
+
dataset: 'A',
|
|
467
|
+
embeddingType: 'A',
|
|
468
|
+
},
|
|
469
|
+
x: 6,
|
|
470
|
+
y: 0,
|
|
471
|
+
w: 6,
|
|
472
|
+
h: 6,
|
|
473
|
+
},
|
|
474
|
+
{
|
|
475
|
+
component: 'status',
|
|
476
|
+
coordinationScopes: {
|
|
477
|
+
dataset: 'A',
|
|
478
|
+
},
|
|
479
|
+
x: 6,
|
|
480
|
+
y: 6,
|
|
481
|
+
w: 6,
|
|
482
|
+
h: 6,
|
|
483
|
+
},
|
|
484
|
+
],
|
|
485
|
+
name: 'My config',
|
|
486
|
+
version: '1.0.4',
|
|
487
|
+
});
|
|
488
|
+
});
|
|
489
|
+
});
|
|
490
|
+
});
|
package/src/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { VitessceConfig, vconcat, hconcat } from './VitessceConfig';
|
package/src/utils.js
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generate a new scope name which does not
|
|
3
|
+
* conflict / overlap with a previous scope name.
|
|
4
|
+
* Really these just need to be unique within the coordination object.
|
|
5
|
+
* So in theory they could be String(Math.random()) or uuidv4() or something.
|
|
6
|
+
* However it may be good to make them more human-readable and memorable
|
|
7
|
+
* since eventually we will want to expose a UI to update the coordination.
|
|
8
|
+
* @param {string[]} prevScopes Previous scope names.
|
|
9
|
+
* @returns {string} The new scope name.
|
|
10
|
+
*/
|
|
11
|
+
export function getNextScope(prevScopes) {
|
|
12
|
+
// Keep an ordered list of valid characters.
|
|
13
|
+
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
|
14
|
+
// Store the value of the next character for each position
|
|
15
|
+
// in the new string.
|
|
16
|
+
// For example, [0] -> "A", [1] -> "B", [0, 1] -> "AB"
|
|
17
|
+
const nextCharIndices = [0];
|
|
18
|
+
|
|
19
|
+
// Generate a new scope name,
|
|
20
|
+
// potentially conflicting with an existing name.
|
|
21
|
+
// Reference: https://stackoverflow.com/a/12504061
|
|
22
|
+
function next() {
|
|
23
|
+
const r = [];
|
|
24
|
+
nextCharIndices.forEach((charIndex) => {
|
|
25
|
+
r.unshift(chars[charIndex]);
|
|
26
|
+
});
|
|
27
|
+
let increment = true;
|
|
28
|
+
for (let i = 0; i < nextCharIndices.length; i++) {
|
|
29
|
+
const val = ++nextCharIndices[i];
|
|
30
|
+
if (val >= chars.length) {
|
|
31
|
+
nextCharIndices[i] = 0;
|
|
32
|
+
} else {
|
|
33
|
+
increment = false;
|
|
34
|
+
break;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
if (increment) {
|
|
38
|
+
nextCharIndices.push(0);
|
|
39
|
+
}
|
|
40
|
+
return r.join('');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
let nextScope;
|
|
44
|
+
do {
|
|
45
|
+
nextScope = next();
|
|
46
|
+
} while (prevScopes.includes(nextScope));
|
|
47
|
+
return nextScope;
|
|
48
|
+
}
|