react-msaview 3.1.2 → 3.1.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.
@@ -1,13 +1,17 @@
1
1
  import React, { useState } from 'react'
2
2
  import { Dialog, ErrorMessage } from '@jbrowse/core/ui'
3
3
  import {
4
- DialogContent,
5
- DialogActions,
6
4
  Button,
7
5
  Checkbox,
6
+ DialogContent,
7
+ DialogActions,
8
8
  FormControlLabel,
9
- useTheme,
9
+ FormControl,
10
+ FormLabel,
11
+ RadioGroup,
12
+ Radio,
10
13
  Typography,
14
+ useTheme,
11
15
  } from '@mui/material'
12
16
  import { MsaViewModel } from '../model'
13
17
 
@@ -19,22 +23,48 @@ export default function ExportSVGDialog({
19
23
  onClose: () => void
20
24
  }) {
21
25
  const [includeMinimap, setIncludeMinimap] = useState(true)
26
+ const [exportType, setExportType] = useState('viewport')
22
27
  const [error, setError] = useState<unknown>()
23
28
  const theme = useTheme()
24
29
  return (
25
30
  <Dialog onClose={() => onClose()} open title="Export SVG">
26
31
  <DialogContent>
27
- <Typography>Export SVG of current view</Typography>
28
32
  {error ? <ErrorMessage error={error} /> : null}
29
- <FormControlLabel
30
- control={
31
- <Checkbox
32
- checked={includeMinimap}
33
- onChange={event => setIncludeMinimap(event.target.checked)}
33
+ <Typography>Settings:</Typography>
34
+ <div>
35
+ <FormControl>
36
+ <FormControlLabel
37
+ control={
38
+ <Checkbox
39
+ checked={includeMinimap}
40
+ onChange={event => setIncludeMinimap(event.target.checked)}
41
+ />
42
+ }
43
+ disabled={exportType === 'entire'}
44
+ label="Include minimap?"
34
45
  />
35
- }
36
- label="Include minimap?"
37
- />
46
+ </FormControl>
47
+ </div>
48
+ <div>
49
+ <FormControl>
50
+ <FormLabel>Export type</FormLabel>
51
+ <RadioGroup
52
+ value={exportType}
53
+ onChange={event => setExportType(event.target.value)}
54
+ >
55
+ <FormControlLabel
56
+ value="entire"
57
+ control={<Radio />}
58
+ label="Entire MSA"
59
+ />
60
+ <FormControlLabel
61
+ value="viewport"
62
+ control={<Radio />}
63
+ label="Current viewport only"
64
+ />
65
+ </RadioGroup>
66
+ </FormControl>
67
+ </div>
38
68
  </DialogContent>
39
69
  <DialogActions>
40
70
  <Button
@@ -42,7 +72,12 @@ export default function ExportSVGDialog({
42
72
  color="primary"
43
73
  onClick={async () => {
44
74
  try {
45
- await model.exportSVG({ theme, includeMinimap })
75
+ await model.exportSVG({
76
+ theme,
77
+ includeMinimap:
78
+ exportType === 'entire' ? false : includeMinimap,
79
+ exportType,
80
+ })
46
81
  } catch (e) {
47
82
  console.error(e)
48
83
  setError(e)
package/src/model.ts CHANGED
@@ -44,13 +44,13 @@ export interface RowDetails {
44
44
  range?: { start: number; end: number }
45
45
  }
46
46
 
47
- interface BasicTrackModel {
47
+ export interface BasicTrackModel {
48
48
  id: string
49
49
  name: string
50
50
  associatedRowName?: string
51
51
  height: number
52
52
  }
53
- interface Structure {
53
+ export interface Structure {
54
54
  pdb: string
55
55
  startPos: number
56
56
  endPos: number
@@ -79,8 +79,9 @@ export interface IBoxTrack {
79
79
  model: BoxTrackModel
80
80
  }
81
81
 
82
- type BasicTrack = IBoxTrack | ITextTrack
83
- type StructureSnap = SnapshotIn<typeof StructureModel>
82
+ export type BasicTrack = IBoxTrack | ITextTrack
83
+
84
+ export type StructureSnap = SnapshotIn<typeof StructureModel>
84
85
 
85
86
  /**
86
87
  * #stateModel MsaView
@@ -1220,7 +1221,11 @@ const model = types
1220
1221
  /**
1221
1222
  * #action
1222
1223
  */
1223
- async exportSVG(opts: { theme: Theme; includeMinimap?: boolean }) {
1224
+ async exportSVG(opts: {
1225
+ theme: Theme
1226
+ includeMinimap?: boolean
1227
+ exportType: string
1228
+ }) {
1224
1229
  const html = await renderToSvg(self as MsaViewModel, opts)
1225
1230
  const blob = new Blob([html], { type: 'image/svg+xml' })
1226
1231
  saveAs(blob, 'image.svg')
@@ -11,23 +11,129 @@ import { renderMSABlock } from './components/MSAPanel/renderMSABlock'
11
11
  import { colorContrast } from './util'
12
12
  import MinimapSVG from './components/MinimapSVG'
13
13
 
14
- // render LGV to SVG
15
14
  export async function renderToSvg(
16
15
  model: MsaViewModel,
17
- opts: { theme: Theme; includeMinimap?: boolean },
16
+ opts: { theme: Theme; includeMinimap?: boolean; exportType: string },
18
17
  ) {
19
18
  await when(() => !!model.initialized)
20
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
21
- const { width, height, scrollX, scrollY, colorScheme, treeAreaWidth } = model
22
- const { theme, includeMinimap } = opts
19
+ const { width, height, scrollX, scrollY } = model
20
+ const { exportType, theme, includeMinimap } = opts
23
21
 
22
+ if (exportType === 'entire') {
23
+ return render({
24
+ width: model.totalWidth + model.treeAreaWidth,
25
+ height: model.totalHeight,
26
+ theme,
27
+ model,
28
+ offsetY: 0,
29
+ offsetX: 0,
30
+ includeMinimap,
31
+ })
32
+ } else if (exportType === 'viewport') {
33
+ return render({
34
+ width,
35
+ height,
36
+ theme,
37
+ model,
38
+ offsetY: scrollY,
39
+ offsetX: -scrollX,
40
+ includeMinimap,
41
+ })
42
+ } else {
43
+ throw new Error('unknown export type')
44
+ }
45
+ }
46
+
47
+ async function render({
48
+ width,
49
+ height,
50
+ offsetX,
51
+ offsetY,
52
+ theme,
53
+ model,
54
+ includeMinimap,
55
+ }: {
56
+ width: number
57
+ height: number
58
+ offsetX: number
59
+ offsetY: number
60
+ theme: Theme
61
+ model: MsaViewModel
62
+ includeMinimap?: boolean
63
+ }) {
24
64
  const { Context } = await import('svgcanvas')
65
+ const Wrapper = includeMinimap ? MinimapWrapper : NullWrapper
66
+
67
+ return renderToStaticMarkup(
68
+ <SvgWrapper width={width} height={height}>
69
+ <Wrapper model={model}>
70
+ <CoreRendering
71
+ Context={Context}
72
+ model={model}
73
+ theme={theme}
74
+ offsetX={offsetX}
75
+ offsetY={offsetY}
76
+ width={width}
77
+ height={height}
78
+ />
79
+ </Wrapper>
80
+ </SvgWrapper>,
81
+ createRoot,
82
+ )
83
+ }
84
+
85
+ // function renderMultiline({ model }: { model: MsaViewModel }) {
86
+ // const { treeAreaWidth, height } = model
87
+ // const clipId = 'tree'
88
+ // return (
89
+ // <Wrapper model={model}>
90
+ // <defs>
91
+ // <clipPath id={clipId}>
92
+ // <rect x={0} y={0} width={treeAreaWidth} height={height} />
93
+ // </clipPath>
94
+ // </defs>
95
+ // <g
96
+ // clipPath={`url(#${clipId})`}
97
+ // /* eslint-disable-next-line react/no-danger */
98
+ // dangerouslySetInnerHTML={{ __html: ctx1.getSvg().innerHTML }}
99
+ // />
100
+ // <g
101
+ // transform={`translate(${treeAreaWidth} 0)`}
102
+ // /* eslint-disable-next-line react/no-danger */
103
+ // dangerouslySetInnerHTML={{ __html: ctx2.getSvg().innerHTML }}
104
+ // />
105
+ // </Wrapper>
106
+ // )
107
+ // }
108
+
109
+ function CoreRendering({
110
+ model,
111
+ theme,
112
+ width,
113
+ height,
114
+ offsetX,
115
+ offsetY,
116
+ Context,
117
+ }: {
118
+ model: MsaViewModel
119
+ theme: Theme
120
+ width: number
121
+ height: number
122
+ offsetX: number
123
+ offsetY: number
124
+ Context: (
125
+ width: number,
126
+ height: number,
127
+ ) => CanvasRenderingContext2D & { getSvg: () => { innerHTML: string } }
128
+ }) {
129
+ const clipId = 'tree'
130
+ const { treeAreaWidth, colorScheme } = model
131
+ const contrastScheme = colorContrast(colorScheme, theme)
25
132
  const ctx1 = Context(width, height)
26
133
  const ctx2 = Context(width, height)
27
- const contrastScheme = colorContrast(colorScheme, theme)
28
134
  renderTreeCanvas({
29
135
  model,
30
- offsetY: scrollY,
136
+ offsetY,
31
137
  ctx: ctx1,
32
138
  theme,
33
139
  blockSizeYOverride: height,
@@ -35,45 +141,32 @@ export async function renderToSvg(
35
141
  })
36
142
  renderMSABlock({
37
143
  model,
38
- offsetY: scrollY,
39
- offsetX: -scrollX,
144
+ offsetY,
145
+ offsetX,
40
146
  contrastScheme,
41
147
  ctx: ctx2,
42
148
  blockSizeXOverride: width - treeAreaWidth,
43
149
  blockSizeYOverride: height,
44
150
  highResScaleFactorOverride: 1,
45
151
  })
46
-
47
- const Wrapper = includeMinimap ? MinimapWrapper : NullWrapper
48
- const clipId = 'tree'
49
- // the xlink namespace is used for rendering <image> tag
50
- return renderToStaticMarkup(
51
- <svg
52
- width={width}
53
- height={height}
54
- xmlns="http://www.w3.org/2000/svg"
55
- xmlnsXlink="http://www.w3.org/1999/xlink"
56
- viewBox={[0, 0, width, height].toString()}
57
- >
58
- <Wrapper model={model}>
59
- <defs>
60
- <clipPath id={clipId}>
61
- <rect x={0} y={0} width={treeAreaWidth} height={height} />
62
- </clipPath>
63
- </defs>
64
- <g
65
- clipPath={`url(#${clipId})`}
66
- /* eslint-disable-next-line react/no-danger */
67
- dangerouslySetInnerHTML={{ __html: ctx1.getSvg().innerHTML }}
68
- />
69
- <g
70
- transform={`translate(${treeAreaWidth} 0)`}
71
- /* eslint-disable-next-line react/no-danger */
72
- dangerouslySetInnerHTML={{ __html: ctx2.getSvg().innerHTML }}
73
- />
74
- </Wrapper>
75
- </svg>,
76
- createRoot,
152
+ return (
153
+ <>
154
+ <defs>
155
+ <clipPath id={clipId}>
156
+ <rect x={0} y={0} width={treeAreaWidth} height={height} />
157
+ </clipPath>
158
+ </defs>
159
+ <g
160
+ clipPath={`url(#${clipId})`}
161
+ /* eslint-disable-next-line react/no-danger */
162
+ dangerouslySetInnerHTML={{ __html: ctx1.getSvg().innerHTML }}
163
+ />
164
+ <g
165
+ transform={`translate(${treeAreaWidth} 0)`}
166
+ /* eslint-disable-next-line react/no-danger */
167
+ dangerouslySetInnerHTML={{ __html: ctx2.getSvg().innerHTML }}
168
+ />
169
+ </>
77
170
  )
78
171
  }
79
172
 
@@ -97,6 +190,28 @@ function MinimapWrapper({
97
190
  )
98
191
  }
99
192
 
193
+ function SvgWrapper({
194
+ width,
195
+ height,
196
+ children,
197
+ }: {
198
+ width: number
199
+ height: number
200
+ children: React.ReactNode
201
+ }) {
202
+ return (
203
+ <svg
204
+ width={width}
205
+ height={height}
206
+ xmlns="http://www.w3.org/2000/svg"
207
+ xmlnsXlink="http://www.w3.org/1999/xlink"
208
+ viewBox={[0, 0, width, height].toString()}
209
+ >
210
+ {children}
211
+ </svg>
212
+ )
213
+ }
214
+
100
215
  function NullWrapper({ children }: { children: React.ReactNode }) {
101
216
  return <>{children}</>
102
217
  }
package/src/version.ts CHANGED
@@ -1 +1 @@
1
- export const version = '3.1.2'
1
+ export const version = '3.1.3'