react-msaview 2.0.0 → 2.1.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 (148) hide show
  1. package/bundle/index.js +35 -33
  2. package/dist/UniprotTrack.js +6 -5
  3. package/dist/UniprotTrack.js.map +1 -1
  4. package/dist/colorSchemes.d.ts +3 -9
  5. package/dist/colorSchemes.js +4 -4
  6. package/dist/colorSchemes.js.map +1 -1
  7. package/dist/components/BoxTrack.d.ts +4 -3
  8. package/dist/components/BoxTrack.js +6 -137
  9. package/dist/components/BoxTrack.js.map +1 -1
  10. package/dist/components/BoxTrackBlock.d.ts +8 -0
  11. package/dist/components/BoxTrackBlock.js +136 -0
  12. package/dist/components/BoxTrackBlock.js.map +1 -0
  13. package/dist/components/Header.d.ts +2 -1
  14. package/dist/components/Header.js +29 -27
  15. package/dist/components/Header.js.map +1 -1
  16. package/dist/components/ImportForm.d.ts +2 -1
  17. package/dist/components/MSABlock.d.ts +8 -0
  18. package/dist/components/MSABlock.js +103 -0
  19. package/dist/components/MSABlock.js.map +1 -0
  20. package/dist/components/MSACanvas.d.ts +2 -1
  21. package/dist/components/MSACanvas.js +3 -101
  22. package/dist/components/MSACanvas.js.map +1 -1
  23. package/dist/components/MSAMouseoverCanvas.d.ts +6 -0
  24. package/dist/components/MSAMouseoverCanvas.js +52 -0
  25. package/dist/components/MSAMouseoverCanvas.js.map +1 -0
  26. package/dist/components/MSAView.d.ts +2 -1
  27. package/dist/components/MSAView.js +10 -36
  28. package/dist/components/MSAView.js.map +1 -1
  29. package/dist/components/MultiAlignmentSelector.d.ts +6 -0
  30. package/dist/components/MultiAlignmentSelector.js +13 -0
  31. package/dist/components/MultiAlignmentSelector.js.map +1 -0
  32. package/dist/components/ResizeHandles.d.ts +3 -2
  33. package/dist/components/Rubberband.d.ts +1 -1
  34. package/dist/components/Rubberband.js +0 -1
  35. package/dist/components/Rubberband.js.map +1 -1
  36. package/dist/components/Ruler.d.ts +2 -1
  37. package/dist/components/Ruler.js +2 -2
  38. package/dist/components/Ruler.js.map +1 -1
  39. package/dist/components/TextTrack.d.ts +2 -1
  40. package/dist/components/Track.d.ts +3 -2
  41. package/dist/components/Track.js +4 -3
  42. package/dist/components/Track.js.map +1 -1
  43. package/dist/components/TreeBranchMenu.d.ts +14 -0
  44. package/dist/components/TreeBranchMenu.js +26 -0
  45. package/dist/components/TreeBranchMenu.js.map +1 -0
  46. package/dist/components/TreeCanvas.d.ts +2 -1
  47. package/dist/components/TreeCanvas.js +2 -320
  48. package/dist/components/TreeCanvas.js.map +1 -1
  49. package/dist/components/TreeCanvasBlock.d.ts +7 -0
  50. package/dist/components/TreeCanvasBlock.js +252 -0
  51. package/dist/components/TreeCanvasBlock.js.map +1 -0
  52. package/dist/components/TreeMenu.d.ts +12 -0
  53. package/dist/components/TreeMenu.js +56 -0
  54. package/dist/components/TreeMenu.js.map +1 -0
  55. package/dist/components/TreeRuler.d.ts +2 -1
  56. package/dist/components/VerticalGuide.d.ts +2 -1
  57. package/dist/components/ZoomControls.d.ts +6 -0
  58. package/dist/components/ZoomControls.js +58 -0
  59. package/dist/components/ZoomControls.js.map +1 -0
  60. package/dist/components/dialogs/AboutDlg.d.ts +4 -0
  61. package/dist/components/{AboutDlg.js → dialogs/AboutDlg.js} +3 -3
  62. package/dist/components/dialogs/AboutDlg.js.map +1 -0
  63. package/dist/components/{AddTrackDlg.d.ts → dialogs/AddTrackDlg.d.ts} +3 -2
  64. package/dist/components/dialogs/AddTrackDlg.js.map +1 -0
  65. package/dist/components/{AnnotationDlg.d.ts → dialogs/AnnotationDlg.d.ts} +3 -2
  66. package/dist/components/dialogs/AnnotationDlg.js.map +1 -0
  67. package/dist/components/dialogs/DetailsDlg.d.ts +7 -0
  68. package/dist/components/{DetailsDlg.js → dialogs/DetailsDlg.js} +3 -2
  69. package/dist/components/dialogs/DetailsDlg.js.map +1 -0
  70. package/dist/components/{MoreInfoDlg.d.ts → dialogs/MoreInfoDlg.d.ts} +2 -1
  71. package/dist/components/dialogs/MoreInfoDlg.js.map +1 -0
  72. package/dist/components/dialogs/SettingsDlg.d.ts +7 -0
  73. package/dist/components/{SettingsDlg.js → dialogs/SettingsDlg.js} +4 -3
  74. package/dist/components/dialogs/SettingsDlg.js.map +1 -0
  75. package/dist/components/{TrackInfoDlg.d.ts → dialogs/TrackInfoDlg.d.ts} +2 -1
  76. package/dist/components/dialogs/TrackInfoDlg.js.map +1 -0
  77. package/dist/components/dialogs/TracklistDlg.d.ts +7 -0
  78. package/dist/components/{TracklistDlg.js → dialogs/TracklistDlg.js} +2 -2
  79. package/dist/components/dialogs/TracklistDlg.js.map +1 -0
  80. package/dist/components/util.js +0 -1
  81. package/dist/components/util.js.map +1 -1
  82. package/dist/model.d.ts +36 -39
  83. package/dist/model.js +25 -22
  84. package/dist/model.js.map +1 -1
  85. package/dist/parseNewick.js +2 -2
  86. package/dist/parseNewick.js.map +1 -1
  87. package/dist/parsers/FastaMSA.d.ts +1 -3
  88. package/dist/parsers/FastaMSA.js +2 -2
  89. package/dist/parsers/FastaMSA.js.map +1 -1
  90. package/dist/parsers/StockholmMSA.d.ts +3 -5
  91. package/dist/parsers/StockholmMSA.js +3 -3
  92. package/dist/parsers/StockholmMSA.js.map +1 -1
  93. package/dist/util.d.ts +5 -7
  94. package/dist/util.js.map +1 -1
  95. package/dist/version.d.ts +1 -1
  96. package/dist/version.js +1 -1
  97. package/dist/version.js.map +1 -1
  98. package/package.json +9 -9
  99. package/src/UniprotTrack.ts +6 -7
  100. package/src/colorSchemes.ts +7 -7
  101. package/src/components/BoxTrack.tsx +6 -198
  102. package/src/components/BoxTrackBlock.tsx +198 -0
  103. package/src/components/Header.tsx +49 -60
  104. package/src/components/MSABlock.tsx +164 -0
  105. package/src/components/MSACanvas.tsx +3 -158
  106. package/src/components/MSAMouseoverCanvas.tsx +87 -0
  107. package/src/components/MSAView.tsx +19 -63
  108. package/src/components/MultiAlignmentSelector.tsx +33 -0
  109. package/src/components/Rubberband.tsx +0 -1
  110. package/src/components/Ruler.tsx +2 -1
  111. package/src/components/Track.tsx +9 -6
  112. package/src/components/TreeBranchMenu.tsx +67 -0
  113. package/src/components/TreeCanvas.tsx +2 -507
  114. package/src/components/TreeCanvasBlock.tsx +359 -0
  115. package/src/components/TreeMenu.tsx +105 -0
  116. package/src/components/ZoomControls.tsx +78 -0
  117. package/src/components/{AboutDlg.tsx → dialogs/AboutDlg.tsx} +3 -9
  118. package/src/components/{AddTrackDlg.tsx → dialogs/AddTrackDlg.tsx} +1 -1
  119. package/src/components/{AnnotationDlg.tsx → dialogs/AnnotationDlg.tsx} +2 -2
  120. package/src/components/{DetailsDlg.tsx → dialogs/DetailsDlg.tsx} +5 -5
  121. package/src/components/{SettingsDlg.tsx → dialogs/SettingsDlg.tsx} +6 -6
  122. package/src/components/{TracklistDlg.tsx → dialogs/TracklistDlg.tsx} +2 -4
  123. package/src/components/util.ts +0 -1
  124. package/src/declare.d.ts +0 -1
  125. package/src/model.ts +32 -29
  126. package/src/parseNewick.ts +2 -3
  127. package/src/parsers/FastaMSA.ts +3 -3
  128. package/src/parsers/StockholmMSA.ts +5 -5
  129. package/src/util.ts +3 -3
  130. package/src/version.ts +1 -1
  131. package/dist/components/AboutDlg.d.ts +0 -4
  132. package/dist/components/AboutDlg.js.map +0 -1
  133. package/dist/components/AddTrackDlg.js.map +0 -1
  134. package/dist/components/AnnotationDlg.js.map +0 -1
  135. package/dist/components/DetailsDlg.d.ts +0 -7
  136. package/dist/components/DetailsDlg.js.map +0 -1
  137. package/dist/components/MoreInfoDlg.js.map +0 -1
  138. package/dist/components/SettingsDlg.d.ts +0 -7
  139. package/dist/components/SettingsDlg.js.map +0 -1
  140. package/dist/components/TrackInfoDlg.js.map +0 -1
  141. package/dist/components/TracklistDlg.d.ts +0 -7
  142. package/dist/components/TracklistDlg.js.map +0 -1
  143. /package/dist/components/{AddTrackDlg.js → dialogs/AddTrackDlg.js} +0 -0
  144. /package/dist/components/{AnnotationDlg.js → dialogs/AnnotationDlg.js} +0 -0
  145. /package/dist/components/{MoreInfoDlg.js → dialogs/MoreInfoDlg.js} +0 -0
  146. /package/dist/components/{TrackInfoDlg.js → dialogs/TrackInfoDlg.js} +0 -0
  147. /package/src/components/{MoreInfoDlg.tsx → dialogs/MoreInfoDlg.tsx} +0 -0
  148. /package/src/components/{TrackInfoDlg.tsx → dialogs/TrackInfoDlg.tsx} +0 -0
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "react-msaview",
3
3
  "type": "module",
4
4
  "author": "Colin",
5
- "version": "2.0.0",
5
+ "version": "2.1.0",
6
6
  "license": "MIT",
7
7
  "main": "dist/index.js",
8
8
  "files": [
@@ -14,13 +14,13 @@
14
14
  "node": ">=10"
15
15
  },
16
16
  "scripts": {
17
- "lint": "eslint src",
18
17
  "clean": "rimraf dist",
19
18
  "prebuild": "npm run clean",
20
- "prepublishOnly": "node -p \"'export const version = ' + JSON.stringify(require('./package.json').version) + ';'\" > src/version.ts",
21
- "build": "tsc && rollup -c",
22
- "preversion": "npm run build",
23
- "postversion": "git push --follow-tags"
19
+ "prepublishOnly": "node output-version.js > src/version.ts && git add -A src && git commit -m '[skip ci] Bump version.ts'",
20
+ "build:esm": "tsc",
21
+ "build:bundle": "rollup -c",
22
+ "build": "npm run build:esm && npm run build:bundle",
23
+ "prepack": "npm run build"
24
24
  },
25
25
  "peerDependencies": {
26
26
  "@jbrowse/core": ">=2.0.0",
@@ -33,7 +33,7 @@
33
33
  "react-dom": ">=16.8.0"
34
34
  },
35
35
  "devDependencies": {
36
- "@rollup/plugin-commonjs": "^24.1.0",
36
+ "@rollup/plugin-commonjs": "^25.0.4",
37
37
  "@rollup/plugin-json": "^6.0.0",
38
38
  "@rollup/plugin-node-resolve": "^15.0.2",
39
39
  "@rollup/plugin-replace": "^5.0.2",
@@ -43,14 +43,14 @@
43
43
  "@types/d3": "^7.4.0",
44
44
  "@types/react": "^18.2.0",
45
45
  "@types/react-dom": "^18.2.1",
46
- "rollup": "^3.21.3",
46
+ "rollup": "^3.0.2",
47
47
  "rollup-plugin-polyfill-node": "^0.12.0",
48
48
  "tslib": "^2.1.0",
49
49
  "typescript": "^5.0.4"
50
50
  },
51
51
  "dependencies": {
52
52
  "clustal-js": "^1.0.3",
53
- "color": "^3.1.3",
53
+ "color": "^4.2.3",
54
54
  "copy-to-clipboard": "^3.3.1",
55
55
  "d3-array": "^3.2.3",
56
56
  "d3-hierarchy": "^3.1.2",
@@ -31,16 +31,15 @@ export const UniprotTrack = types
31
31
  autorun(async () => {
32
32
  try {
33
33
  const { accession } = self
34
- const url = `https://rest.uniprot.org/uniprotkb/${accession}.gff`
35
- const response = await fetch(url)
36
- if (!response.ok) {
34
+ const accessionSansVersion = accession?.replace(/\.[^/.]+$/, '')
35
+ const url = `https://rest.uniprot.org/uniprotkb/${accessionSansVersion}.gff`
36
+ const res = await fetch(url)
37
+ if (!res.ok) {
37
38
  throw new Error(
38
- `HTTP ${
39
- response.status
40
- } fetching ${url}: ${await response.text()}`,
39
+ `HTTP ${res.status} fetching ${url}: ${await res.text()}`,
41
40
  )
42
41
  }
43
- self.setData(await response.text())
42
+ self.setData(await res.text())
44
43
  } catch (e) {
45
44
  console.error(e)
46
45
  self.setError(e)
@@ -332,7 +332,7 @@ const colorSchemes = {
332
332
  '-': 'gray',
333
333
  '.': 'gray',
334
334
  },
335
- } as { [key: string]: { [key: string]: string } }
335
+ } as Record<string, Record<string, string>>
336
336
 
337
337
  // turn all supplied colors to hex colors which getContrastText from mui
338
338
  // requires
@@ -347,7 +347,7 @@ export default transform(colorSchemes, ([key, val]) => [
347
347
  // scheme says there the jalview.org colorscheme says WLVIMAFCHP but it
348
348
  // should be WLVIMAFCHPY, colprot.xml says e.g. %#ACFHILMVWYPp" which has Y
349
349
  export function getClustalXColor(
350
- stats: { [key: string]: number },
350
+ stats: Record<string, number>,
351
351
  model: { columns: Record<string, string> },
352
352
  row: string,
353
353
  col: number,
@@ -494,7 +494,7 @@ export function getClustalXColor(
494
494
  // scheme says there the jalview.org colorscheme says WLVIMAFCHP but it
495
495
  // should be WLVIMAFCHPY, colprot.xml says e.g. %#ACFHILMVWYPp" which has Y
496
496
  export function getPercentIdentityColor(
497
- stats: { [key: string]: number },
497
+ stats: Record<string, number>,
498
498
  model: { columns: Record<string, string> },
499
499
  row: string,
500
500
  col: number,
@@ -504,10 +504,10 @@ export function getPercentIdentityColor(
504
504
  const entries = Object.entries(stats)
505
505
  let ent = 0
506
506
  let letter = ''
507
- for (let i = 0; i < entries.length; i++) {
508
- if (entries[i][1] > ent && entries[i][0] !== '-') {
509
- letter = entries[i][0]
510
- ent = entries[i][1]
507
+ for (const entry of entries) {
508
+ if (entry[1] > ent && entry[0] !== '-') {
509
+ letter = entry[0]
510
+ ent = entry[1]
511
511
  }
512
512
  }
513
513
  const proportion = ent / total
@@ -1,201 +1,11 @@
1
- import React, { useRef, useMemo, useEffect } from 'react'
1
+ import React from 'react'
2
2
  import { observer } from 'mobx-react'
3
- import { getSnapshot, isStateTreeNode } from 'mobx-state-tree'
4
3
 
5
4
  // locals
6
5
  import { IBoxTrack, MsaViewModel } from '../model'
7
- import Layout from '../layout'
6
+ import BoxTrackBlock from './BoxTrackBlock'
8
7
 
9
- interface Feat {
10
- start: number
11
- end: number
12
- }
13
- const AnnotationBlock = observer(function ({
14
- track,
15
- model,
16
- offsetX,
17
- }: {
18
- track: IBoxTrack
19
- model: MsaViewModel
20
- offsetX: number
21
- }) {
22
- const {
23
- blockSize,
24
- colWidth,
25
- blanks,
26
- rowHeight,
27
- highResScaleFactor,
28
- scrollX,
29
- } = model
30
- const {
31
- model: { height, features, associatedRowName },
32
- } = track
33
-
34
- const feats: Feat[] = isStateTreeNode(features)
35
- ? // @ts-expect-error
36
- getSnapshot(features)
37
- : features
38
-
39
- const layout = useMemo(() => {
40
- const temp = new Layout()
41
-
42
- feats?.forEach((feature, index) => {
43
- const { start, end } = feature
44
- if (associatedRowName) {
45
- const s = model.rowSpecificBpToPx(associatedRowName, start - 1)
46
- const e = model.rowSpecificBpToPx(associatedRowName, end)
47
- temp.addRect(`${index}`, s, e, rowHeight, feature)
48
- } else {
49
- const s = model.globalBpToPx(start - 1)
50
- const e = model.globalBpToPx(end)
51
- temp.addRect(`${index}`, s, e, rowHeight, feature)
52
- }
53
- })
54
- return temp
55
-
56
- // might convert to autorun based drawing
57
- // eslint-disable-next-line react-hooks/exhaustive-deps
58
- }, [rowHeight, feats, associatedRowName, model, blanks])
59
-
60
- const ref = useRef<HTMLCanvasElement>(null)
61
- const labelRef = useRef<HTMLCanvasElement>(null)
62
- const mouseoverRef = useRef<HTMLCanvasElement>(null)
63
-
64
- useEffect(() => {
65
- if (!ref.current) {
66
- return
67
- }
68
-
69
- const ctx = ref.current.getContext('2d')
70
- if (!ctx) {
71
- return
72
- }
73
-
74
- ctx.resetTransform()
75
- ctx.scale(highResScaleFactor, highResScaleFactor)
76
- ctx.clearRect(0, 0, blockSize, height)
77
- ctx.translate(-offsetX, 0)
78
- ctx.textAlign = 'center'
79
- ctx.font = ctx.font.replace(/\d+px/, `${Math.max(8, rowHeight - 8)}px`)
80
-
81
- const xStart = Math.max(0, Math.floor(offsetX / colWidth))
82
- ctx.fillStyle = 'goldenrod'
83
- layout.rectangles.forEach(value => {
84
- const { minX, maxX, minY, maxY } = value
85
-
86
- const x1 = (minX - xStart) * colWidth + offsetX - (offsetX % colWidth)
87
- const x2 = (maxX - xStart) * colWidth + offsetX - (offsetX % colWidth)
88
-
89
- if (x2 - x1 > 0) {
90
- ctx.fillRect(x1, minY, x2 - x1, (maxY - minY) / 2)
91
- }
92
- })
93
- }, [
94
- associatedRowName,
95
- blockSize,
96
- colWidth,
97
- layout.rectangles,
98
- model,
99
- rowHeight,
100
- height,
101
- offsetX,
102
- highResScaleFactor,
103
- features,
104
- blanks,
105
- ])
106
-
107
- useEffect(() => {
108
- if (!labelRef.current) {
109
- return
110
- }
111
-
112
- const ctx = labelRef.current.getContext('2d')
113
- if (!ctx) {
114
- return
115
- }
116
-
117
- // this logic is very similar to MSACanvas
118
- ctx.resetTransform()
119
- ctx.scale(highResScaleFactor, highResScaleFactor)
120
- ctx.clearRect(0, 0, blockSize, height)
121
- ctx.translate(-offsetX, 0)
122
- ctx.textAlign = 'center'
123
- ctx.font = ctx.font.replace(/\d+px/, `${Math.max(8, rowHeight - 8)}px`)
124
-
125
- ctx.fillStyle = 'black'
126
- ctx.textAlign = 'left'
127
- layout.rectangles.forEach(value => {
128
- const { minX, maxX, maxY, minY } = value
129
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
130
- const feature = value.data as any
131
-
132
- const x1 = minX * colWidth
133
- const x2 = maxX * colWidth
134
-
135
- if (x2 - x1 > 0) {
136
- const note = feature.attributes?.Note?.[0]
137
- const name = feature.attributes?.Name?.[0]
138
- const type = feature.type
139
- ctx.fillText(
140
- [type, name, note].filter(f => !!f).join(' - '),
141
- Math.max(Math.min(-scrollX, x2), x1),
142
- minY + (maxY - minY),
143
- )
144
- }
145
- })
146
- }, [
147
- blockSize,
148
- colWidth,
149
- scrollX,
150
- highResScaleFactor,
151
- height,
152
- layout.rectangles,
153
- offsetX,
154
- features,
155
- model,
156
- rowHeight,
157
- blanks,
158
- ])
159
-
160
- return !features ? null : (
161
- <>
162
- <canvas
163
- ref={ref}
164
- height={height * highResScaleFactor}
165
- width={blockSize * highResScaleFactor}
166
- style={{
167
- position: 'absolute',
168
- left: scrollX + offsetX,
169
- width: blockSize,
170
- height,
171
- }}
172
- />
173
- <canvas
174
- ref={labelRef}
175
- height={height * highResScaleFactor}
176
- width={blockSize * highResScaleFactor}
177
- style={{
178
- position: 'absolute',
179
- left: scrollX + offsetX,
180
- width: blockSize,
181
- height,
182
- }}
183
- />
184
- <canvas
185
- ref={mouseoverRef}
186
- height={height * highResScaleFactor}
187
- width={blockSize * highResScaleFactor}
188
- style={{
189
- position: 'absolute',
190
- left: scrollX + offsetX,
191
- width: blockSize,
192
- height,
193
- }}
194
- />
195
- </>
196
- )
197
- })
198
- const AnnotationTrack = observer(function ({
8
+ const BoxTrack = observer(function ({
199
9
  model,
200
10
  track,
201
11
  }: {
@@ -203,9 +13,7 @@ const AnnotationTrack = observer(function ({
203
13
  track: IBoxTrack
204
14
  }) {
205
15
  const { blocksX, msaAreaWidth } = model
206
- const {
207
- model: { height },
208
- } = track
16
+ const { height } = track.model
209
17
  return (
210
18
  <div
211
19
  style={{
@@ -216,10 +24,10 @@ const AnnotationTrack = observer(function ({
216
24
  }}
217
25
  >
218
26
  {blocksX.map(bx => (
219
- <AnnotationBlock track={track} key={bx} model={model} offsetX={bx} />
27
+ <BoxTrackBlock track={track} key={bx} model={model} offsetX={bx} />
220
28
  ))}
221
29
  </div>
222
30
  )
223
31
  })
224
32
 
225
- export default AnnotationTrack
33
+ export default BoxTrack
@@ -0,0 +1,198 @@
1
+ import React, { useRef, useMemo, useEffect } from 'react'
2
+ import { observer } from 'mobx-react'
3
+ import { getSnapshot, isStateTreeNode } from 'mobx-state-tree'
4
+
5
+ // locals
6
+ import { IBoxTrack, MsaViewModel } from '../model'
7
+ import Layout from '../layout'
8
+
9
+ interface Feat {
10
+ start: number
11
+ end: number
12
+ }
13
+
14
+ const BoxTrackBlock = observer(function ({
15
+ track,
16
+ model,
17
+ offsetX,
18
+ }: {
19
+ track: IBoxTrack
20
+ model: MsaViewModel
21
+ offsetX: number
22
+ }) {
23
+ const {
24
+ blockSize,
25
+ colWidth,
26
+ blanks,
27
+ rowHeight,
28
+ highResScaleFactor,
29
+ scrollX,
30
+ } = model
31
+ const { height, features, associatedRowName } = track.model
32
+
33
+ const feats: Feat[] = isStateTreeNode(features)
34
+ ? // @ts-expect-error
35
+ getSnapshot(features)
36
+ : features
37
+
38
+ const layout = useMemo(() => {
39
+ const temp = new Layout()
40
+
41
+ feats?.forEach((feature, index) => {
42
+ const { start, end } = feature
43
+ if (associatedRowName) {
44
+ const s = model.rowSpecificBpToPx(associatedRowName, start - 1)
45
+ const e = model.rowSpecificBpToPx(associatedRowName, end)
46
+ temp.addRect(`${index}`, s, e, rowHeight, feature)
47
+ } else {
48
+ const s = model.globalBpToPx(start - 1)
49
+ const e = model.globalBpToPx(end)
50
+ temp.addRect(`${index}`, s, e, rowHeight, feature)
51
+ }
52
+ })
53
+ return temp
54
+
55
+ // might convert to autorun based drawing
56
+ // eslint-disable-next-line react-hooks/exhaustive-deps
57
+ }, [rowHeight, feats, associatedRowName, model, blanks])
58
+
59
+ const ref = useRef<HTMLCanvasElement>(null)
60
+ const labelRef = useRef<HTMLCanvasElement>(null)
61
+ const mouseoverRef = useRef<HTMLCanvasElement>(null)
62
+
63
+ useEffect(() => {
64
+ if (!ref.current) {
65
+ return
66
+ }
67
+
68
+ const ctx = ref.current.getContext('2d')
69
+ if (!ctx) {
70
+ return
71
+ }
72
+
73
+ ctx.resetTransform()
74
+ ctx.scale(highResScaleFactor, highResScaleFactor)
75
+ ctx.clearRect(0, 0, blockSize, height)
76
+ ctx.translate(-offsetX, 0)
77
+ ctx.textAlign = 'center'
78
+ ctx.font = ctx.font.replace(/\d+px/, `${Math.max(8, rowHeight - 8)}px`)
79
+
80
+ const xStart = Math.max(0, Math.floor(offsetX / colWidth))
81
+ ctx.fillStyle = 'goldenrod'
82
+ layout.rectangles.forEach(value => {
83
+ const { minX, maxX, minY, maxY } = value
84
+
85
+ const x1 = (minX - xStart) * colWidth + offsetX - (offsetX % colWidth)
86
+ const x2 = (maxX - xStart) * colWidth + offsetX - (offsetX % colWidth)
87
+
88
+ if (x2 - x1 > 0) {
89
+ ctx.fillRect(x1, minY, x2 - x1, (maxY - minY) / 2)
90
+ }
91
+ })
92
+ }, [
93
+ associatedRowName,
94
+ blockSize,
95
+ colWidth,
96
+ layout.rectangles,
97
+ model,
98
+ rowHeight,
99
+ height,
100
+ offsetX,
101
+ highResScaleFactor,
102
+ features,
103
+ blanks,
104
+ ])
105
+
106
+ useEffect(() => {
107
+ if (!labelRef.current) {
108
+ return
109
+ }
110
+
111
+ const ctx = labelRef.current.getContext('2d')
112
+ if (!ctx) {
113
+ return
114
+ }
115
+
116
+ // this logic is very similar to MSACanvas
117
+ ctx.resetTransform()
118
+ ctx.scale(highResScaleFactor, highResScaleFactor)
119
+ ctx.clearRect(0, 0, blockSize, height)
120
+ ctx.translate(-offsetX, 0)
121
+ ctx.textAlign = 'center'
122
+ ctx.font = ctx.font.replace(/\d+px/, `${Math.max(8, rowHeight - 8)}px`)
123
+
124
+ ctx.fillStyle = 'black'
125
+ ctx.textAlign = 'left'
126
+ for (const value of layout.rectangles.values()) {
127
+ const { minX, maxX, maxY, minY, data } = value
128
+
129
+ const x1 = minX * colWidth
130
+ const x2 = maxX * colWidth
131
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
132
+ const feature = data as any
133
+
134
+ if (x2 - x1 > 0) {
135
+ const note = feature.attributes?.Note?.[0]
136
+ const name = feature.attributes?.Name?.[0]
137
+ const type = feature.type
138
+ ctx.fillText(
139
+ [type, name, note].filter(f => !!f).join(' - '),
140
+ Math.max(Math.min(-scrollX, x2), x1),
141
+ minY + (maxY - minY),
142
+ )
143
+ }
144
+ }
145
+ }, [
146
+ blockSize,
147
+ colWidth,
148
+ scrollX,
149
+ highResScaleFactor,
150
+ height,
151
+ layout.rectangles,
152
+ offsetX,
153
+ features,
154
+ model,
155
+ rowHeight,
156
+ blanks,
157
+ ])
158
+
159
+ return !features ? null : (
160
+ <>
161
+ <canvas
162
+ ref={ref}
163
+ height={height * highResScaleFactor}
164
+ width={blockSize * highResScaleFactor}
165
+ style={{
166
+ position: 'absolute',
167
+ left: scrollX + offsetX,
168
+ width: blockSize,
169
+ height,
170
+ }}
171
+ />
172
+ <canvas
173
+ ref={labelRef}
174
+ height={height * highResScaleFactor}
175
+ width={blockSize * highResScaleFactor}
176
+ style={{
177
+ position: 'absolute',
178
+ left: scrollX + offsetX,
179
+ width: blockSize,
180
+ height,
181
+ }}
182
+ />
183
+ <canvas
184
+ ref={mouseoverRef}
185
+ height={height * highResScaleFactor}
186
+ width={blockSize * highResScaleFactor}
187
+ style={{
188
+ position: 'absolute',
189
+ left: scrollX + offsetX,
190
+ width: blockSize,
191
+ height,
192
+ }}
193
+ />
194
+ </>
195
+ )
196
+ })
197
+
198
+ export default BoxTrackBlock
@@ -1,20 +1,23 @@
1
- import React, { useState } from 'react'
2
- import { IconButton, Select, Typography } from '@mui/material'
1
+ import React, { Suspense, lazy, useState } from 'react'
2
+ import { IconButton, Typography } from '@mui/material'
3
3
  import { observer } from 'mobx-react'
4
4
 
5
5
  // locals
6
6
  import { MsaViewModel } from '../model'
7
- import SettingsDialog from './SettingsDlg'
8
- import AboutDialog from './AboutDlg'
9
- import DetailsDialog from './DetailsDlg'
10
- import TracklistDialog from './TracklistDlg'
11
7
 
12
8
  // icons
13
- import FolderOpenIcon from '@mui/icons-material/FolderOpen'
14
- import SettingsIcon from '@mui/icons-material/Settings'
15
- import HelpIcon from '@mui/icons-material/Help'
16
- import AssignmentIcon from '@mui/icons-material/Assignment'
17
- import ListIcon from '@mui/icons-material/List'
9
+ import FolderOpen from '@mui/icons-material/FolderOpen'
10
+ import Settings from '@mui/icons-material/Settings'
11
+ import Help from '@mui/icons-material/Help'
12
+ import Assignment from '@mui/icons-material/Assignment'
13
+ import List from '@mui/icons-material/List'
14
+ import ZoomControls from './ZoomControls'
15
+ import MultiAlignmentSelector from './MultiAlignmentSelector'
16
+
17
+ const SettingsDialog = lazy(() => import('./dialogs/SettingsDlg'))
18
+ const AboutDialog = lazy(() => import('./dialogs/AboutDlg'))
19
+ const DetailsDialog = lazy(() => import('./dialogs/DetailsDlg'))
20
+ const TracklistDialog = lazy(() => import('./dialogs/TracklistDlg'))
18
21
 
19
22
  const InfoArea = observer(({ model }: { model: MsaViewModel }) => {
20
23
  const { mouseOverRowName, mouseCol } = model
@@ -32,7 +35,6 @@ const Header = observer(({ model }: { model: MsaViewModel }) => {
32
35
  const [aboutDialogViz, setAboutDialogViz] = useState(false)
33
36
  const [detailsDialogViz, setDetailsDialogViz] = useState(false)
34
37
  const [tracklistDialogViz, setTracklistDialogViz] = useState(false)
35
- const { currentAlignment, alignmentNames } = model
36
38
 
37
39
  return (
38
40
  <div style={{ display: 'flex' }}>
@@ -51,67 +53,54 @@ const Header = observer(({ model }: { model: MsaViewModel }) => {
51
53
  }
52
54
  }}
53
55
  >
54
- <FolderOpenIcon />
56
+ <FolderOpen />
55
57
  </IconButton>
56
58
  <IconButton onClick={() => setSettingsDialogViz(true)}>
57
- <SettingsIcon />
59
+ <Settings />
58
60
  </IconButton>
59
61
  <IconButton onClick={() => setDetailsDialogViz(true)}>
60
- <AssignmentIcon />
62
+ <Assignment />
61
63
  </IconButton>
62
64
  <IconButton onClick={() => setTracklistDialogViz(true)}>
63
- <ListIcon />
65
+ <List />
64
66
  </IconButton>
65
- {settingsDialogViz ? (
66
- <SettingsDialog
67
- open
68
- model={model}
69
- onClose={() => setSettingsDialogViz(false)}
70
- />
71
- ) : null}
72
- {aboutDialogViz ? (
73
- <AboutDialog open onClose={() => setAboutDialogViz(false)} />
74
- ) : null}
75
- {detailsDialogViz ? (
76
- <DetailsDialog
77
- open
78
- model={model}
79
- onClose={() => setDetailsDialogViz(false)}
80
- />
81
- ) : null}
67
+ <Suspense fallback={null}>
68
+ {settingsDialogViz ? (
69
+ <SettingsDialog
70
+ model={model}
71
+ onClose={() => setSettingsDialogViz(false)}
72
+ />
73
+ ) : null}
74
+ {aboutDialogViz ? (
75
+ <AboutDialog onClose={() => setAboutDialogViz(false)} />
76
+ ) : null}
77
+ {detailsDialogViz ? (
78
+ <DetailsDialog
79
+ model={model}
80
+ onClose={() => setDetailsDialogViz(false)}
81
+ />
82
+ ) : null}
82
83
 
83
- {tracklistDialogViz ? (
84
- <TracklistDialog
85
- open
86
- model={model}
87
- onClose={() => setTracklistDialogViz(false)}
88
- />
89
- ) : null}
90
- {alignmentNames.length > 0 ? (
91
- <Select
92
- native
93
- value={currentAlignment}
94
- size="small"
95
- onChange={event => {
96
- model.setCurrentAlignment(+(event.target.value as string))
97
- model.setScrollX(0)
98
- model.setScrollY(0)
99
- }}
100
- >
101
- {alignmentNames.map((option, index) => (
102
- <option key={`${option}-${index}`} value={index}>
103
- {option}
104
- </option>
105
- ))}
106
- </Select>
107
- ) : null}
84
+ {tracklistDialogViz ? (
85
+ <TracklistDialog
86
+ model={model}
87
+ onClose={() => setTracklistDialogViz(false)}
88
+ />
89
+ ) : null}
90
+ </Suspense>
91
+ <MultiAlignmentSelector model={model} />
92
+ <ZoomControls model={model} />
108
93
  <InfoArea model={model} />
109
- <div style={{ flex: 1 }} />
94
+ <Spacer />
110
95
  <IconButton onClick={() => setAboutDialogViz(true)}>
111
- <HelpIcon />
96
+ <Help />
112
97
  </IconButton>
113
98
  </div>
114
99
  )
115
100
  })
116
101
 
102
+ function Spacer() {
103
+ return <div style={{ flex: 1 }} />
104
+ }
105
+
117
106
  export default Header