react-msaview 4.1.1 → 4.3.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 (91) hide show
  1. package/bundle/index.js +104 -213
  2. package/bundle/index.js.LICENSE.txt +173 -0
  3. package/bundle/index.js.map +1 -0
  4. package/dist/components/TextTrack.d.ts +2 -1
  5. package/dist/components/TextTrack.js.map +1 -1
  6. package/dist/components/Track.js.map +1 -1
  7. package/dist/components/header/Header.js +10 -3
  8. package/dist/components/header/Header.js.map +1 -1
  9. package/dist/components/header/HeaderMenu.js +199 -11
  10. package/dist/components/header/HeaderMenu.js.map +1 -1
  11. package/dist/components/header/MultiAlignmentSelector.js +3 -3
  12. package/dist/components/header/MultiAlignmentSelector.js.map +1 -1
  13. package/dist/components/header/{HeaderMenuExtra.d.ts → SettingsMenu.d.ts} +2 -2
  14. package/dist/components/header/SettingsMenu.js +141 -0
  15. package/dist/components/header/SettingsMenu.js.map +1 -0
  16. package/dist/components/header/ZoomControls.d.ts +1 -1
  17. package/dist/components/header/ZoomControls.js +1 -46
  18. package/dist/components/header/ZoomControls.js.map +1 -1
  19. package/dist/components/header/ZoomMenu.d.ts +6 -0
  20. package/dist/components/header/ZoomMenu.js +33 -0
  21. package/dist/components/header/ZoomMenu.js.map +1 -0
  22. package/dist/components/header/ZoomStar.d.ts +6 -0
  23. package/dist/components/header/ZoomStar.js +40 -0
  24. package/dist/components/header/ZoomStar.js.map +1 -0
  25. package/dist/components/tree/renderTreeCanvas.js +6 -3
  26. package/dist/components/tree/renderTreeCanvas.js.map +1 -1
  27. package/dist/flatToTree.d.ts +17 -0
  28. package/dist/flatToTree.js +41 -0
  29. package/dist/flatToTree.js.map +1 -0
  30. package/dist/model.d.ts +21 -28
  31. package/dist/model.js +47 -32
  32. package/dist/model.js.map +1 -1
  33. package/dist/parseAsn1.d.ts +34 -0
  34. package/dist/parseAsn1.js +385 -0
  35. package/dist/parseAsn1.js.map +1 -0
  36. package/dist/parseAsn1.test.d.ts +1 -0
  37. package/dist/parseAsn1.test.js +8 -0
  38. package/dist/parseAsn1.test.js.map +1 -0
  39. package/dist/parsers/ClustalMSA.d.ts +1 -1
  40. package/dist/parsers/ClustalMSA.js.map +1 -1
  41. package/dist/parsers/EmfMSA.d.ts +1 -1
  42. package/dist/parsers/FastaMSA.d.ts +1 -1
  43. package/dist/parsers/StockholmMSA.d.ts +1 -1
  44. package/dist/parsers/StockholmMSA.js.map +1 -1
  45. package/dist/renderToSvg.d.ts +3 -2
  46. package/dist/renderToSvg.js.map +1 -1
  47. package/dist/reparseTree.d.ts +1 -1
  48. package/dist/reparseTree.js +2 -0
  49. package/dist/reparseTree.js.map +1 -1
  50. package/dist/types.d.ts +38 -0
  51. package/dist/types.js +2 -0
  52. package/dist/types.js.map +1 -0
  53. package/dist/util.d.ts +6 -20
  54. package/dist/util.js +19 -0
  55. package/dist/util.js.map +1 -1
  56. package/dist/version.d.ts +1 -1
  57. package/dist/version.js +1 -1
  58. package/dist/webpack.d.ts +5 -0
  59. package/dist/webpack.js +7 -0
  60. package/dist/webpack.js.map +1 -0
  61. package/package.json +4 -3
  62. package/src/__snapshots__/parseAsn1.test.ts.snap +2400 -0
  63. package/src/components/TextTrack.tsx +2 -1
  64. package/src/components/Track.tsx +0 -2
  65. package/src/components/header/Header.tsx +11 -3
  66. package/src/components/header/HeaderMenu.tsx +215 -11
  67. package/src/components/header/MultiAlignmentSelector.tsx +7 -6
  68. package/src/components/header/SettingsMenu.tsx +169 -0
  69. package/src/components/header/ZoomControls.tsx +1 -52
  70. package/src/components/header/ZoomMenu.tsx +42 -0
  71. package/src/components/header/ZoomStar.tsx +74 -0
  72. package/src/components/msa/renderBoxFeatureCanvasBlock.ts +1 -1
  73. package/src/components/msa/renderMSABlock.ts +1 -1
  74. package/src/components/tree/renderTreeCanvas.ts +13 -3
  75. package/src/flatToTree.ts +57 -0
  76. package/src/model.ts +75 -61
  77. package/src/parseAsn1.test.ts +11 -0
  78. package/src/parseAsn1.ts +494 -0
  79. package/src/parsers/ClustalMSA.ts +2 -1
  80. package/src/parsers/EmfMSA.ts +1 -1
  81. package/src/parsers/FastaMSA.ts +1 -1
  82. package/src/parsers/StockholmMSA.ts +4 -1
  83. package/src/renderToSvg.tsx +6 -4
  84. package/src/reparseTree.ts +3 -1
  85. package/src/types.ts +44 -0
  86. package/src/util.ts +26 -22
  87. package/src/version.ts +1 -1
  88. package/src/webpack.ts +9 -0
  89. package/dist/components/header/HeaderMenuExtra.js +0 -122
  90. package/dist/components/header/HeaderMenuExtra.js.map +0 -1
  91. package/src/components/header/HeaderMenuExtra.tsx +0 -135
@@ -0,0 +1,494 @@
1
+ /**
2
+ * NCBI ASN.1 Parser
3
+ * A simple hand-made parser for NCBI ASN.1 format
4
+ * Was too lazy to figure out how ASN.1 actually worked, and use a dedicated
5
+ * ASN.1 parser (you have to generally pre-defined your schema)
6
+ * Written with help of Claude AI
7
+ */
8
+
9
+ // Define types for our ASN structure
10
+ export interface ASNNode {
11
+ id: number
12
+ parent?: number
13
+ features?: ASNFeature[]
14
+ }
15
+
16
+ export interface ASNFeature {
17
+ featureid: number
18
+ value: string
19
+ }
20
+
21
+ export interface ASNDictEntry {
22
+ id: number
23
+ name: string
24
+ }
25
+
26
+ export interface BioTreeContainer {
27
+ fdict: ASNDictEntry[]
28
+ nodes: ASNNode[]
29
+ }
30
+
31
+ // Parse the fdict section
32
+ const remap = {
33
+ $NODE_COLLAPSED: 'collapsed',
34
+ $NODE_COLOR: 'color',
35
+ $LABEL_BG_COLOR: 'color',
36
+ 'seq-id': 'seqId',
37
+ 'seq-title': 'seqTitle',
38
+ 'align-index': 'alignIndex',
39
+ 'accession-nbr': 'accessionNbr',
40
+ 'blast-name': 'blastName',
41
+ 'common-name': 'commonName',
42
+ 'leaf-count': 'leafCount',
43
+ }
44
+ /**
45
+ * Parse NCBI ASN.1 format string into a JavaScript object
46
+ * @param asnString The ASN.1 string to parse
47
+ * @returns Parsed BioTreeContainer object
48
+ */
49
+ export function parseAsn1(
50
+ asnString: string,
51
+ ): { id: number; parent: number; name: string }[] {
52
+ const sections = extractSections(
53
+ asnString
54
+ .replace(/\s+/g, ' ')
55
+ .replace(/\s*{\s*/g, '{')
56
+ .replace(/\s*}\s*/g, '}')
57
+ .replace(/\s*,\s*/g, ',')
58
+ .replace(/\s*::\s*=\s*/g, '::=')
59
+ .replace(/^.*?::=/, ''),
60
+ )
61
+
62
+ const dict = Object.fromEntries(
63
+ parseFdict(sections.fdict!).map(r => [
64
+ r.id,
65
+ remap[r.name as keyof typeof remap] || r.name,
66
+ ]),
67
+ )
68
+
69
+ return parseNodes(sections.nodes!).map(node => {
70
+ const { features, ...rest } = node
71
+ return {
72
+ ...rest,
73
+ ...Object.fromEntries(
74
+ features!.map(f => [dict[f.featureid], f.value] as const),
75
+ ),
76
+ }
77
+ })
78
+ }
79
+
80
+ /**
81
+ * Extract main sections from the ASN.1 string
82
+ * @param asnString The ASN.1 string without type definition
83
+ * @returns Object with extracted sections
84
+ */
85
+ function extractSections(asnString: string): Record<string, string> {
86
+ const sections: Record<string, string> = {}
87
+
88
+ // First, let's clean up the string by removing any leading/trailing whitespace
89
+ const cleanedString = asnString.trim()
90
+
91
+ // Remove the outer braces if they exist
92
+ const contentString =
93
+ cleanedString.startsWith('{') && cleanedString.endsWith('}')
94
+ ? cleanedString.slice(1, -1).trim()
95
+ : cleanedString
96
+
97
+ // Now we'll manually parse the top-level sections
98
+ let currentPos = 0
99
+
100
+ while (currentPos < contentString.length) {
101
+ // Skip whitespace
102
+ while (
103
+ currentPos < contentString.length &&
104
+ /\s/.test(contentString[currentPos]!)
105
+ ) {
106
+ currentPos++
107
+ }
108
+
109
+ if (currentPos >= contentString.length) {
110
+ break
111
+ }
112
+
113
+ // Read section name
114
+ const sectionNameStart = currentPos
115
+ while (
116
+ currentPos < contentString.length &&
117
+ /\w/.test(contentString[currentPos]!)
118
+ ) {
119
+ currentPos++
120
+ }
121
+
122
+ if (
123
+ currentPos >= contentString.length ||
124
+ (contentString[currentPos] !== ' ' && contentString[currentPos] !== '{')
125
+ ) {
126
+ // Not a valid section, skip to next comma or end
127
+ while (
128
+ currentPos < contentString.length &&
129
+ contentString[currentPos] !== ','
130
+ ) {
131
+ currentPos++
132
+ }
133
+ if (currentPos < contentString.length) {
134
+ currentPos++
135
+ } // Skip the comma
136
+ continue
137
+ }
138
+
139
+ const sectionName = contentString.slice(sectionNameStart, currentPos).trim()
140
+
141
+ // Skip whitespace
142
+ while (
143
+ currentPos < contentString.length &&
144
+ /\s/.test(contentString[currentPos]!)
145
+ ) {
146
+ currentPos++
147
+ }
148
+
149
+ if (
150
+ currentPos >= contentString.length ||
151
+ contentString[currentPos] !== '{'
152
+ ) {
153
+ // Not a valid section, skip to next comma or end
154
+ while (
155
+ currentPos < contentString.length &&
156
+ contentString[currentPos] !== ','
157
+ ) {
158
+ currentPos++
159
+ }
160
+ if (currentPos < contentString.length) {
161
+ currentPos++
162
+ } // Skip the comma
163
+ continue
164
+ }
165
+
166
+ // We found an opening brace, now we need to find the matching closing brace
167
+ const sectionContentStart = currentPos + 1
168
+ let braceCount = 1
169
+ currentPos++
170
+
171
+ while (currentPos < contentString.length && braceCount > 0) {
172
+ if (contentString[currentPos] === '{') {
173
+ braceCount++
174
+ } else if (contentString[currentPos] === '}') {
175
+ braceCount--
176
+ }
177
+ currentPos++
178
+ }
179
+
180
+ if (braceCount === 0) {
181
+ // We found the matching closing brace
182
+ const sectionContent = contentString
183
+ .slice(sectionContentStart, currentPos - 1)
184
+ .trim()
185
+ sections[sectionName] = sectionContent
186
+ }
187
+
188
+ // Skip to next comma or end
189
+ while (
190
+ currentPos < contentString.length &&
191
+ contentString[currentPos] !== ','
192
+ ) {
193
+ currentPos++
194
+ }
195
+ if (currentPos < contentString.length) {
196
+ currentPos++
197
+ } // Skip the comma
198
+ }
199
+
200
+ return sections
201
+ }
202
+
203
+ /**
204
+ * Parse the fdict section
205
+ * @param fdictString The fdict section content
206
+ * @returns Array of ASNDictEntry objects
207
+ */
208
+ function parseFdict(fdictString: string): ASNDictEntry[] {
209
+ const entries: ASNDictEntry[] = []
210
+
211
+ // We need to properly handle nested braces
212
+ let currentPos = 0
213
+
214
+ while (currentPos < fdictString.length) {
215
+ // Skip whitespace
216
+ while (
217
+ currentPos < fdictString.length &&
218
+ /\s/.test(fdictString[currentPos]!)
219
+ ) {
220
+ currentPos++
221
+ }
222
+
223
+ if (currentPos >= fdictString.length) {
224
+ break
225
+ }
226
+
227
+ // Look for opening brace
228
+ if (fdictString[currentPos] === '{') {
229
+ const entryContentStart = currentPos + 1
230
+ let braceCount = 1
231
+ currentPos++
232
+
233
+ while (currentPos < fdictString.length && braceCount > 0) {
234
+ if (fdictString[currentPos] === '{') {
235
+ braceCount++
236
+ } else if (fdictString[currentPos] === '}') {
237
+ braceCount--
238
+ }
239
+ currentPos++
240
+ }
241
+
242
+ if (braceCount === 0) {
243
+ // We found the matching closing brace
244
+ const entryContent = fdictString
245
+ .slice(entryContentStart, currentPos - 1)
246
+ .trim()
247
+ const entry = parseDictEntry(entryContent)
248
+ if (entry) {
249
+ entries.push(entry)
250
+ }
251
+ }
252
+ } else {
253
+ // Skip to next opening brace
254
+ while (
255
+ currentPos < fdictString.length &&
256
+ fdictString[currentPos] !== '{'
257
+ ) {
258
+ currentPos++
259
+ }
260
+ }
261
+ }
262
+
263
+ return entries
264
+ }
265
+
266
+ /**
267
+ * Parse a single dictionary entry
268
+ * @param entryString The entry content
269
+ * @returns ASNDictEntry object or null if parsing fails
270
+ */
271
+ function parseDictEntry(entryString: string): ASNDictEntry | null {
272
+ const idMatch = /id\s+(\d+)/.exec(entryString)
273
+ // Handle escaped quotes in strings
274
+ const nameMatch = /name\s+"((?:[^"\\]|\\.)*)"/s.exec(entryString)
275
+
276
+ if (idMatch && nameMatch) {
277
+ // Process escaped characters in the string
278
+ const processedName = nameMatch[1]!
279
+ .replace(/\\"/g, '"')
280
+ .replace(/\\\\/g, '\\')
281
+
282
+ return {
283
+ id: parseInt(idMatch[1]!, 10),
284
+ name: processedName,
285
+ }
286
+ }
287
+
288
+ return null
289
+ }
290
+
291
+ /**
292
+ * Parse the nodes section
293
+ * @param nodesString The nodes section content
294
+ * @returns Array of ASNNode objects
295
+ */
296
+ function parseNodes(nodesString: string): ASNNode[] {
297
+ const nodes: ASNNode[] = []
298
+
299
+ // We need to properly handle nested braces
300
+ let currentPos = 0
301
+
302
+ while (currentPos < nodesString.length) {
303
+ // Skip whitespace
304
+ while (
305
+ currentPos < nodesString.length &&
306
+ /\s/.test(nodesString[currentPos]!)
307
+ ) {
308
+ currentPos++
309
+ }
310
+
311
+ if (currentPos >= nodesString.length) {
312
+ break
313
+ }
314
+
315
+ // Look for opening brace
316
+ if (nodesString[currentPos] === '{') {
317
+ const nodeContentStart = currentPos + 1
318
+ let braceCount = 1
319
+ currentPos++
320
+
321
+ while (currentPos < nodesString.length && braceCount > 0) {
322
+ if (nodesString[currentPos] === '{') {
323
+ braceCount++
324
+ } else if (nodesString[currentPos] === '}') {
325
+ braceCount--
326
+ }
327
+ currentPos++
328
+ }
329
+
330
+ if (braceCount === 0) {
331
+ // We found the matching closing brace
332
+ const nodeContent = nodesString
333
+ .slice(nodeContentStart, currentPos - 1)
334
+ .trim()
335
+ const node = parseNode(nodeContent)
336
+ if (node) {
337
+ nodes.push(node)
338
+ }
339
+ }
340
+ } else {
341
+ // Skip to next opening brace
342
+ while (
343
+ currentPos < nodesString.length &&
344
+ nodesString[currentPos] !== '{'
345
+ ) {
346
+ currentPos++
347
+ }
348
+ }
349
+ }
350
+
351
+ return nodes
352
+ }
353
+
354
+ /**
355
+ * Parse a single node
356
+ * @param nodeString The node content
357
+ * @returns ASNNode object or null if parsing fails
358
+ */
359
+ function parseNode(nodeString: string): ASNNode | null {
360
+ const idMatch = /id\s+(\d+)/.exec(nodeString)
361
+ const parentMatch = /parent\s+(\d+)/.exec(nodeString)
362
+
363
+ if (idMatch) {
364
+ const node: ASNNode = {
365
+ id: parseInt(idMatch[1]!, 10),
366
+ }
367
+
368
+ if (parentMatch) {
369
+ node.parent = parseInt(parentMatch[1]!, 10)
370
+ }
371
+
372
+ // Extract features if present
373
+ // First find the features section
374
+ const featuresIndex = nodeString.indexOf('features')
375
+ if (featuresIndex !== -1) {
376
+ // Find the opening brace after "features"
377
+ const openBraceIndex = nodeString.indexOf('{', featuresIndex)
378
+ if (openBraceIndex !== -1) {
379
+ // Now find the matching closing brace
380
+ let braceCount = 1
381
+ let closeBraceIndex = openBraceIndex + 1
382
+
383
+ while (closeBraceIndex < nodeString.length && braceCount > 0) {
384
+ if (nodeString[closeBraceIndex] === '{') {
385
+ braceCount++
386
+ } else if (nodeString[closeBraceIndex] === '}') {
387
+ braceCount--
388
+ }
389
+ closeBraceIndex++
390
+ }
391
+
392
+ if (braceCount === 0) {
393
+ // We found the matching closing brace
394
+ const featuresContent = nodeString
395
+ .slice(openBraceIndex + 1, closeBraceIndex - 1)
396
+ .trim()
397
+ node.features = parseFeatures(featuresContent)
398
+ }
399
+ }
400
+ }
401
+
402
+ return node
403
+ }
404
+
405
+ return null
406
+ }
407
+
408
+ /**
409
+ * Parse features section of a node
410
+ * @param featuresString The features section content
411
+ * @returns Array of ASNFeature objects
412
+ */
413
+ function parseFeatures(featuresString: string): ASNFeature[] {
414
+ const features: ASNFeature[] = []
415
+
416
+ // We need to properly handle nested braces
417
+ let currentPos = 0
418
+
419
+ while (currentPos < featuresString.length) {
420
+ // Skip whitespace
421
+ while (
422
+ currentPos < featuresString.length &&
423
+ /\s/.test(featuresString[currentPos]!)
424
+ ) {
425
+ currentPos++
426
+ }
427
+
428
+ if (currentPos >= featuresString.length) {
429
+ break
430
+ }
431
+
432
+ // Look for opening brace
433
+ if (featuresString[currentPos] === '{') {
434
+ const featureContentStart = currentPos + 1
435
+ let braceCount = 1
436
+ currentPos++
437
+
438
+ while (currentPos < featuresString.length && braceCount > 0) {
439
+ if (featuresString[currentPos] === '{') {
440
+ braceCount++
441
+ } else if (featuresString[currentPos] === '}') {
442
+ braceCount--
443
+ }
444
+ currentPos++
445
+ }
446
+
447
+ if (braceCount === 0) {
448
+ // We found the matching closing brace
449
+ const featureContent = featuresString
450
+ .slice(featureContentStart, currentPos - 1)
451
+ .trim()
452
+ const feature = parseFeature(featureContent)
453
+ if (feature) {
454
+ features.push(feature)
455
+ }
456
+ }
457
+ } else {
458
+ // Skip to next opening brace
459
+ while (
460
+ currentPos < featuresString.length &&
461
+ featuresString[currentPos] !== '{'
462
+ ) {
463
+ currentPos++
464
+ }
465
+ }
466
+ }
467
+
468
+ return features
469
+ }
470
+
471
+ /**
472
+ * Parse a single feature
473
+ * @param featureString The feature content
474
+ * @returns ASNFeature object or null if parsing fails
475
+ */
476
+ function parseFeature(featureString: string): ASNFeature | null {
477
+ const featureidMatch = /featureid\s+(\d+)/.exec(featureString)
478
+ // Handle escaped quotes in strings
479
+ const valueMatch = /value\s+"((?:[^"\\]|\\.)*)"/s.exec(featureString)
480
+
481
+ if (featureidMatch && valueMatch) {
482
+ // Process escaped characters in the string
483
+ const processedValue = valueMatch[1]!
484
+ .replace(/\\"/g, '"')
485
+ .replace(/\\\\/g, '\\')
486
+
487
+ return {
488
+ featureid: parseInt(featureidMatch[1]!, 10),
489
+ value: processedValue,
490
+ }
491
+ }
492
+
493
+ return null
494
+ }
@@ -1,6 +1,7 @@
1
1
  import { parse } from 'clustal-js'
2
2
 
3
- import type { NodeWithIds } from '../util'
3
+ import type { NodeWithIds } from '../types'
4
+
4
5
  export default class ClustalMSA {
5
6
  private MSA: ReturnType<typeof parse>
6
7
 
@@ -1,6 +1,6 @@
1
1
  import { parseEmfAln } from 'emf-js'
2
2
 
3
- import type { NodeWithIds } from '../util'
3
+ import type { NodeWithIds } from '../types'
4
4
 
5
5
  export default class EmfMSA {
6
6
  private MSA: ReturnType<typeof parseEmfAln>
@@ -1,4 +1,4 @@
1
- import type { NodeWithIds } from '../util'
1
+ import type { NodeWithIds } from '../types'
2
2
 
3
3
  function parseSmallFasta(text: string) {
4
4
  return text
@@ -1,7 +1,10 @@
1
1
  import Stockholm from 'stockholm-js'
2
2
 
3
3
  import parseNewick from '../parseNewick'
4
- import { type NodeWithIds, generateNodeIds } from '../util'
4
+ import { generateNodeIds } from '../util'
5
+
6
+ import type { NodeWithIds } from '../types'
7
+
5
8
  interface StockholmEntry {
6
9
  gf: {
7
10
  DE?: string[]
@@ -13,10 +13,12 @@ import { colorContrast } from './util'
13
13
  import type { MsaViewModel } from './model'
14
14
  import type { Theme } from '@mui/material'
15
15
 
16
- export async function renderToSvg(
17
- model: MsaViewModel,
18
- opts: { theme: Theme; includeMinimap?: boolean; exportType: string },
19
- ) {
16
+ export interface ExportSvgOptions {
17
+ theme: Theme
18
+ includeMinimap?: boolean
19
+ exportType: string
20
+ }
21
+ export async function renderToSvg(model: MsaViewModel, opts: ExportSvgOptions) {
20
22
  await when(() => !!model.dataInitialized)
21
23
  const { width, height, scrollX, scrollY } = model
22
24
  const { exportType, theme, includeMinimap } = opts
@@ -1,5 +1,7 @@
1
- import type { NodeWithIds } from './util'
1
+ import type { NodeWithIds } from './types'
2
2
 
3
+ // this reparse routine helps to allow the app to hide/collapse a single
4
+ // leafnode
3
5
  export function reparseTree(tree: NodeWithIds): NodeWithIds {
4
6
  return {
5
7
  ...tree,
package/src/types.ts ADDED
@@ -0,0 +1,44 @@
1
+ export interface Accession {
2
+ accession: string
3
+ name: string
4
+ description: string
5
+ }
6
+ export interface BasicTrackModel {
7
+ id: string
8
+ name: string
9
+ associatedRowName?: string
10
+ height: number
11
+ }
12
+
13
+ export interface TextTrackModel extends BasicTrackModel {
14
+ customColorScheme?: Record<string, string>
15
+ data: string
16
+ }
17
+
18
+ export interface ITextTrack {
19
+ ReactComponent: React.FC<any>
20
+ model: TextTrackModel
21
+ }
22
+
23
+ export type BasicTrack = ITextTrack
24
+
25
+ export interface Node {
26
+ children?: Node[]
27
+ name?: string
28
+ }
29
+
30
+ export interface NodeWithIds {
31
+ id: string
32
+ name: string
33
+ children: NodeWithIds[]
34
+ length?: number
35
+ noTree?: boolean
36
+ }
37
+
38
+ export interface NodeWithIdsAndLength {
39
+ id: string
40
+ name: string
41
+ children: NodeWithIdsAndLength[]
42
+ noTree?: boolean
43
+ length: number
44
+ }
package/src/util.ts CHANGED
@@ -2,6 +2,7 @@ import { colord, extend } from 'colord'
2
2
  import namesPlugin from 'colord/plugins/names'
3
3
  import { max } from 'd3-array'
4
4
 
5
+ import type { Node, NodeWithIds } from './types'
5
6
  import type { Theme } from '@mui/material'
6
7
  import type { HierarchyNode } from 'd3-hierarchy'
7
8
 
@@ -14,28 +15,6 @@ export function transform<T>(
14
15
  return Object.fromEntries(Object.entries(obj).map(cb))
15
16
  }
16
17
 
17
- interface Node {
18
- children?: Node[]
19
- name?: string
20
- [key: string]: unknown
21
- }
22
-
23
- export interface NodeWithIds {
24
- id: string
25
- name: string
26
- children: NodeWithIds[]
27
- length?: number
28
- noTree?: boolean
29
- }
30
-
31
- export interface NodeWithIdsAndLength {
32
- id: string
33
- name: string
34
- children: NodeWithIdsAndLength[]
35
- noTree?: boolean
36
- length: number
37
- }
38
-
39
18
  export function generateNodeIds(
40
19
  tree: Node,
41
20
  parent = 'node',
@@ -118,3 +97,28 @@ export function clamp(min: number, num: number, max: number) {
118
97
  export function len(a: { end: number; start: number }) {
119
98
  return a.end - a.start
120
99
  }
100
+
101
+ export function localStorageGetItem(item: string) {
102
+ return typeof localStorage !== 'undefined'
103
+ ? localStorage.getItem(item)
104
+ : undefined
105
+ }
106
+
107
+ export function localStorageGetBoolean(key: string, defaultVal: boolean) {
108
+ return Boolean(
109
+ JSON.parse(localStorageGetItem(key) || JSON.stringify(defaultVal)),
110
+ )
111
+ }
112
+
113
+ export function localStorageSetItem(str: string, item: string) {
114
+ if (typeof localStorage !== 'undefined') {
115
+ localStorage.setItem(str, item)
116
+ }
117
+ }
118
+ export function localStorageSetBoolean(key: string, value: boolean) {
119
+ localStorageSetItem(key, JSON.stringify(value))
120
+ }
121
+
122
+ export function isGzip(buf: Uint8Array) {
123
+ return buf[0] === 31 && buf[1] === 139 && buf[2] === 8
124
+ }
package/src/version.ts CHANGED
@@ -1 +1 @@
1
- export const version = '4.1.1'
1
+ export const version = '4.3.0'
package/src/webpack.ts ADDED
@@ -0,0 +1,9 @@
1
+ import * as React from 'react'
2
+
3
+ export { default as MSAView } from './components/Loading'
4
+ export { type MsaViewModel, default as MSAModelF } from './model'
5
+
6
+ export * from 'react-dom/client'
7
+
8
+ // eslint-disable-next-line unicorn/prefer-export-from
9
+ export { React }