react-msaview 3.0.3 → 3.1.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 (47) hide show
  1. package/bundle/index.js +45 -31
  2. package/dist/calculateBlocks.d.ts +12 -0
  3. package/dist/calculateBlocks.js +23 -0
  4. package/dist/calculateBlocks.js.map +1 -0
  5. package/dist/components/ExportSVGDialog.d.ts +6 -0
  6. package/dist/components/ExportSVGDialog.js +26 -0
  7. package/dist/components/ExportSVGDialog.js.map +1 -0
  8. package/dist/components/MSAPanel/MSABlock.js +2 -2
  9. package/dist/components/MSAPanel/MSABlock.js.map +1 -1
  10. package/dist/components/MSAPanel/renderMSABlock.d.ts +4 -1
  11. package/dist/components/MSAPanel/renderMSABlock.js +8 -6
  12. package/dist/components/MSAPanel/renderMSABlock.js.map +1 -1
  13. package/dist/components/MSAPanel/renderMSAMouseover.js +0 -1
  14. package/dist/components/MSAPanel/renderMSAMouseover.js.map +1 -1
  15. package/dist/components/MinimapSVG.d.ts +6 -0
  16. package/dist/components/MinimapSVG.js +25 -0
  17. package/dist/components/MinimapSVG.js.map +1 -0
  18. package/dist/components/TreePanel/TreeCanvasBlock.js +0 -1
  19. package/dist/components/TreePanel/TreeCanvasBlock.js.map +1 -1
  20. package/dist/components/TreePanel/renderTreeCanvas.d.ts +13 -8
  21. package/dist/components/TreePanel/renderTreeCanvas.js +43 -17
  22. package/dist/components/TreePanel/renderTreeCanvas.js.map +1 -1
  23. package/dist/components/ZoomControls.js +15 -14
  24. package/dist/components/ZoomControls.js.map +1 -1
  25. package/dist/model.d.ts +72 -66
  26. package/dist/model.js +81 -70
  27. package/dist/model.js.map +1 -1
  28. package/dist/parsers/ClustalMSA.d.ts +18 -5
  29. package/dist/renderToSvg.d.ts +6 -0
  30. package/dist/renderToSvg.js +62 -0
  31. package/dist/renderToSvg.js.map +1 -0
  32. package/dist/version.d.ts +1 -1
  33. package/dist/version.js +1 -1
  34. package/package.json +4 -1
  35. package/src/calculateBlocks.ts +58 -0
  36. package/src/components/ExportSVGDialog.tsx +61 -0
  37. package/src/components/MSAPanel/MSABlock.tsx +2 -2
  38. package/src/components/MSAPanel/renderMSABlock.ts +14 -6
  39. package/src/components/MSAPanel/renderMSAMouseover.ts +0 -1
  40. package/src/components/MinimapSVG.tsx +57 -0
  41. package/src/components/TreePanel/TreeCanvasBlock.tsx +0 -1
  42. package/src/components/TreePanel/renderTreeCanvas.ts +53 -17
  43. package/src/components/ZoomControls.tsx +21 -13
  44. package/src/declare.d.ts +1 -0
  45. package/src/model.ts +86 -88
  46. package/src/renderToSvg.tsx +102 -0
  47. package/src/version.ts +1 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"renderToSvg.js","sourceRoot":"","sources":["../src/renderToSvg.tsx"],"names":[],"mappings":"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"}
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const version = "3.0.3";
1
+ export declare const version = "3.1.1";
package/dist/version.js CHANGED
@@ -1,2 +1,2 @@
1
- export const version = '3.0.3';
1
+ export const version = '3.1.1';
2
2
  //# sourceMappingURL=version.js.map
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "react-msaview",
3
3
  "author": "Colin",
4
- "version": "3.0.3",
4
+ "version": "3.1.1",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",
7
7
  "files": [
@@ -57,14 +57,17 @@
57
57
  "dependencies": {
58
58
  "@typescript-eslint/eslint-plugin": "^6.21.0",
59
59
  "@typescript-eslint/parser": "^6.21.0",
60
+ "canvas2svg": "^1.0.16",
60
61
  "clustal-js": "^2.0.0",
61
62
  "colord": "^2.9.3",
62
63
  "copy-to-clipboard": "^3.3.1",
63
64
  "d3-array": "^3.2.3",
64
65
  "d3-hierarchy": "^3.1.2",
66
+ "file-saver": "^2.0.5",
65
67
  "normalize-wheel": "^1.0.1",
66
68
  "rbush": "^3.0.1",
67
69
  "stockholm-js": "^1.0.10",
70
+ "svgcanvas": "^2.5.0",
68
71
  "tss-react": "^4.8.3"
69
72
  }
70
73
  }
@@ -0,0 +1,58 @@
1
+ export function blocksY({
2
+ mapHeight,
3
+ blockSize,
4
+ viewportY,
5
+ viewportHeight,
6
+ }: {
7
+ mapHeight: number
8
+ blockSize: number
9
+ viewportY: number
10
+ viewportHeight: number
11
+ }) {
12
+ const clampedViewportY = Math.max(
13
+ 0,
14
+ Math.min(viewportY, mapHeight * blockSize - viewportHeight),
15
+ )
16
+
17
+ // Calculate visible tile ranges
18
+ const minTileY = Math.floor(clampedViewportY / blockSize)
19
+ const maxTileY = Math.floor(
20
+ (clampedViewportY + viewportHeight - 1) / blockSize,
21
+ )
22
+
23
+ // Generate list of visible tiles
24
+ const blocksY = []
25
+ for (let tileY = minTileY; tileY <= maxTileY; tileY++) {
26
+ blocksY.push(tileY * blockSize)
27
+ }
28
+
29
+ return blocksY
30
+ }
31
+
32
+ export function blocksX({
33
+ mapWidth,
34
+ blockSize,
35
+ viewportX,
36
+ viewportWidth,
37
+ }: {
38
+ mapWidth: number
39
+ blockSize: number
40
+ viewportX: number
41
+ viewportWidth: number
42
+ }) {
43
+ const clampedViewportX = Math.max(
44
+ 0,
45
+ Math.min(viewportX, mapWidth - viewportWidth),
46
+ )
47
+
48
+ const minTileX = Math.floor(clampedViewportX / blockSize)
49
+ const maxTileX = Math.floor(
50
+ (clampedViewportX + viewportWidth - 1) / blockSize,
51
+ )
52
+ const blocksX = []
53
+ for (let tileX = minTileX; tileX <= maxTileX; tileX++) {
54
+ blocksX.push(tileX * blockSize)
55
+ }
56
+
57
+ return blocksX
58
+ }
@@ -0,0 +1,61 @@
1
+ import React, { useState } from 'react'
2
+ import { Dialog, ErrorMessage } from '@jbrowse/core/ui'
3
+ import {
4
+ DialogContent,
5
+ DialogActions,
6
+ Button,
7
+ Checkbox,
8
+ FormControlLabel,
9
+ useTheme,
10
+ Typography,
11
+ } from '@mui/material'
12
+ import { MsaViewModel } from '../model'
13
+
14
+ export default function ExportSVGDialog({
15
+ model,
16
+ onClose,
17
+ }: {
18
+ model: MsaViewModel
19
+ onClose: () => void
20
+ }) {
21
+ const [includeMinimap, setIncludeMinimap] = useState(true)
22
+ const [error, setError] = useState<unknown>()
23
+ const theme = useTheme()
24
+ return (
25
+ <Dialog onClose={() => onClose()} open title="Export SVG">
26
+ <DialogContent>
27
+ <Typography>Export SVG of current view</Typography>
28
+ {error ? <ErrorMessage error={error} /> : null}
29
+ <FormControlLabel
30
+ control={
31
+ <Checkbox
32
+ checked={includeMinimap}
33
+ onChange={event => setIncludeMinimap(event.target.checked)}
34
+ />
35
+ }
36
+ label="Include minimap?"
37
+ />
38
+ </DialogContent>
39
+ <DialogActions>
40
+ <Button
41
+ variant="contained"
42
+ color="primary"
43
+ onClick={async () => {
44
+ try {
45
+ await model.exportSVG({ theme, includeMinimap })
46
+ } catch (e) {
47
+ console.error(e)
48
+ setError(e)
49
+ }
50
+ onClose()
51
+ }}
52
+ >
53
+ Submit
54
+ </Button>
55
+ <Button variant="contained" color="secondary" onClick={() => onClose()}>
56
+ Cancel
57
+ </Button>
58
+ </DialogActions>
59
+ </Dialog>
60
+ )
61
+ }
@@ -4,7 +4,7 @@ import { useTheme } from '@mui/material'
4
4
  import { observer } from 'mobx-react'
5
5
 
6
6
  // locals
7
- import { renderBlock } from './renderMSABlock'
7
+ import { renderMSABlock } from './renderMSABlock'
8
8
  import { MsaViewModel } from '../../model'
9
9
  import { colorContrast } from '../../util'
10
10
 
@@ -40,7 +40,7 @@ const MSABlock = observer(function ({
40
40
  return
41
41
  }
42
42
  return autorun(() => {
43
- renderBlock({
43
+ renderMSABlock({
44
44
  ctx,
45
45
  offsetX,
46
46
  offsetY,
@@ -4,18 +4,24 @@ import { getClustalXColor, getPercentIdentityColor } from '../../colorSchemes'
4
4
  import { NodeWithIdsAndLength } from '../../util'
5
5
  import { HierarchyNode } from 'd3-hierarchy'
6
6
 
7
- export function renderBlock({
7
+ export function renderMSABlock({
8
8
  model,
9
9
  offsetX,
10
10
  offsetY,
11
11
  contrastScheme,
12
12
  ctx,
13
+ highResScaleFactorOverride,
14
+ blockSizeXOverride,
15
+ blockSizeYOverride,
13
16
  }: {
14
17
  offsetX: number
15
18
  offsetY: number
16
19
  model: MsaViewModel
17
20
  contrastScheme: Record<string, string>
18
21
  ctx: CanvasRenderingContext2D
22
+ highResScaleFactorOverride?: number
23
+ blockSizeXOverride?: number
24
+ blockSizeYOverride?: number
19
25
  }) {
20
26
  const {
21
27
  hierarchy,
@@ -25,20 +31,22 @@ export function renderBlock({
25
31
  fontSize,
26
32
  highResScaleFactor,
27
33
  } = model
34
+ const k = highResScaleFactorOverride || highResScaleFactor
35
+ const bx = blockSizeXOverride || blockSize
36
+ const by = blockSizeYOverride || blockSize
28
37
  ctx.resetTransform()
29
- ctx.scale(highResScaleFactor, highResScaleFactor)
30
- ctx.clearRect(0, 0, blockSize, blockSize)
38
+ ctx.scale(k, k)
39
+ ctx.clearRect(0, 0, bx, by)
31
40
  ctx.translate(-offsetX, rowHeight / 2 - offsetY)
32
41
  ctx.textAlign = 'center'
33
42
  ctx.font = ctx.font.replace(/\d+px/, `${fontSize}px`)
34
43
 
35
44
  const leaves = hierarchy.leaves()
36
- const b = blockSize
37
45
 
38
46
  const yStart = Math.max(0, Math.floor((offsetY - rowHeight) / rowHeight))
39
- const yEnd = Math.max(0, Math.ceil((offsetY + b + rowHeight) / rowHeight))
47
+ const yEnd = Math.max(0, Math.ceil((offsetY + by + rowHeight) / rowHeight))
40
48
  const xStart = Math.max(0, Math.floor(offsetX / colWidth))
41
- const xEnd = Math.max(0, Math.ceil((offsetX + b) / colWidth))
49
+ const xEnd = Math.max(0, Math.ceil((offsetX + bx) / colWidth))
42
50
  const visibleLeaves = leaves.slice(yStart, yEnd)
43
51
 
44
52
  drawTiles({
@@ -25,7 +25,6 @@ export function renderMouseover({
25
25
  } = model
26
26
  ctx.resetTransform()
27
27
  ctx.clearRect(0, 0, width, height)
28
- console.log({ mouseCol })
29
28
 
30
29
  if (mouseCol !== undefined) {
31
30
  ctx.fillStyle = 'rgba(0,0,0,0.15)'
@@ -0,0 +1,57 @@
1
+ import React from 'react'
2
+ import { observer } from 'mobx-react'
3
+ import { MsaViewModel } from '../model'
4
+
5
+ const MinimapSVG = observer(function ({ model }: { model: MsaViewModel }) {
6
+ const {
7
+ scrollX,
8
+ msaAreaWidth: W,
9
+ minimapHeight: H,
10
+ colWidth,
11
+ numColumns,
12
+ } = model
13
+
14
+ const BAR_HEIGHT = 12
15
+ const H2 = H - 12
16
+
17
+ const unit = W / numColumns / colWidth
18
+ const left = -scrollX
19
+ const right = left + W
20
+ const s = left * unit
21
+ const e = right * unit
22
+ const fill = 'rgba(66, 119, 127, 0.3)'
23
+
24
+ return (
25
+ <>
26
+ <rect
27
+ x={0}
28
+ y={0}
29
+ width={W}
30
+ height={BAR_HEIGHT}
31
+ stroke="#555"
32
+ fill="none"
33
+ />
34
+ <rect
35
+ x={Math.max(0, s)}
36
+ y={0}
37
+ width={e - s}
38
+ height={BAR_HEIGHT}
39
+ fill={fill}
40
+ stroke="#555"
41
+ />
42
+ <g transform={`translate(0 ${BAR_HEIGHT})`}>
43
+ <polygon
44
+ fill={fill}
45
+ points={[
46
+ [e, 0],
47
+ [s, 0],
48
+ [0, H2],
49
+ [W, H2],
50
+ ].toString()}
51
+ />
52
+ </g>
53
+ </>
54
+ )
55
+ })
56
+
57
+ export default MinimapSVG
@@ -158,7 +158,6 @@ const TreeCanvasBlock = observer(function ({
158
158
  }
159
159
 
160
160
  const ret = hoverNameClickMap(event) || hoverBranchClickMap(event)
161
- console.log({ ret })
162
161
  ref.current.style.cursor = ret ? 'pointer' : 'default'
163
162
  setHoverElt(hoverNameClickMap(event))
164
163
  }}
@@ -1,8 +1,8 @@
1
1
  import RBush from 'rbush'
2
+ import { Theme } from '@mui/material'
2
3
 
3
4
  // locals
4
5
  import { MsaViewModel } from '../../model'
5
- import { Theme } from '@mui/material'
6
6
 
7
7
  export const padding = 600
8
8
  const extendBounds = 5
@@ -24,13 +24,16 @@ export function renderTree({
24
24
  ctx,
25
25
  model,
26
26
  theme,
27
+ blockSizeYOverride,
27
28
  }: {
28
29
  offsetY: number
29
30
  ctx: CanvasRenderingContext2D
30
31
  model: MsaViewModel
31
32
  theme: Theme
33
+ blockSizeYOverride?: number
32
34
  }) {
33
35
  const { hierarchy, showBranchLen, blockSize } = model
36
+ const by = blockSizeYOverride || blockSize
34
37
  ctx.strokeStyle = theme.palette.text.primary
35
38
  for (const { source, target } of hierarchy.links()) {
36
39
  const y = showBranchLen ? 'len' : 'y'
@@ -44,7 +47,7 @@ export function renderTree({
44
47
  // 1d line intersection to check if line crosses block at all, this is
45
48
  // an optimization that allows us to skip drawing most tree links
46
49
  // outside the block
47
- if (offsetY + blockSize >= y1 && y2 >= offsetY) {
50
+ if (offsetY + by >= y1 && y2 >= offsetY) {
48
51
  ctx.beginPath()
49
52
  ctx.moveTo(sx, sy)
50
53
  ctx.lineTo(sx, ty)
@@ -59,14 +62,17 @@ export function renderNodeBubbles({
59
62
  clickMap,
60
63
  offsetY,
61
64
  model,
65
+ blockSizeYOverride,
62
66
  }: {
63
67
  ctx: CanvasRenderingContext2D
64
- clickMap: RBush<ClickEntry>
68
+ clickMap?: RBush<ClickEntry>
65
69
  offsetY: number
66
70
  model: MsaViewModel
67
71
  theme: Theme
72
+ blockSizeYOverride?: number
68
73
  }) {
69
74
  const { hierarchy, showBranchLen, collapsed, blockSize } = model
75
+ const by = blockSizeYOverride || blockSize
70
76
  for (const node of hierarchy.descendants()) {
71
77
  const val = showBranchLen ? 'len' : 'y'
72
78
  const {
@@ -80,7 +86,7 @@ export function renderNodeBubbles({
80
86
  if (
81
87
  branchset.length &&
82
88
  y > offsetY - extendBounds &&
83
- y < offsetY + blockSize + extendBounds
89
+ y < offsetY + by + extendBounds
84
90
  ) {
85
91
  ctx.strokeStyle = 'black'
86
92
  ctx.fillStyle = collapsed.includes(id) ? 'black' : 'white'
@@ -89,7 +95,7 @@ export function renderNodeBubbles({
89
95
  ctx.fill()
90
96
  ctx.stroke()
91
97
 
92
- clickMap.insert({
98
+ clickMap?.insert({
93
99
  minX: x - radius,
94
100
  maxX: x - radius + d,
95
101
  minY: y - radius,
@@ -108,12 +114,14 @@ export function renderTreeLabels({
108
114
  offsetY,
109
115
  ctx,
110
116
  clickMap,
117
+ blockSizeYOverride,
111
118
  }: {
112
119
  model: MsaViewModel
113
120
  offsetY: number
114
121
  ctx: CanvasRenderingContext2D
115
- clickMap: RBush<ClickEntry>
122
+ clickMap?: RBush<ClickEntry>
116
123
  theme: Theme
124
+ blockSizeYOverride?: number
117
125
  }) {
118
126
  const {
119
127
  rowHeight,
@@ -128,6 +136,7 @@ export function renderTreeLabels({
128
136
  margin,
129
137
  noTree,
130
138
  } = model
139
+ const by = blockSizeYOverride || blockSize
131
140
  if (labelsAlignRight) {
132
141
  ctx.textAlign = 'right'
133
142
  ctx.setLineDash([1, 3])
@@ -147,7 +156,7 @@ export function renderTreeLabels({
147
156
 
148
157
  const displayName = treeMetadata[name]?.genome || name
149
158
 
150
- if (y > offsetY - extendBounds && y < offsetY + blockSize + extendBounds) {
159
+ if (y > offsetY - extendBounds && y < offsetY + by + extendBounds) {
151
160
  // note: +rowHeight/4 matches with -rowHeight/4 in msa
152
161
  const yp = y + rowHeight / 4
153
162
  const xp = showBranchLen ? len : x
@@ -160,7 +169,7 @@ export function renderTreeLabels({
160
169
 
161
170
  if (!drawTree && !labelsAlignRight) {
162
171
  ctx.fillText(displayName, 0, yp)
163
- clickMap.insert({
172
+ clickMap?.insert({
164
173
  minX: 0,
165
174
  maxX: width,
166
175
  minY: yp - height,
@@ -178,7 +187,7 @@ export function renderTreeLabels({
178
187
  ctx.stroke()
179
188
  }
180
189
  ctx.fillText(displayName, offset, yp)
181
- clickMap.insert({
190
+ clickMap?.insert({
182
191
  minX: treeAreaWidth - margin.left - width,
183
192
  maxX: treeAreaWidth - margin.left,
184
193
  minY: yp - height,
@@ -188,7 +197,7 @@ export function renderTreeLabels({
188
197
  })
189
198
  } else {
190
199
  ctx.fillText(displayName, xp + d, yp)
191
- clickMap.insert({
200
+ clickMap?.insert({
192
201
  minX: xp + d,
193
202
  maxX: xp + d + width,
194
203
  minY: yp - height,
@@ -208,14 +217,18 @@ export function renderTreeCanvas({
208
217
  ctx,
209
218
  offsetY,
210
219
  theme,
220
+ highResScaleFactorOverride,
221
+ blockSizeYOverride,
211
222
  }: {
212
223
  model: MsaViewModel
213
224
  offsetY: number
214
225
  ctx: CanvasRenderingContext2D
215
- clickMap: RBush<ClickEntry>
226
+ clickMap?: RBush<ClickEntry>
216
227
  theme: Theme
228
+ highResScaleFactorOverride?: number
229
+ blockSizeYOverride?: number
217
230
  }) {
218
- clickMap.clear()
231
+ clickMap?.clear()
219
232
  const {
220
233
  noTree,
221
234
  drawTree,
@@ -231,24 +244,47 @@ export function renderTreeCanvas({
231
244
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
232
245
  nref,
233
246
  } = model
247
+ const by = blockSizeYOverride || blockSize
234
248
 
235
249
  ctx.resetTransform()
236
- ctx.scale(highResScaleFactor, highResScaleFactor)
237
- ctx.clearRect(0, 0, treeWidth + padding, blockSize)
250
+ const k = highResScaleFactorOverride || highResScaleFactor
251
+ ctx.scale(k, k)
252
+ ctx.clearRect(0, 0, treeWidth + padding, by)
238
253
  ctx.translate(margin.left, -offsetY)
239
254
 
255
+ // console.log(ctx.font)
240
256
  const font = ctx.font
241
257
  ctx.font = font.replace(/\d+px/, `${fontSize}px`)
242
258
 
243
259
  if (!noTree && drawTree) {
244
- renderTree({ ctx, offsetY, model, theme })
260
+ renderTree({
261
+ ctx,
262
+ offsetY,
263
+ model,
264
+ theme,
265
+ blockSizeYOverride,
266
+ })
245
267
 
246
268
  if (drawNodeBubbles) {
247
- renderNodeBubbles({ ctx, offsetY, clickMap, model, theme })
269
+ renderNodeBubbles({
270
+ ctx,
271
+ offsetY,
272
+ clickMap,
273
+ model,
274
+ theme,
275
+ blockSizeYOverride,
276
+ })
248
277
  }
249
278
  }
250
279
 
251
280
  if (rowHeight >= 5) {
252
- renderTreeLabels({ ctx, offsetY, model, clickMap, theme })
281
+ renderTreeLabels({
282
+ ctx,
283
+ offsetY,
284
+ model,
285
+ clickMap,
286
+ theme,
287
+ blockSizeYOverride,
288
+ })
253
289
  }
254
290
  }
@@ -1,4 +1,4 @@
1
- import React from 'react'
1
+ import React, { Suspense, lazy, useState } from 'react'
2
2
  import { IconButton } from '@mui/material'
3
3
  import { observer } from 'mobx-react'
4
4
  import CascadingMenuButton from '@jbrowse/core/ui/CascadingMenuButton'
@@ -11,12 +11,16 @@ import MoreVert from '@mui/icons-material/MoreVert'
11
11
  import ZoomIn from '@mui/icons-material/ZoomIn'
12
12
  import ZoomOut from '@mui/icons-material/ZoomOut'
13
13
 
14
+ // lazies
15
+ const ExportSVGDialog = lazy(() => import('./ExportSVGDialog'))
16
+
14
17
  const ZoomControls = observer(function ZoomControls({
15
18
  model,
16
19
  }: {
17
20
  model: MsaViewModel
18
21
  }) {
19
22
  const { colWidth, rowHeight } = model
23
+ const [exportSvgDialogOpen, setExportSvgDialogOpen] = useState(false)
20
24
  return (
21
25
  <>
22
26
  <IconButton
@@ -39,27 +43,19 @@ const ZoomControls = observer(function ZoomControls({
39
43
  menuItems={[
40
44
  {
41
45
  label: 'Decrease row height',
42
- onClick: () => {
43
- model.setRowHeight(Math.max(1.5, rowHeight * 0.75))
44
- },
46
+ onClick: () => model.setRowHeight(Math.max(1.5, rowHeight * 0.75)),
45
47
  },
46
48
  {
47
49
  label: 'Increase row height',
48
- onClick: () => {
49
- model.setRowHeight(rowHeight * 1.5)
50
- },
50
+ onClick: () => model.setRowHeight(rowHeight * 1.5),
51
51
  },
52
52
  {
53
53
  label: 'Decrease col width',
54
- onClick: () => {
55
- model.setColWidth(Math.max(1, colWidth * 0.75))
56
- },
54
+ onClick: () => model.setColWidth(Math.max(1, colWidth * 0.75)),
57
55
  },
58
56
  {
59
57
  label: 'Increase col width',
60
- onClick: () => {
61
- model.setColWidth(colWidth * 1.5)
62
- },
58
+ onClick: () => model.setColWidth(colWidth * 1.5),
63
59
  },
64
60
  {
65
61
  label: 'Reset zoom to default',
@@ -68,10 +64,22 @@ const ZoomControls = observer(function ZoomControls({
68
64
  model.setRowHeight(20)
69
65
  },
70
66
  },
67
+ {
68
+ label: 'Export SVG',
69
+ onClick: () => setExportSvgDialogOpen(true),
70
+ },
71
71
  ]}
72
72
  >
73
73
  <MoreVert />
74
74
  </CascadingMenuButton>
75
+ {exportSvgDialogOpen ? (
76
+ <Suspense fallback={null}>
77
+ <ExportSVGDialog
78
+ model={model}
79
+ onClose={() => setExportSvgDialogOpen(false)}
80
+ />
81
+ </Suspense>
82
+ ) : null}
75
83
  </>
76
84
  )
77
85
  })
package/src/declare.d.ts CHANGED
@@ -1 +1,2 @@
1
1
  declare module 'stockholm-js'
2
+ declare module 'svgcanvas'