react-msaview 8.1.0 → 8.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.
- package/bundle/index.js +104 -117
- package/bundle/index.js.map +4 -4
- package/dist/columnCounts.d.ts +4 -3
- package/dist/columnCounts.js +11 -6
- package/dist/columnCounts.js.map +1 -1
- package/dist/components/CopyButton.d.ts +1 -1
- package/dist/components/CopyButton.js +1 -1
- package/dist/components/CopyButton.js.map +1 -1
- package/dist/components/DragHandle.js +14 -2
- package/dist/components/DragHandle.js.map +1 -1
- package/dist/components/Loading.js +10 -11
- package/dist/components/Loading.js.map +1 -1
- package/dist/components/MSAViewer.d.ts +12 -1
- package/dist/components/MSAViewer.js +12 -1
- package/dist/components/MSAViewer.js.map +1 -1
- package/dist/components/SequenceTextArea.d.ts +11 -0
- package/dist/components/SequenceTextArea.js +40 -8
- package/dist/components/SequenceTextArea.js.map +1 -1
- package/dist/components/StatusMessage.d.ts +12 -0
- package/dist/components/StatusMessage.js +43 -0
- package/dist/components/StatusMessage.js.map +1 -0
- package/dist/components/Track.js +3 -3
- package/dist/components/Track.js.map +1 -1
- package/dist/components/VerticalScrollbar.js +6 -1
- package/dist/components/VerticalScrollbar.js.map +1 -1
- package/dist/components/dialogs/ExportSVGDialog.d.ts +3 -2
- package/dist/components/dialogs/ExportSVGDialog.js +4 -2
- package/dist/components/dialogs/ExportSVGDialog.js.map +1 -1
- package/dist/components/dialogs/FeatureDialog.js +15 -10
- package/dist/components/dialogs/FeatureDialog.js.map +1 -1
- package/dist/components/getVisibleLeaves.d.ts +1 -1
- package/dist/components/header/FileMenu.js +9 -0
- package/dist/components/header/FileMenu.js.map +1 -1
- package/dist/components/header/GappynessSlider.js +15 -3
- package/dist/components/header/GappynessSlider.js.map +1 -1
- package/dist/components/header/Header.js +6 -7
- package/dist/components/header/Header.js.map +1 -1
- package/dist/components/header/{HeaderStatusArea.d.ts → RowSearch.d.ts} +2 -2
- package/dist/components/header/RowSearch.js +15 -0
- package/dist/components/header/RowSearch.js.map +1 -0
- package/dist/components/header/revealRow.d.ts +3 -0
- package/dist/components/header/revealRow.js +15 -0
- package/dist/components/header/revealRow.js.map +1 -0
- package/dist/components/header/settingsMenuItems.js +57 -1
- package/dist/components/header/settingsMenuItems.js.map +1 -1
- package/dist/components/import/ImportForm.js +10 -4
- package/dist/components/import/ImportForm.js.map +1 -1
- package/dist/components/import/ImportFormExamples.js +19 -27
- package/dist/components/import/ImportFormExamples.js.map +1 -1
- package/dist/components/minimap/Minimap.js +6 -1
- package/dist/components/minimap/Minimap.js.map +1 -1
- package/dist/components/msa/AnnotationLegend.js +11 -5
- package/dist/components/msa/AnnotationLegend.js.map +1 -1
- package/dist/components/msa/MSACanvas.js +6 -1
- package/dist/components/msa/MSACanvas.js.map +1 -1
- package/dist/components/msa/MSACanvasBlock.js +3 -7
- package/dist/components/msa/MSACanvasBlock.js.map +1 -1
- package/dist/components/msa/msaKeys.d.ts +5 -0
- package/dist/components/msa/msaKeys.js +35 -0
- package/dist/components/msa/msaKeys.js.map +1 -0
- package/dist/components/msa/useMsaBlockMouse.d.ts +2 -4
- package/dist/components/msa/useMsaBlockMouse.js +18 -5
- package/dist/components/msa/useMsaBlockMouse.js.map +1 -1
- package/dist/components/rowpanels/RowPanelBlock.js +10 -14
- package/dist/components/rowpanels/RowPanelBlock.js.map +1 -1
- package/dist/components/tracks/drawTracks.d.ts +1 -0
- package/dist/components/tracks/drawTracks.js +14 -2
- package/dist/components/tracks/drawTracks.js.map +1 -1
- package/dist/components/tracks/useTrackHover.d.ts +1 -5
- package/dist/components/tracks/useTrackHover.js +5 -5
- package/dist/components/tracks/useTrackHover.js.map +1 -1
- package/dist/components/tree/ScaleBarSVG.d.ts +14 -0
- package/dist/components/tree/ScaleBarSVG.js +11 -0
- package/dist/components/tree/ScaleBarSVG.js.map +1 -0
- package/dist/components/tree/TreeCanvas.js +8 -26
- package/dist/components/tree/TreeCanvas.js.map +1 -1
- package/dist/components/tree/TreeCanvasBlock.js +11 -20
- package/dist/components/tree/TreeCanvasBlock.js.map +1 -1
- package/dist/components/tree/{TreeBranchMenu.d.ts → TreeMenu.d.ts} +5 -4
- package/dist/components/tree/TreeMenu.js +48 -0
- package/dist/components/tree/TreeMenu.js.map +1 -0
- package/dist/components/tree/TreeRuler.js +3 -3
- package/dist/components/tree/TreeRuler.js.map +1 -1
- package/dist/components/tree/renderTreeCanvas.js +19 -9
- package/dist/components/tree/renderTreeCanvas.js.map +1 -1
- package/dist/components/tree/renderTreeMouseover.d.ts +5 -0
- package/dist/components/tree/renderTreeMouseover.js +20 -0
- package/dist/components/tree/renderTreeMouseover.js.map +1 -0
- package/dist/components/tree/useTreeHover.d.ts +3 -8
- package/dist/components/tree/useTreeHover.js +8 -10
- package/dist/components/tree/useTreeHover.js.map +1 -1
- package/dist/components/useHoverAnchor.d.ts +19 -0
- package/dist/components/useHoverAnchor.js +18 -0
- package/dist/components/useHoverAnchor.js.map +1 -0
- package/dist/constants.d.ts +4 -1
- package/dist/constants.js +12 -3
- package/dist/constants.js.map +1 -1
- package/dist/expandSpec.d.ts +46 -0
- package/dist/expandSpec.js +127 -0
- package/dist/expandSpec.js.map +1 -0
- package/dist/exportFileName.d.ts +3 -3
- package/dist/exportFileName.js +4 -4
- package/dist/exportFileName.js.map +1 -1
- package/dist/hierarchy.d.ts +51 -0
- package/dist/hierarchy.js +125 -0
- package/dist/hierarchy.js.map +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/model/treeModel.d.ts +12 -0
- package/dist/model/treeModel.js +14 -1
- package/dist/model/treeModel.js.map +1 -1
- package/dist/model.d.ts +188 -47
- package/dist/model.js +482 -192
- package/dist/model.js.map +1 -1
- package/dist/renderToSvg.js +2 -4
- package/dist/renderToSvg.js.map +1 -1
- package/dist/rerootTree.d.ts +33 -0
- package/dist/rerootTree.js +144 -0
- package/dist/rerootTree.js.map +1 -0
- package/dist/svgTestUtil.d.ts +114 -20
- package/dist/types.d.ts +25 -12
- package/dist/useMsaSvgFigure.d.ts +2 -1
- package/dist/useMsaSvgFigure.js +11 -4
- package/dist/useMsaSvgFigure.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/writeNewick.d.ts +8 -0
- package/dist/writeNewick.js +34 -0
- package/dist/writeNewick.js.map +1 -0
- package/package.json +3 -3
- package/src/columnCounts.ts +14 -6
- package/src/components/CopyButton.tsx +2 -2
- package/src/components/DragHandle.tsx +21 -1
- package/src/components/Loading.tsx +14 -30
- package/src/components/MSAViewer.tsx +26 -0
- package/src/components/SequenceTextArea.tsx +54 -8
- package/src/components/StatusMessage.tsx +70 -0
- package/src/components/Track.tsx +8 -4
- package/src/components/VerticalScrollbar.tsx +7 -0
- package/src/components/dialogs/ExportSVGDialog.tsx +5 -2
- package/src/components/dialogs/FeatureDialog.tsx +59 -38
- package/src/components/header/FileMenu.tsx +9 -0
- package/src/components/header/GappynessSlider.tsx +17 -4
- package/src/components/header/Header.tsx +7 -8
- package/src/components/header/RowSearch.tsx +34 -0
- package/src/components/header/revealRow.ts +17 -0
- package/src/components/header/settingsMenuItems.ts +66 -0
- package/src/components/import/ImportForm.tsx +14 -7
- package/src/components/import/ImportFormExamples.tsx +20 -28
- package/src/components/minimap/Minimap.tsx +7 -0
- package/src/components/msa/AnnotationLegend.tsx +12 -6
- package/src/components/msa/MSACanvas.tsx +8 -0
- package/src/components/msa/MSACanvasBlock.tsx +5 -11
- package/src/components/msa/msaKeys.ts +39 -0
- package/src/components/msa/useMsaBlockMouse.ts +23 -7
- package/src/components/rowpanels/RowPanelBlock.tsx +16 -23
- package/src/components/tracks/drawTracks.ts +15 -2
- package/src/components/tracks/useTrackHover.ts +5 -5
- package/src/components/tree/ScaleBarSVG.tsx +30 -0
- package/src/components/tree/TreeCanvas.tsx +9 -40
- package/src/components/tree/TreeCanvasBlock.tsx +19 -39
- package/src/components/tree/TreeMenu.tsx +129 -0
- package/src/components/tree/TreeRuler.tsx +7 -13
- package/src/components/tree/renderTreeCanvas.ts +20 -8
- package/src/components/tree/renderTreeMouseover.ts +41 -0
- package/src/components/tree/useTreeHover.ts +8 -17
- package/src/components/useHoverAnchor.ts +31 -0
- package/src/constants.ts +14 -3
- package/src/expandSpec.ts +196 -0
- package/src/exportFileName.ts +4 -3
- package/src/hierarchy.ts +151 -4
- package/src/index.ts +11 -1
- package/src/model/treeModel.ts +22 -0
- package/src/model.ts +588 -221
- package/src/renderToSvg.tsx +6 -9
- package/src/rerootTree.ts +172 -0
- package/src/types.ts +34 -12
- package/src/useMsaSvgFigure.ts +11 -4
- package/src/version.ts +1 -1
- package/src/writeNewick.ts +38 -0
- package/dist/components/PortalTooltip.d.ts +0 -11
- package/dist/components/PortalTooltip.js +0 -29
- package/dist/components/PortalTooltip.js.map +0 -1
- package/dist/components/header/HeaderStatusArea.js +0 -21
- package/dist/components/header/HeaderStatusArea.js.map +0 -1
- package/dist/components/tree/TreeBranchMenu.js +0 -29
- package/dist/components/tree/TreeBranchMenu.js.map +0 -1
- package/dist/components/tree/TreeNodeMenu.d.ts +0 -13
- package/dist/components/tree/TreeNodeMenu.js +0 -41
- package/dist/components/tree/TreeNodeMenu.js.map +0 -1
- package/src/components/PortalTooltip.tsx +0 -47
- package/src/components/header/HeaderStatusArea.tsx +0 -32
- package/src/components/tree/TreeBranchMenu.tsx +0 -68
- package/src/components/tree/TreeNodeMenu.tsx +0 -95
package/dist/types.d.ts
CHANGED
|
@@ -269,10 +269,27 @@ export interface Highlight {
|
|
|
269
269
|
/**
|
|
270
270
|
* What the viewer draws over a clade. `highlight` fills the rows behind it,
|
|
271
271
|
* across the tree and the alignment. `bracket` draws a bar and the clade's
|
|
272
|
-
* `label` in the gutter right of the tip labels. `collapse
|
|
273
|
-
* the collapsed list
|
|
272
|
+
* `label` in the gutter right of the tip labels. `collapse`, `focus` and
|
|
273
|
+
* `rotate` seed the collapsed list, the subtree in focus and the rotated nodes
|
|
274
|
+
* at load.
|
|
274
275
|
*/
|
|
275
|
-
export type CladeMark = 'highlight' | 'bracket' | 'collapse' | 'focus';
|
|
276
|
+
export type CladeMark = 'highlight' | 'bracket' | 'collapse' | 'focus' | 'rotate';
|
|
277
|
+
/**
|
|
278
|
+
* The order each node's children draw in, top to bottom. `branchLength` puts
|
|
279
|
+
* the shortest branch first, `input` keeps the file's order, `ladderize` puts
|
|
280
|
+
* the clade with the fewest tips first, so the deep clades step down toward
|
|
281
|
+
* the bottom, and `ladderizeReverse` puts the one with the most tips first.
|
|
282
|
+
*/
|
|
283
|
+
export type TreeOrder = 'branchLength' | 'input' | 'ladderize' | 'ladderizeReverse';
|
|
284
|
+
/**
|
|
285
|
+
* Where the tree is rooted: `midpoint` halfway along the longest path between
|
|
286
|
+
* two tips, and `{outgroup}` on the branch above those tips' most recent common
|
|
287
|
+
* ancestor, or above the rest of the tips' where the outgroup straddles the
|
|
288
|
+
* root the file gives.
|
|
289
|
+
*/
|
|
290
|
+
export type TreeRoot = 'midpoint' | {
|
|
291
|
+
outgroup: string[];
|
|
292
|
+
};
|
|
276
293
|
/**
|
|
277
294
|
* A clade of the tree and the mark drawn over it. `mrca` names tips whose most
|
|
278
295
|
* recent common ancestor is the clade; `range` names the first and last tip of
|
|
@@ -316,8 +333,9 @@ export interface Region {
|
|
|
316
333
|
/**
|
|
317
334
|
* A cell of the alignment in the host's coordinates, 1-based like Highlight:
|
|
318
335
|
* `column` counts every column of the file, hidden gappy ones included, and
|
|
319
|
-
* `residue` counts the row's own letters, absent on a gap.
|
|
320
|
-
*
|
|
336
|
+
* `residue` counts the row's own letters, absent on a gap. `letter` is the
|
|
337
|
+
* character in the cell, the gap character (`-` or `.`) on a gap. A pointer
|
|
338
|
+
* over a track has a column and no row.
|
|
321
339
|
*/
|
|
322
340
|
export interface Cell {
|
|
323
341
|
column: number;
|
|
@@ -369,10 +387,5 @@ export interface NodeWithIds {
|
|
|
369
387
|
length?: number;
|
|
370
388
|
noTree?: boolean;
|
|
371
389
|
}
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
name: string;
|
|
375
|
-
children: NodeWithIdsAndLength[];
|
|
376
|
-
noTree?: boolean;
|
|
377
|
-
length: number;
|
|
378
|
-
}
|
|
390
|
+
/** @deprecated the same type as `NodeWithIds` */
|
|
391
|
+
export type NodeWithIdsAndLength = NodeWithIds;
|
|
@@ -8,7 +8,8 @@ export interface MsaSvgFigure {
|
|
|
8
8
|
/**
|
|
9
9
|
* The viewer's SVG export of its viewport, as markup for the page to insert.
|
|
10
10
|
* It redraws after the view settles: `delay` ms after the last scroll, zoom,
|
|
11
|
-
* resize or change to the snapshot. A draw a later one overtakes is
|
|
11
|
+
* resize, highlight or change to the snapshot. A draw a later one overtakes is
|
|
12
|
+
* dropped.
|
|
12
13
|
*
|
|
13
14
|
* The figure's row names and letters are SVG text, so the browser's find
|
|
14
15
|
* locates them, a reader can select them, and print stylesheets apply.
|
package/dist/useMsaSvgFigure.js
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { useEffect, useState } from 'react';
|
|
2
2
|
import { createJBrowseTheme } from '@jbrowse/core/ui/theme';
|
|
3
3
|
import { getSnapshot } from '@jbrowse/mobx-state-tree';
|
|
4
|
-
import {
|
|
4
|
+
import { compareShallow, reaction } from 'mobx';
|
|
5
5
|
import { renderToSvg } from './renderToSvg.js';
|
|
6
6
|
/**
|
|
7
7
|
* The viewer's SVG export of its viewport, as markup for the page to insert.
|
|
8
8
|
* It redraws after the view settles: `delay` ms after the last scroll, zoom,
|
|
9
|
-
* resize or change to the snapshot. A draw a later one overtakes is
|
|
9
|
+
* resize, highlight or change to the snapshot. A draw a later one overtakes is
|
|
10
|
+
* dropped.
|
|
10
11
|
*
|
|
11
12
|
* The figure's row names and letters are SVG text, so the browser's find
|
|
12
13
|
* locates them, a reader can select them, and print stylesheets apply.
|
|
@@ -16,7 +17,13 @@ export function useMsaSvgFigure(model, { theme, includeTracks = false, delay = 3
|
|
|
16
17
|
useEffect(() => {
|
|
17
18
|
let latest = 0;
|
|
18
19
|
const dispose = reaction(() => model.viewInitialized && model.dataInitialized
|
|
19
|
-
? [
|
|
20
|
+
? [
|
|
21
|
+
getSnapshot(model),
|
|
22
|
+
model.width,
|
|
23
|
+
model.height,
|
|
24
|
+
model.resolvedHighlights,
|
|
25
|
+
model.highlightedColumnRuns,
|
|
26
|
+
]
|
|
20
27
|
: undefined, key => {
|
|
21
28
|
if (!key) {
|
|
22
29
|
return;
|
|
@@ -35,7 +42,7 @@ export function useMsaSvgFigure(model, { theme, includeTracks = false, delay = 3
|
|
|
35
42
|
setFigure({ error });
|
|
36
43
|
}
|
|
37
44
|
});
|
|
38
|
-
}, { delay, fireImmediately: true, equals:
|
|
45
|
+
}, { delay, fireImmediately: true, equals: compareShallow });
|
|
39
46
|
return () => {
|
|
40
47
|
latest = -1;
|
|
41
48
|
dispose();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useMsaSvgFigure.js","sourceRoot":"","sources":["../src/useMsaSvgFigure.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AAE3C,OAAO,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAA;AAC3D,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAA;AACtD,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"useMsaSvgFigure.js","sourceRoot":"","sources":["../src/useMsaSvgFigure.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AAE3C,OAAO,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAA;AAC3D,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAA;AACtD,OAAO,EAAE,cAAc,EAAE,QAAQ,EAAE,MAAM,MAAM,CAAA;AAE/C,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAmB,CAAA;AAW/C;;;;;;;;GAQG;AACH,MAAM,UAAU,eAAe,CAC7B,KAAmB,EACnB,EACE,KAAK,EACL,aAAa,GAAG,KAAK,EACrB,KAAK,GAAG,GAAG,GACZ,GAA+D,EAAE;IAElE,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,GAAG,QAAQ,CAAe,EAAE,CAAC,CAAA;IACtD,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,MAAM,GAAG,CAAC,CAAA;QACd,MAAM,OAAO,GAAG,QAAQ,CACtB,GAAG,EAAE,CACH,KAAK,CAAC,eAAe,IAAI,KAAK,CAAC,eAAe;YAC5C,CAAC,CAAC;gBACE,WAAW,CAAC,KAAK,CAAC;gBAClB,KAAK,CAAC,KAAK;gBACX,KAAK,CAAC,MAAM;gBACZ,KAAK,CAAC,kBAAkB;gBACxB,KAAK,CAAC,qBAAqB;aAC5B;YACH,CAAC,CAAC,SAAS,EACf,GAAG,CAAC,EAAE;YACJ,IAAI,CAAC,GAAG,EAAE,CAAC;gBACT,OAAM;YACR,CAAC;YACD,MAAM,IAAI,GAAG,EAAE,MAAM,CAAA;YACrB,WAAW,CAAC,KAAK,EAAE;gBACjB,KAAK,EAAE,KAAK,IAAI,kBAAkB,EAAE;gBACpC,UAAU,EAAE,UAAU;gBACtB,aAAa;aACd,CAAC,CAAC,IAAI,CACL,GAAG,CAAC,EAAE;gBACJ,IAAI,IAAI,KAAK,MAAM,EAAE,CAAC;oBACpB,SAAS,CAAC,EAAE,GAAG,EAAE,CAAC,CAAA;gBACpB,CAAC;YACH,CAAC,EACD,CAAC,KAAc,EAAE,EAAE;gBACjB,IAAI,IAAI,KAAK,MAAM,EAAE,CAAC;oBACpB,SAAS,CAAC,EAAE,KAAK,EAAE,CAAC,CAAA;gBACtB,CAAC;YACH,CAAC,CACF,CAAA;QACH,CAAC,EACD,EAAE,KAAK,EAAE,eAAe,EAAE,IAAI,EAAE,MAAM,EAAE,cAAc,EAAE,CACzD,CAAA;QACD,OAAO,GAAG,EAAE;YACV,MAAM,GAAG,CAAC,CAAC,CAAA;YACX,OAAO,EAAE,CAAA;QACX,CAAC,CAAA;IACH,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,aAAa,EAAE,KAAK,CAAC,CAAC,CAAA;IACxC,OAAO,MAAM,CAAA;AACf,CAAC"}
|
package/dist/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const version = "8.
|
|
1
|
+
export declare const version = "8.3.0";
|
package/dist/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export const version = '8.
|
|
1
|
+
export const version = '8.3.0';
|
|
2
2
|
//# sourceMappingURL=version.js.map
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { HierarchyNode } from './hierarchy.ts';
|
|
2
|
+
/**
|
|
3
|
+
* The tree as Newick, in the order it is drawn. An internal node whose name is
|
|
4
|
+
* only its generated id goes unnamed. Each subtree's string is dropped once its
|
|
5
|
+
* parent takes it in, since a caterpillar tree is as deep as it has tips and
|
|
6
|
+
* keeping every one is quadratic.
|
|
7
|
+
*/
|
|
8
|
+
export declare function writeNewick(root: HierarchyNode): string;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { descendants } from './hierarchy.js';
|
|
2
|
+
// a name holding any of these reads back as grammar unless it is quoted
|
|
3
|
+
const needsQuotes = /[\s(),:;[\]']/;
|
|
4
|
+
function quote(name) {
|
|
5
|
+
return needsQuotes.test(name) ? `'${name.replaceAll("'", "''")}'` : name;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* The tree as Newick, in the order it is drawn. An internal node whose name is
|
|
9
|
+
* only its generated id goes unnamed. Each subtree's string is dropped once its
|
|
10
|
+
* parent takes it in, since a caterpillar tree is as deep as it has tips and
|
|
11
|
+
* keeping every one is quadratic.
|
|
12
|
+
*/
|
|
13
|
+
export function writeNewick(root) {
|
|
14
|
+
const written = new Map();
|
|
15
|
+
for (const node of descendants(root).reverse()) {
|
|
16
|
+
const { id, name, length } = node.data;
|
|
17
|
+
let text = '';
|
|
18
|
+
if (node.children) {
|
|
19
|
+
text = `(${node.children.map(c => written.get(c)).join(',')})`;
|
|
20
|
+
for (const child of node.children) {
|
|
21
|
+
written.delete(child);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
if (!(node.children && name === id)) {
|
|
25
|
+
text += quote(name);
|
|
26
|
+
}
|
|
27
|
+
if (length !== undefined) {
|
|
28
|
+
text += `:${length}`;
|
|
29
|
+
}
|
|
30
|
+
written.set(node, text);
|
|
31
|
+
}
|
|
32
|
+
return `${written.get(root)};`;
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=writeNewick.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"writeNewick.js","sourceRoot":"","sources":["../src/writeNewick.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA;AAI5C,wEAAwE;AACxE,MAAM,WAAW,GAAG,eAAe,CAAA;AAEnC,SAAS,KAAK,CAAC,IAAY;IACzB,OAAO,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAA;AAC1E,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,WAAW,CAAC,IAAmB;IAC7C,MAAM,OAAO,GAAG,IAAI,GAAG,EAAyB,CAAA;IAChD,KAAK,MAAM,IAAI,IAAI,WAAW,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC;QAC/C,MAAM,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC,IAAI,CAAA;QACtC,IAAI,IAAI,GAAG,EAAE,CAAA;QACb,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YAClB,IAAI,GAAG,IAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAE,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAA;YAC/D,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;gBAClC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;YACvB,CAAC;QACH,CAAC;QACD,IAAI,CAAC,CAAC,IAAI,CAAC,QAAQ,IAAI,IAAI,KAAK,EAAE,CAAC,EAAE,CAAC;YACpC,IAAI,IAAI,KAAK,CAAC,IAAI,CAAC,CAAA;QACrB,CAAC;QACD,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;YACzB,IAAI,IAAI,IAAI,MAAM,EAAE,CAAA;QACtB,CAAC;QACD,OAAO,CAAC,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,CAAA;IACzB,CAAC;IACD,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,CAAE,GAAG,CAAA;AACjC,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-msaview",
|
|
3
3
|
"author": "Colin",
|
|
4
|
-
"version": "8.
|
|
4
|
+
"version": "8.3.0",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"repository": {
|
|
@@ -50,10 +50,10 @@
|
|
|
50
50
|
},
|
|
51
51
|
"dependencies": {
|
|
52
52
|
"@gmod/newick": "^1.0.3",
|
|
53
|
-
"@jbrowse/svgcanvas": "8.
|
|
53
|
+
"@jbrowse/svgcanvas": "8.3.0",
|
|
54
54
|
"colord": "^2.10.0",
|
|
55
55
|
"flatbush": "^4.6.2",
|
|
56
|
-
"msa-parsers": "8.
|
|
56
|
+
"msa-parsers": "8.3.0"
|
|
57
57
|
},
|
|
58
58
|
"scripts": {
|
|
59
59
|
"clean": "rimraf dist bundle",
|
package/src/columnCounts.ts
CHANGED
|
@@ -63,15 +63,21 @@ export class ColumnCounts {
|
|
|
63
63
|
this.totals = new Uint32Array(numColumns)
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
+
/** tallies a row, counting the columns past a short row's end as gaps */
|
|
66
67
|
addRow(row: string) {
|
|
67
|
-
const { counts, totals, slotTotals } = this
|
|
68
|
-
const n = Math.min(row.length,
|
|
68
|
+
const { counts, totals, slotTotals, numColumns } = this
|
|
69
|
+
const n = Math.min(row.length, numColumns)
|
|
69
70
|
for (let col = 0; col < n; col++) {
|
|
70
71
|
const slot = slotOf(row.charCodeAt(col))
|
|
71
72
|
counts[col * SLOTS + slot]!++
|
|
72
73
|
totals[col]!++
|
|
73
74
|
slotTotals[slot]!++
|
|
74
75
|
}
|
|
76
|
+
for (let col = n; col < numColumns; col++) {
|
|
77
|
+
counts[col * SLOTS + DASH]!++
|
|
78
|
+
totals[col]!++
|
|
79
|
+
}
|
|
80
|
+
slotTotals[DASH]! += numColumns - n
|
|
75
81
|
}
|
|
76
82
|
|
|
77
83
|
add(col: number, letter: string) {
|
|
@@ -161,11 +167,13 @@ export const numResidueSlots = SLOTS
|
|
|
161
167
|
|
|
162
168
|
/**
|
|
163
169
|
* Tally counts from alignment rows (gapped sequence strings, one per row). The
|
|
164
|
-
* column count
|
|
165
|
-
*
|
|
170
|
+
* column count defaults to the longest row, and a shorter row counts as gapped
|
|
171
|
+
* to the end.
|
|
166
172
|
*/
|
|
167
|
-
export function columnCountsFromRows(
|
|
168
|
-
|
|
173
|
+
export function columnCountsFromRows(
|
|
174
|
+
rows: string[],
|
|
175
|
+
numColumns = rows.reduce((max, row) => Math.max(max, row.length), 0),
|
|
176
|
+
) {
|
|
169
177
|
const counts = new ColumnCounts(numColumns)
|
|
170
178
|
for (const row of rows) {
|
|
171
179
|
counts.addRow(row)
|
|
@@ -12,7 +12,7 @@ export default function CopyButton({
|
|
|
12
12
|
text,
|
|
13
13
|
label = 'Copy to clipboard',
|
|
14
14
|
}: {
|
|
15
|
-
text: string
|
|
15
|
+
text: string | (() => string)
|
|
16
16
|
label?: string
|
|
17
17
|
}) {
|
|
18
18
|
const [copied, setCopied] = useState(false)
|
|
@@ -30,7 +30,7 @@ export default function CopyButton({
|
|
|
30
30
|
variant="contained"
|
|
31
31
|
color="primary"
|
|
32
32
|
onClick={() => {
|
|
33
|
-
copy(text)
|
|
33
|
+
copy(typeof text === 'string' ? text : text())
|
|
34
34
|
setCopied(true)
|
|
35
35
|
clearTimeout(timer.current)
|
|
36
36
|
timer.current = setTimeout(() => {
|
|
@@ -8,6 +8,10 @@ import type { Theme } from '@mui/material'
|
|
|
8
8
|
|
|
9
9
|
export const scrollbarThumbFill = 'rgba(66,119,127,0.3)'
|
|
10
10
|
|
|
11
|
+
// the model's resizeHandleWidth feeds the layout, so a divider keeps its drawn
|
|
12
|
+
// width and widens its target with this much invisible margin each side
|
|
13
|
+
const hitSlop = 4
|
|
14
|
+
|
|
11
15
|
// The scroll thumbs (minimap, vertical scrollbar) and the resize dividers are
|
|
12
16
|
// one widget -- press, drag along an axis, report the pixel delta -- differing
|
|
13
17
|
// only in the affordance they present, so the variant carries the whole
|
|
@@ -71,6 +75,10 @@ export default function DragHandle({
|
|
|
71
75
|
|
|
72
76
|
return (
|
|
73
77
|
<div
|
|
78
|
+
{...(variant === 'resizer' && {
|
|
79
|
+
role: 'separator',
|
|
80
|
+
'aria-orientation': axis === 'x' ? 'vertical' : 'horizontal',
|
|
81
|
+
})}
|
|
74
82
|
onMouseEnter={() => {
|
|
75
83
|
setHovered(true)
|
|
76
84
|
}}
|
|
@@ -87,6 +95,18 @@ export default function DragHandle({
|
|
|
87
95
|
background: hovered ? fillHovered(theme) : fill(theme),
|
|
88
96
|
...style,
|
|
89
97
|
}}
|
|
90
|
-
|
|
98
|
+
>
|
|
99
|
+
{variant === 'resizer' ? (
|
|
100
|
+
<div
|
|
101
|
+
style={{
|
|
102
|
+
position: 'absolute',
|
|
103
|
+
zIndex: 101,
|
|
104
|
+
...(axis === 'x'
|
|
105
|
+
? { top: 0, bottom: 0, left: -hitSlop, right: -hitSlop }
|
|
106
|
+
: { left: 0, right: 0, top: -hitSlop, bottom: -hitSlop }),
|
|
107
|
+
}}
|
|
108
|
+
/>
|
|
109
|
+
) : null}
|
|
110
|
+
</div>
|
|
91
111
|
)
|
|
92
112
|
}
|
|
@@ -2,35 +2,15 @@ import React from 'react'
|
|
|
2
2
|
|
|
3
3
|
import { ErrorMessage } from '@jbrowse/core/ui'
|
|
4
4
|
import { ErrorBoundary } from '@jbrowse/core/ui/ErrorBoundary'
|
|
5
|
-
import { Button
|
|
5
|
+
import { Button } from '@mui/material'
|
|
6
6
|
import { observer } from 'mobx-react'
|
|
7
7
|
|
|
8
8
|
import MSAView from './MSAView.tsx'
|
|
9
|
+
import StatusMessage from './StatusMessage.tsx'
|
|
9
10
|
import ImportForm from './import/ImportForm.tsx'
|
|
10
11
|
|
|
11
12
|
import type { MsaViewModel } from '../model.ts'
|
|
12
13
|
|
|
13
|
-
const LoadingSpinner = observer(function ({ model }: { model: MsaViewModel }) {
|
|
14
|
-
const { status } = model
|
|
15
|
-
return (
|
|
16
|
-
<div
|
|
17
|
-
style={{ display: 'flex', alignItems: 'center', gap: 12, padding: 20 }}
|
|
18
|
-
>
|
|
19
|
-
<CircularProgress size={24} />
|
|
20
|
-
<Typography variant="h6">{status?.msg ?? 'Loading...'}</Typography>
|
|
21
|
-
{status?.onCancel ? (
|
|
22
|
-
<Button
|
|
23
|
-
variant="outlined"
|
|
24
|
-
size="small"
|
|
25
|
-
onClick={() => status.onCancel?.()}
|
|
26
|
-
>
|
|
27
|
-
Cancel
|
|
28
|
-
</Button>
|
|
29
|
-
) : null}
|
|
30
|
-
</div>
|
|
31
|
-
)
|
|
32
|
-
})
|
|
33
|
-
|
|
34
14
|
const Reset = observer(function ({
|
|
35
15
|
model,
|
|
36
16
|
error,
|
|
@@ -58,12 +38,15 @@ const Loading = observer(function ({ model }: { model: MsaViewModel }) {
|
|
|
58
38
|
const {
|
|
59
39
|
isLoading,
|
|
60
40
|
dataInitialized,
|
|
41
|
+
data,
|
|
61
42
|
error,
|
|
62
43
|
resetCount,
|
|
63
44
|
msaFilehandle,
|
|
64
45
|
treeFilehandle,
|
|
65
46
|
} = model
|
|
66
|
-
const
|
|
47
|
+
const hasData = !!(data.msa || data.tree)
|
|
48
|
+
// a failed load keeps its filehandle, so the error outranks the spinner
|
|
49
|
+
const loading = !error && !!(msaFilehandle || treeFilehandle || isLoading)
|
|
67
50
|
|
|
68
51
|
return (
|
|
69
52
|
<div>
|
|
@@ -76,18 +59,19 @@ const Loading = observer(function ({ model }: { model: MsaViewModel }) {
|
|
|
76
59
|
>
|
|
77
60
|
{dataInitialized ? (
|
|
78
61
|
isLoading ? (
|
|
79
|
-
<
|
|
62
|
+
<StatusMessage model={model} variant="page" />
|
|
80
63
|
) : (
|
|
81
64
|
<MSAView model={model} />
|
|
82
65
|
)
|
|
83
|
-
) :
|
|
84
|
-
// checked before the spinner: a failed load keeps its filehandle
|
|
85
|
-
// (only an abort clears it), so hasPendingFilehandle stays true
|
|
66
|
+
) : hasData ? (
|
|
86
67
|
<Reset model={model} error={error} />
|
|
87
|
-
) : hasPendingFilehandle || isLoading ? (
|
|
88
|
-
<LoadingSpinner model={model} />
|
|
89
68
|
) : (
|
|
90
|
-
|
|
69
|
+
<>
|
|
70
|
+
{loading ? <StatusMessage model={model} variant="page" /> : null}
|
|
71
|
+
<div style={{ display: loading ? 'none' : undefined }}>
|
|
72
|
+
<ImportForm model={model} />
|
|
73
|
+
</div>
|
|
74
|
+
</>
|
|
91
75
|
)}
|
|
92
76
|
</ErrorBoundary>
|
|
93
77
|
</div>
|
|
@@ -20,6 +20,8 @@ import type {
|
|
|
20
20
|
ResidueEncoding,
|
|
21
21
|
ResidueMapping,
|
|
22
22
|
RowPanelSpec,
|
|
23
|
+
TreeOrder,
|
|
24
|
+
TreeRoot,
|
|
23
25
|
Viewport,
|
|
24
26
|
} from '../types.ts'
|
|
25
27
|
import type { FileLocation as FileLocationType } from '@jbrowse/core/util/types'
|
|
@@ -94,6 +96,17 @@ export interface MSAViewerProps {
|
|
|
94
96
|
autoTreeAreaWidth?: boolean
|
|
95
97
|
/** draw branch lengths (default true); false draws a cladogram */
|
|
96
98
|
showBranchLen?: boolean
|
|
99
|
+
/**
|
|
100
|
+
* the order each node's children draw in: `branchLength` (default,
|
|
101
|
+
* shortest first), `input` (as the file gives them), `ladderize` (fewest
|
|
102
|
+
* tips first) or `ladderizeReverse` (most tips first)
|
|
103
|
+
*/
|
|
104
|
+
treeOrder?: TreeOrder
|
|
105
|
+
/**
|
|
106
|
+
* reroot the tree: `midpoint`, or `{outgroup: [tip names]}` on the branch
|
|
107
|
+
* above those tips' common ancestor
|
|
108
|
+
*/
|
|
109
|
+
treeRoot?: TreeRoot
|
|
97
110
|
/**
|
|
98
111
|
* which channel `colorScheme` paints: `fill` (default) colors the background
|
|
99
112
|
* of the cell a residue sits in, `color` colors the letter itself and leaves
|
|
@@ -215,6 +228,8 @@ function Viewer({
|
|
|
215
228
|
treeAreaWidth,
|
|
216
229
|
autoTreeAreaWidth,
|
|
217
230
|
showBranchLen,
|
|
231
|
+
treeOrder,
|
|
232
|
+
treeRoot,
|
|
218
233
|
region,
|
|
219
234
|
hideHeader,
|
|
220
235
|
theme,
|
|
@@ -258,6 +273,8 @@ function Viewer({
|
|
|
258
273
|
...(treeAreaWidth ? { treeAreaWidth } : {}),
|
|
259
274
|
...(autoTreeAreaWidth ? { autoTreeAreaWidth } : {}),
|
|
260
275
|
...(showBranchLen !== undefined ? { showBranchLen } : {}),
|
|
276
|
+
...(treeOrder ? { treeOrder } : {}),
|
|
277
|
+
...(treeRoot ? { treeRoot } : {}),
|
|
261
278
|
...(residueEncoding !== undefined
|
|
262
279
|
? { bgColor: residueEncoding === 'fill' }
|
|
263
280
|
: {}),
|
|
@@ -317,6 +334,11 @@ function Viewer({
|
|
|
317
334
|
model.setShowBranchLen(showBranchLen)
|
|
318
335
|
}
|
|
319
336
|
}, [model, showBranchLen])
|
|
337
|
+
useEffect(() => {
|
|
338
|
+
if (treeOrder !== undefined) {
|
|
339
|
+
model.setTreeOrder(treeOrder)
|
|
340
|
+
}
|
|
341
|
+
}, [model, treeOrder])
|
|
320
342
|
useEffect(() => {
|
|
321
343
|
if (residueEncoding !== undefined) {
|
|
322
344
|
model.setBgColor(residueEncoding === 'fill')
|
|
@@ -336,6 +358,10 @@ function Viewer({
|
|
|
336
358
|
useEffect(() => {
|
|
337
359
|
model.setHighlights(JSON.parse(highlightsKey))
|
|
338
360
|
}, [model, highlightsKey])
|
|
361
|
+
const treeRootKey = JSON.stringify(treeRoot ?? null)
|
|
362
|
+
useEffect(() => {
|
|
363
|
+
model.setTreeRoot(JSON.parse(treeRootKey) ?? undefined)
|
|
364
|
+
}, [model, treeRootKey])
|
|
339
365
|
const cladesKey = JSON.stringify(clades ?? [])
|
|
340
366
|
useEffect(() => {
|
|
341
367
|
model.setClades(JSON.parse(cladesKey))
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import React, { useState } from 'react'
|
|
2
2
|
|
|
3
3
|
import { makeStyles } from '@jbrowse/core/util/tss-react'
|
|
4
|
-
import { TextField } from '@mui/material'
|
|
4
|
+
import { Button, TextField } from '@mui/material'
|
|
5
5
|
import { getUngappedSequence } from 'msa-parsers'
|
|
6
6
|
|
|
7
|
+
import { saveAs } from '../vendor/fileSaver.ts'
|
|
7
8
|
import Checkbox2 from './Checkbox2.tsx'
|
|
8
9
|
import CopyButton from './CopyButton.tsx'
|
|
9
10
|
|
|
@@ -18,19 +19,59 @@ const useStyles = makeStyles()(theme => ({
|
|
|
18
19
|
},
|
|
19
20
|
}))
|
|
20
21
|
|
|
22
|
+
export const maxShownRecords = 100
|
|
23
|
+
export const maxShownChars = 200_000
|
|
24
|
+
|
|
25
|
+
function fastaRecord([name, seq]: [string, string], showGaps: boolean) {
|
|
26
|
+
return `>${name}\n${showGaps ? seq : getUngappedSequence(seq)}`
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function isAllGaps(seq: string) {
|
|
30
|
+
return !/[^.-]/.test(seq)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* The first records of `rows` as FASTA, up to `maxShownRecords` records or
|
|
35
|
+
* `maxShownChars` characters, whichever comes first. A textarea holding every
|
|
36
|
+
* row of a large alignment freezes the page.
|
|
37
|
+
*/
|
|
38
|
+
export function fastaPreview(rows: [string, string][], showGaps: boolean) {
|
|
39
|
+
const shown: string[] = []
|
|
40
|
+
let chars = 0
|
|
41
|
+
for (const row of rows) {
|
|
42
|
+
if (shown.length >= maxShownRecords || chars >= maxShownChars) {
|
|
43
|
+
break
|
|
44
|
+
}
|
|
45
|
+
const record = fastaRecord(row, showGaps)
|
|
46
|
+
shown.push(record)
|
|
47
|
+
chars += record.length + 1
|
|
48
|
+
}
|
|
49
|
+
return { text: shown.join('\n'), hidden: rows.length - shown.length }
|
|
50
|
+
}
|
|
51
|
+
|
|
21
52
|
export default function SequenceTextArea({ str }: { str: [string, string][] }) {
|
|
22
53
|
const { classes } = useStyles()
|
|
23
54
|
const [showGaps, setShowGaps] = useState(false)
|
|
24
55
|
const [showEmpty, setShowEmpty] = useState(false)
|
|
25
56
|
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
.filter(([,
|
|
29
|
-
|
|
30
|
-
|
|
57
|
+
const rows = showEmpty
|
|
58
|
+
? str
|
|
59
|
+
: str.filter(([, seq]) => (showGaps ? !!seq : !isAllGaps(seq)))
|
|
60
|
+
const { text, hidden } = fastaPreview(rows, showGaps)
|
|
61
|
+
const fullText = () => rows.map(row => fastaRecord(row, showGaps)).join('\n')
|
|
31
62
|
return (
|
|
32
63
|
<>
|
|
33
|
-
<CopyButton text={
|
|
64
|
+
<CopyButton text={fullText} />
|
|
65
|
+
<Button
|
|
66
|
+
onClick={() => {
|
|
67
|
+
saveAs(
|
|
68
|
+
new Blob([fullText()], { type: 'text/plain' }),
|
|
69
|
+
'sequences.fasta',
|
|
70
|
+
)
|
|
71
|
+
}}
|
|
72
|
+
>
|
|
73
|
+
Download
|
|
74
|
+
</Button>
|
|
34
75
|
<Checkbox2
|
|
35
76
|
label="Show gaps"
|
|
36
77
|
checked={showGaps}
|
|
@@ -52,7 +93,12 @@ export default function SequenceTextArea({ str }: { str: [string, string][] }) {
|
|
|
52
93
|
minRows={5}
|
|
53
94
|
maxRows={10}
|
|
54
95
|
fullWidth
|
|
55
|
-
value={
|
|
96
|
+
value={text}
|
|
97
|
+
helperText={
|
|
98
|
+
hidden > 0
|
|
99
|
+
? `Showing ${rows.length - hidden} of ${rows.length.toLocaleString()} sequences. ${hidden.toLocaleString()} more are in the copy and the download.`
|
|
100
|
+
: undefined
|
|
101
|
+
}
|
|
56
102
|
slotProps={{
|
|
57
103
|
input: {
|
|
58
104
|
readOnly: true,
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
|
|
3
|
+
import { LoadingEllipses } from '@jbrowse/core/ui'
|
|
4
|
+
import { makeStyles } from '@jbrowse/core/util/tss-react'
|
|
5
|
+
import { Button, CircularProgress, Typography } from '@mui/material'
|
|
6
|
+
import { observer } from 'mobx-react'
|
|
7
|
+
|
|
8
|
+
import type { MsaViewModel } from '../model.ts'
|
|
9
|
+
|
|
10
|
+
const useStyles = makeStyles()({
|
|
11
|
+
page: {
|
|
12
|
+
display: 'flex',
|
|
13
|
+
alignItems: 'center',
|
|
14
|
+
gap: 12,
|
|
15
|
+
padding: 20,
|
|
16
|
+
},
|
|
17
|
+
header: {
|
|
18
|
+
display: 'flex',
|
|
19
|
+
alignItems: 'center',
|
|
20
|
+
gap: 4,
|
|
21
|
+
margin: 'auto',
|
|
22
|
+
marginLeft: 10,
|
|
23
|
+
},
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* The model's status line with its Cancel button: a spinner and a heading in
|
|
28
|
+
* place of the view while it loads, or a line in the header afterwards, which
|
|
29
|
+
* disappears when there is no status.
|
|
30
|
+
*/
|
|
31
|
+
const StatusMessage = observer(function ({
|
|
32
|
+
model,
|
|
33
|
+
variant,
|
|
34
|
+
}: {
|
|
35
|
+
model: MsaViewModel
|
|
36
|
+
variant: 'page' | 'header'
|
|
37
|
+
}) {
|
|
38
|
+
const { status } = model
|
|
39
|
+
const { classes } = useStyles()
|
|
40
|
+
if (variant === 'header' && !status) {
|
|
41
|
+
return null
|
|
42
|
+
}
|
|
43
|
+
const message = status?.msg ?? 'Loading...'
|
|
44
|
+
const onCancel = status?.onCancel
|
|
45
|
+
return (
|
|
46
|
+
<div className={classes[variant]}>
|
|
47
|
+
{variant === 'page' ? (
|
|
48
|
+
<>
|
|
49
|
+
<CircularProgress size={24} />
|
|
50
|
+
<Typography variant="h6">{message}</Typography>
|
|
51
|
+
</>
|
|
52
|
+
) : (
|
|
53
|
+
<LoadingEllipses message={message} component="span" />
|
|
54
|
+
)}
|
|
55
|
+
{onCancel ? (
|
|
56
|
+
<Button
|
|
57
|
+
variant={variant === 'page' ? 'outlined' : 'text'}
|
|
58
|
+
size="small"
|
|
59
|
+
onClick={() => {
|
|
60
|
+
onCancel()
|
|
61
|
+
}}
|
|
62
|
+
>
|
|
63
|
+
Cancel
|
|
64
|
+
</Button>
|
|
65
|
+
) : null}
|
|
66
|
+
</div>
|
|
67
|
+
)
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
export default StatusMessage
|
package/src/components/Track.tsx
CHANGED
|
@@ -146,7 +146,7 @@ const Track = observer(function ({
|
|
|
146
146
|
)
|
|
147
147
|
useWheelScroll({ ref, onScrollX })
|
|
148
148
|
const { classes } = useStyles()
|
|
149
|
-
const {
|
|
149
|
+
const { anchor, onMouseMove, onMouseLeave } = useTrackHover({
|
|
150
150
|
model,
|
|
151
151
|
tooltip: showColumnStats,
|
|
152
152
|
})
|
|
@@ -172,9 +172,13 @@ const Track = observer(function ({
|
|
|
172
172
|
}}
|
|
173
173
|
>
|
|
174
174
|
<track.ReactComponent model={model} track={track} />
|
|
175
|
-
{
|
|
176
|
-
<BaseTooltip clientPoint={
|
|
177
|
-
<TrackTooltipContent
|
|
175
|
+
{anchor ? (
|
|
176
|
+
<BaseTooltip clientPoint={anchor.clientPoint}>
|
|
177
|
+
<TrackTooltipContent
|
|
178
|
+
model={model}
|
|
179
|
+
track={track}
|
|
180
|
+
col={anchor.value}
|
|
181
|
+
/>
|
|
178
182
|
</BaseTooltip>
|
|
179
183
|
) : null}
|
|
180
184
|
</div>
|
|
@@ -23,6 +23,13 @@ const VerticalScrollbar = observer(({ model }: { model: MsaViewModel }) => {
|
|
|
23
23
|
|
|
24
24
|
return (
|
|
25
25
|
<div
|
|
26
|
+
onMouseDown={event => {
|
|
27
|
+
if (event.button === 0 && event.target === event.currentTarget) {
|
|
28
|
+
const y =
|
|
29
|
+
event.clientY - event.currentTarget.getBoundingClientRect().top
|
|
30
|
+
model.setScrollY(-y / unit + msaAreaHeight / 2)
|
|
31
|
+
}
|
|
32
|
+
}}
|
|
26
33
|
style={{
|
|
27
34
|
position: 'relative',
|
|
28
35
|
width,
|