react-msaview 3.1.1 → 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.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "react-msaview",
3
3
  "author": "Colin",
4
- "version": "3.1.1",
4
+ "version": "3.1.3",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",
7
7
  "files": [
@@ -34,29 +34,7 @@
34
34
  "react": ">=16.8.0",
35
35
  "react-dom": ">=16.8.0"
36
36
  },
37
- "devDependencies": {
38
- "@rollup/plugin-commonjs": "^25.0.4",
39
- "@rollup/plugin-json": "^6.0.0",
40
- "@rollup/plugin-node-resolve": "^15.0.2",
41
- "@rollup/plugin-replace": "^5.0.5",
42
- "@rollup/plugin-terser": "^0.4.1",
43
- "@rollup/plugin-typescript": "^11.1.0",
44
- "@types/color": "^3.0.1",
45
- "@types/d3": "^7.4.0",
46
- "@types/react": "^18.2.55",
47
- "@types/react-dom": "^18.2.19",
48
- "@typescript-eslint/eslint-plugin": "^6.21.0",
49
- "@typescript-eslint/parser": "^6.21.0",
50
- "eslint-plugin-unicorn": "^51.0.1",
51
- "rollup": "^4.9.1",
52
- "rollup-plugin-polyfill-node": "^0.13.0",
53
- "slugify": "^1.6.6",
54
- "tslib": "^2.1.0",
55
- "typescript": "^5.0.4"
56
- },
57
37
  "dependencies": {
58
- "@typescript-eslint/eslint-plugin": "^6.21.0",
59
- "@typescript-eslint/parser": "^6.21.0",
60
38
  "canvas2svg": "^1.0.16",
61
39
  "clustal-js": "^2.0.0",
62
40
  "colord": "^2.9.3",
@@ -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)
@@ -239,20 +239,23 @@ export function renderTreeCanvas({
239
239
  blockSize,
240
240
  fontSize,
241
241
  rowHeight,
242
- // nref has to be kept as a unused var or at least reference to force
243
- // redraw after canvas ref change
244
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
245
242
  nref,
246
243
  } = model
247
244
  const by = blockSizeYOverride || blockSize
248
245
 
249
246
  ctx.resetTransform()
250
- const k = highResScaleFactorOverride || highResScaleFactor
247
+
248
+ // this is a bogus use of nref, it is never less than 0. we just are using it
249
+ // in this statement because otherwise it would be an unused variable, we
250
+ // just need to use nref to indicate a redraw in an autorun when canvas ref
251
+ // is updated and in order to convince bundlers like not to delete unused
252
+ // usage with propertyReadSideEffects
253
+ const k =
254
+ nref < 0 ? -Infinity : highResScaleFactorOverride || highResScaleFactor
251
255
  ctx.scale(k, k)
252
256
  ctx.clearRect(0, 0, treeWidth + padding, by)
253
257
  ctx.translate(margin.left, -offsetY)
254
258
 
255
- // console.log(ctx.font)
256
259
  const font = ctx.font
257
260
  ctx.font = font.replace(/\d+px/, `${fontSize}px`)
258
261
 
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.1'
1
+ export const version = '3.1.3'