react-msaview 8.2.0 → 8.4.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 +77 -300
- package/bundle/index.js.map +4 -4
- package/dist/blockFasta.d.ts +6 -0
- package/dist/blockFasta.js +12 -0
- package/dist/blockFasta.js.map +1 -0
- package/dist/colorSchemes.d.ts +7 -0
- package/dist/colorSchemes.js +15 -0
- package/dist/colorSchemes.js.map +1 -1
- 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/MSAView.js +15 -9
- package/dist/components/MSAView.js.map +1 -1
- package/dist/components/MSAViewer.d.ts +39 -7
- package/dist/components/MSAViewer.js +72 -22
- package/dist/components/MSAViewer.js.map +1 -1
- package/dist/components/MetadataTable.d.ts +5 -0
- package/dist/components/MetadataTable.js +29 -0
- package/dist/components/MetadataTable.js.map +1 -0
- 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 +13 -4
- 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/dialogs/MetadataDialog.js +5 -6
- package/dist/components/dialogs/MetadataDialog.js.map +1 -1
- package/dist/components/getVisibleLeaves.d.ts +1 -1
- package/dist/components/header/ColorSchemeMenu.d.ts +6 -0
- package/dist/components/header/ColorSchemeMenu.js +23 -5
- package/dist/components/header/ColorSchemeMenu.js.map +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/{HeaderStatusArea.d.ts → GoToBox.d.ts} +2 -2
- package/dist/components/header/GoToBox.js +21 -0
- package/dist/components/header/GoToBox.js.map +1 -0
- package/dist/components/header/Header.js +8 -7
- package/dist/components/header/Header.js.map +1 -1
- package/dist/components/header/SelectionMenu.d.ts +12 -0
- package/dist/components/header/SelectionMenu.js +72 -0
- package/dist/components/header/SelectionMenu.js.map +1 -0
- package/dist/components/header/goTo.d.ts +20 -0
- package/dist/components/header/goTo.js +69 -0
- package/dist/components/header/goTo.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 +17 -1
- package/dist/components/msa/MSACanvas.js.map +1 -1
- package/dist/components/msa/MSACanvasBlock.js +6 -6
- package/dist/components/msa/MSACanvasBlock.js.map +1 -1
- package/dist/components/msa/msaKeys.d.ts +5 -0
- package/dist/components/msa/msaKeys.js +41 -0
- package/dist/components/msa/msaKeys.js.map +1 -0
- package/dist/components/msa/msaRaster.js +1 -0
- package/dist/components/msa/msaRaster.js.map +1 -1
- package/dist/components/msa/renderHighlights.d.ts +14 -3
- package/dist/components/msa/renderHighlights.js +29 -4
- package/dist/components/msa/renderHighlights.js.map +1 -1
- package/dist/components/msa/tileColor.js +9 -8
- package/dist/components/msa/tileColor.js.map +1 -1
- package/dist/components/msa/useMsaBlockMouse.d.ts +2 -4
- package/dist/components/msa/useMsaBlockMouse.js +34 -14
- package/dist/components/msa/useMsaBlockMouse.js.map +1 -1
- package/dist/components/overlayColors.d.ts +2 -0
- package/dist/components/overlayColors.js +3 -0
- package/dist/components/overlayColors.js.map +1 -1
- package/dist/components/rowpanels/RowPanelBlock.js +10 -14
- package/dist/components/rowpanels/RowPanelBlock.js.map +1 -1
- package/dist/components/selectionDrag.d.ts +17 -0
- package/dist/components/selectionDrag.js +30 -0
- package/dist/components/selectionDrag.js.map +1 -0
- 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 +4 -6
- package/dist/components/tracks/useTrackHover.js +30 -10
- 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/dialogs/TreeNodeInfoDialog.js +9 -8
- package/dist/components/tree/dialogs/TreeNodeInfoDialog.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 +3 -0
- package/dist/constants.js +12 -3
- package/dist/constants.js.map +1 -1
- package/dist/element.d.ts +7 -3
- package/dist/element.js +32 -4
- package/dist/element.js.map +1 -1
- package/dist/expandSpec.js +3 -3
- package/dist/expandSpec.js.map +1 -1
- package/dist/exportFileName.d.ts +3 -3
- package/dist/exportFileName.js +4 -4
- package/dist/exportFileName.js.map +1 -1
- package/dist/featuresLayer.d.ts +8 -0
- package/dist/featuresLayer.js +42 -0
- package/dist/featuresLayer.js.map +1 -0
- 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 +2 -2
- package/dist/model/DataModel.d.ts +1 -1
- package/dist/model/DataModel.js +3 -3
- package/dist/model/DataModel.js.map +1 -1
- package/dist/model/msaModel.d.ts +14 -1
- package/dist/model/msaModel.js +17 -1
- package/dist/model/msaModel.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 +285 -53
- package/dist/model.js +693 -231
- 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 +172 -26
- package/dist/types.d.ts +75 -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/blockFasta.ts +18 -0
- package/src/colorSchemes.ts +20 -0
- 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/MSAView.tsx +16 -9
- package/src/components/MSAViewer.tsx +121 -26
- package/src/components/MetadataTable.tsx +55 -0
- package/src/components/SequenceTextArea.tsx +54 -8
- package/src/components/StatusMessage.tsx +70 -0
- package/src/components/Track.tsx +21 -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/dialogs/MetadataDialog.tsx +9 -8
- package/src/components/header/ColorSchemeMenu.tsx +31 -12
- package/src/components/header/FileMenu.tsx +9 -0
- package/src/components/header/GappynessSlider.tsx +17 -4
- package/src/components/header/GoToBox.tsx +50 -0
- package/src/components/header/Header.tsx +11 -8
- package/src/components/header/SelectionMenu.tsx +117 -0
- package/src/components/header/goTo.ts +75 -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 +19 -1
- package/src/components/msa/MSACanvasBlock.tsx +9 -11
- package/src/components/msa/msaKeys.ts +45 -0
- package/src/components/msa/msaRaster.ts +1 -0
- package/src/components/msa/renderHighlights.ts +46 -4
- package/src/components/msa/tileColor.ts +9 -8
- package/src/components/msa/useMsaBlockMouse.ts +44 -31
- package/src/components/overlayColors.ts +4 -0
- package/src/components/rowpanels/RowPanelBlock.tsx +16 -23
- package/src/components/selectionDrag.ts +42 -0
- package/src/components/tracks/drawTracks.ts +15 -2
- package/src/components/tracks/useTrackHover.ts +32 -10
- 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/dialogs/TreeNodeInfoDialog.tsx +14 -16
- 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/element.ts +45 -5
- package/src/expandSpec.ts +3 -3
- package/src/exportFileName.ts +4 -3
- package/src/featuresLayer.ts +48 -0
- package/src/hierarchy.ts +151 -4
- package/src/index.ts +8 -1
- package/src/model/DataModel.ts +3 -3
- package/src/model/msaModel.ts +19 -1
- package/src/model/treeModel.ts +22 -0
- package/src/model.ts +841 -272
- package/src/renderToSvg.tsx +6 -9
- package/src/rerootTree.ts +172 -0
- package/src/types.ts +92 -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
|
@@ -1,21 +1,16 @@
|
|
|
1
1
|
import { useState } from 'react'
|
|
2
2
|
|
|
3
|
+
import { useHoverAnchor } from '../useHoverAnchor.ts'
|
|
3
4
|
import { ClickMapIndex } from './clickMap.ts'
|
|
4
5
|
|
|
5
6
|
import type { MsaViewModel } from '../../model.ts'
|
|
6
7
|
import type { ClickEntry } from './clickMap.ts'
|
|
7
8
|
import type React from 'react'
|
|
8
9
|
|
|
9
|
-
export interface TreeHoverTarget extends ClickEntry {
|
|
10
|
-
clientX: number
|
|
11
|
-
clientY: number
|
|
12
|
-
}
|
|
13
|
-
|
|
14
10
|
/**
|
|
15
11
|
* Owns hit-testing for one tree block: the spatial index the render pass fills
|
|
16
|
-
* in, the entry
|
|
17
|
-
* MSA panel mirrors.
|
|
18
|
-
* it lives in a ref; everything the component draws comes back as plain state.
|
|
12
|
+
* in, the entry under the pointer with its tooltip anchor, and the model hover
|
|
13
|
+
* state that the MSA panel mirrors.
|
|
19
14
|
*/
|
|
20
15
|
export function useTreeHover({
|
|
21
16
|
model,
|
|
@@ -25,10 +20,10 @@ export function useTreeHover({
|
|
|
25
20
|
offsetY: number
|
|
26
21
|
}) {
|
|
27
22
|
const [clickMap] = useState(() => new ClickMapIndex())
|
|
28
|
-
const
|
|
23
|
+
const { anchor, hoverAt, clearAnchor } = useHoverAnchor<ClickEntry>()
|
|
29
24
|
|
|
30
25
|
// leaf labels win over the branch/bubble targets they overlap, so a click on a
|
|
31
|
-
// name opens the
|
|
26
|
+
// name opens the leaf's menu
|
|
32
27
|
function hitTest(event: React.MouseEvent) {
|
|
33
28
|
const x = event.nativeEvent.offsetX
|
|
34
29
|
const y = event.nativeEvent.offsetY + offsetY
|
|
@@ -43,11 +38,7 @@ export function useTreeHover({
|
|
|
43
38
|
|
|
44
39
|
function onMouseMove(event: React.MouseEvent) {
|
|
45
40
|
const entry = hitTest(event)
|
|
46
|
-
|
|
47
|
-
entry
|
|
48
|
-
? { ...entry, clientX: event.clientX, clientY: event.clientY }
|
|
49
|
-
: undefined,
|
|
50
|
-
)
|
|
41
|
+
hoverAt(event, entry)
|
|
51
42
|
// hovering an internal node highlights every tip below it; hovering a leaf
|
|
52
43
|
// label additionally drives the single-row highlight
|
|
53
44
|
model.setHoveredTreeNode(entry?.id)
|
|
@@ -58,10 +49,10 @@ export function useTreeHover({
|
|
|
58
49
|
}
|
|
59
50
|
|
|
60
51
|
function onMouseLeave() {
|
|
61
|
-
|
|
52
|
+
clearAnchor()
|
|
62
53
|
model.setHoveredTreeNode(undefined)
|
|
63
54
|
model.setMousePos(undefined, undefined)
|
|
64
55
|
}
|
|
65
56
|
|
|
66
|
-
return { clickMap,
|
|
57
|
+
return { clickMap, anchor, hitTest, onMouseMove, onMouseLeave }
|
|
67
58
|
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { useState } from 'react'
|
|
2
|
+
|
|
3
|
+
export interface HoverAnchor<T> {
|
|
4
|
+
clientPoint: { x: number; y: number }
|
|
5
|
+
value: T
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* The thing under the pointer and the point a tooltip about it hangs from,
|
|
10
|
+
* 15px below the cursor so the tooltip clears it.
|
|
11
|
+
*/
|
|
12
|
+
export function useHoverAnchor<T>() {
|
|
13
|
+
const [anchor, setAnchor] = useState<HoverAnchor<T>>()
|
|
14
|
+
|
|
15
|
+
function hoverAt(
|
|
16
|
+
event: { clientX: number; clientY: number },
|
|
17
|
+
value: T | undefined,
|
|
18
|
+
) {
|
|
19
|
+
setAnchor(
|
|
20
|
+
value === undefined
|
|
21
|
+
? undefined
|
|
22
|
+
: { value, clientPoint: { x: event.clientX, y: event.clientY + 15 } },
|
|
23
|
+
)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function clearAnchor() {
|
|
27
|
+
setAnchor(undefined)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return { anchor, hoverAt, clearAnchor }
|
|
31
|
+
}
|
package/src/constants.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { TreeOrder } from './types.ts'
|
|
2
|
+
|
|
1
3
|
// Main model defaults
|
|
2
4
|
export const defaultRowHeight = 16
|
|
3
5
|
export const defaultColWidth = 12
|
|
@@ -75,6 +77,13 @@ export const defaultLabelsAlignRight = false
|
|
|
75
77
|
export const defaultTreeAreaWidth = 400
|
|
76
78
|
export const defaultTreeWidth = 300
|
|
77
79
|
export const defaultShowBranchLen = true
|
|
80
|
+
export const treeOrders = [
|
|
81
|
+
'branchLength',
|
|
82
|
+
'input',
|
|
83
|
+
'ladderize',
|
|
84
|
+
'ladderizeReverse',
|
|
85
|
+
] as const satisfies readonly TreeOrder[]
|
|
86
|
+
export const defaultTreeOrder: TreeOrder = 'branchLength'
|
|
78
87
|
export const defaultDrawTree = true
|
|
79
88
|
export const defaultDrawNodeBubbles = true
|
|
80
89
|
export const defaultDrawNodeLabels = false
|
|
@@ -97,9 +106,11 @@ export const rowTintAlpha = 0.25
|
|
|
97
106
|
export const cladeHighlightColor = '#fff3c4'
|
|
98
107
|
export const cladeHighlightAlpha = 0.6
|
|
99
108
|
|
|
100
|
-
// The largest inline document
|
|
101
|
-
// locally-opened file stays in the
|
|
102
|
-
//
|
|
109
|
+
// The largest inline document a snapshot carries by default, which keeps a
|
|
110
|
+
// jbrowse session small. A larger pasted or locally-opened file stays in the
|
|
111
|
+
// live model and out of the snapshot, and `unshareableData` warns using the
|
|
112
|
+
// same number. `MSAModelF({ maxInlineSnapshotBytes })` sets another; the demo
|
|
113
|
+
// app's links hold far more.
|
|
103
114
|
export const maxInlineSnapshotBytes = 50_000
|
|
104
115
|
|
|
105
116
|
// Height of the band the `rowPanels` headers draw in, above the strips in the
|
package/src/element.ts
CHANGED
|
@@ -7,15 +7,26 @@ import type { MsaViewModel } from './model.ts'
|
|
|
7
7
|
import type { MountedViewer } from './mount.tsx'
|
|
8
8
|
import type {
|
|
9
9
|
Cell,
|
|
10
|
+
ColorScheme,
|
|
10
11
|
ColumnTrackSpec,
|
|
12
|
+
Feature,
|
|
11
13
|
Highlight,
|
|
14
|
+
MsaSelection,
|
|
12
15
|
ResidueMapping,
|
|
13
16
|
Viewport,
|
|
14
17
|
} from './types.ts'
|
|
15
18
|
|
|
16
19
|
type ElementData = Pick<
|
|
17
20
|
MSAViewerProps,
|
|
18
|
-
|
|
21
|
+
| 'msa'
|
|
22
|
+
| 'tree'
|
|
23
|
+
| 'gff'
|
|
24
|
+
| 'features'
|
|
25
|
+
| 'colorScheme'
|
|
26
|
+
| 'highlights'
|
|
27
|
+
| 'columnTracks'
|
|
28
|
+
| 'residueMappings'
|
|
29
|
+
| 'selection'
|
|
19
30
|
>
|
|
20
31
|
|
|
21
32
|
interface NightingaleManager extends HTMLElement {
|
|
@@ -78,9 +89,13 @@ function residueRange(model: MsaViewModel, row: string, viewport: Viewport) {
|
|
|
78
89
|
/**
|
|
79
90
|
* Registers a custom element that renders MSAViewer, for pages with no React.
|
|
80
91
|
* Attributes: msa-url, tree-url, gff-url, color-scheme, height, hide-header,
|
|
81
|
-
* theme, tree-area-width and reference-row. Properties: msa, tree and gff text,
|
|
82
|
-
*
|
|
83
|
-
*
|
|
92
|
+
* theme, tree-area-width and reference-row. Properties: msa, tree and gff text,
|
|
93
|
+
* features, colorScheme, highlights, columnTracks, residueMappings and
|
|
94
|
+
* selection. The
|
|
95
|
+
* colorScheme property takes a `{map}` the color-scheme attribute cannot hold,
|
|
96
|
+
* and wins over the attribute while set. Events: cell-hover, cell-click,
|
|
97
|
+
* viewport-change and selection-change, each with the MSAViewer callback's
|
|
98
|
+
* value as detail.
|
|
84
99
|
*
|
|
85
100
|
* Inside a <nightingale-manager>, the element registers with it and speaks its
|
|
86
101
|
* protocol: it follows the display-start, display-end and highlight attributes
|
|
@@ -122,6 +137,18 @@ export function defineMsaElement(tagName = 'jbrowse-msa') {
|
|
|
122
137
|
set gff(value: string | undefined) {
|
|
123
138
|
this.#setData({ gff: value })
|
|
124
139
|
}
|
|
140
|
+
get features() {
|
|
141
|
+
return this.#data.features
|
|
142
|
+
}
|
|
143
|
+
set features(value: Feature[] | undefined) {
|
|
144
|
+
this.#setData({ features: value })
|
|
145
|
+
}
|
|
146
|
+
get colorScheme() {
|
|
147
|
+
return this.#data.colorScheme
|
|
148
|
+
}
|
|
149
|
+
set colorScheme(value: ColorScheme | undefined) {
|
|
150
|
+
this.#setData({ colorScheme: value })
|
|
151
|
+
}
|
|
125
152
|
get highlights() {
|
|
126
153
|
return this.#data.highlights
|
|
127
154
|
}
|
|
@@ -140,6 +167,13 @@ export function defineMsaElement(tagName = 'jbrowse-msa') {
|
|
|
140
167
|
set residueMappings(value: ResidueMapping[] | undefined) {
|
|
141
168
|
this.#setData({ residueMappings: value })
|
|
142
169
|
}
|
|
170
|
+
// the viewer's selection, which the reader's drags change
|
|
171
|
+
get selection() {
|
|
172
|
+
return this.#model ? this.#model.selection : this.#data.selection
|
|
173
|
+
}
|
|
174
|
+
set selection(value: MsaSelection | undefined) {
|
|
175
|
+
this.#setData({ selection: value })
|
|
176
|
+
}
|
|
143
177
|
|
|
144
178
|
get #referenceRow() {
|
|
145
179
|
return this.getAttribute('reference-row') ?? undefined
|
|
@@ -157,7 +191,10 @@ export function defineMsaElement(tagName = 'jbrowse-msa') {
|
|
|
157
191
|
msaFilehandle: location(this.getAttribute('msa-url')),
|
|
158
192
|
treeFilehandle: location(this.getAttribute('tree-url')),
|
|
159
193
|
gffFilehandle: location(this.getAttribute('gff-url')),
|
|
160
|
-
colorScheme:
|
|
194
|
+
colorScheme:
|
|
195
|
+
this.#data.colorScheme ??
|
|
196
|
+
this.getAttribute('color-scheme') ??
|
|
197
|
+
undefined,
|
|
161
198
|
height: numberAttribute(this.getAttribute('height')),
|
|
162
199
|
treeAreaWidth: numberAttribute(this.getAttribute('tree-area-width')),
|
|
163
200
|
hideHeader: this.hasAttribute('hide-header'),
|
|
@@ -173,6 +210,9 @@ export function defineMsaElement(tagName = 'jbrowse-msa') {
|
|
|
173
210
|
this.#emit('viewport-change', viewport)
|
|
174
211
|
this.#reportRange(viewport)
|
|
175
212
|
},
|
|
213
|
+
onSelectionChange: selection => {
|
|
214
|
+
this.#emit('selection-change', selection)
|
|
215
|
+
},
|
|
176
216
|
onModel: model => {
|
|
177
217
|
this.#model = model
|
|
178
218
|
this.#followRange()
|
package/src/expandSpec.ts
CHANGED
|
@@ -59,9 +59,9 @@ function parseSpan(text: string, what: string) {
|
|
|
59
59
|
`${what} "${text}" is not "start", "start-end" or either followed by a label`,
|
|
60
60
|
)
|
|
61
61
|
}
|
|
62
|
-
const
|
|
63
|
-
const
|
|
64
|
-
return { start, end, label: match[3] }
|
|
62
|
+
const a = Number(match[1])
|
|
63
|
+
const b = match[2] === undefined ? a : Number(match[2])
|
|
64
|
+
return { start: Math.min(a, b), end: Math.max(a, b), label: match[3] }
|
|
65
65
|
}
|
|
66
66
|
|
|
67
67
|
function withRow<T extends RowChoice>(entry: T, query?: string) {
|
package/src/exportFileName.ts
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import type { FileLocation } from '@jbrowse/core/util/types'
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* The name to save an export under: the loaded
|
|
5
|
-
*
|
|
4
|
+
* The name to save an export under: the loaded file's name with the extension
|
|
5
|
+
* swapped, or `fallback` when the data was pasted in.
|
|
6
6
|
*/
|
|
7
7
|
export function exportFileName(
|
|
8
8
|
location: FileLocation | undefined,
|
|
9
9
|
extension: string,
|
|
10
|
+
fallback = 'image',
|
|
10
11
|
) {
|
|
11
12
|
const loc = location as
|
|
12
13
|
| { uri?: string; localPath?: string; name?: string }
|
|
@@ -17,5 +18,5 @@ export function exportFileName(
|
|
|
17
18
|
?.split(/[/\\]/)
|
|
18
19
|
.pop()
|
|
19
20
|
?.replace(/\.[^.]*$/, '')
|
|
20
|
-
return `${base ||
|
|
21
|
+
return `${base || fallback}.${extension}`
|
|
21
22
|
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { Annotation, Feature } from './types.ts'
|
|
2
|
+
|
|
3
|
+
const placement = new Set(['row', 'start', 'end'])
|
|
4
|
+
|
|
5
|
+
// The colors and labels are maps keyed by annotation, and a getter read
|
|
6
|
+
// outside a reaction recomputes on every read. Caching on the frozen feature
|
|
7
|
+
// gives it one annotation however often the layer converts.
|
|
8
|
+
const converted = new WeakMap<Feature, Annotation>()
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* The `features` layer as the annotations a GFF would parse to. Every field
|
|
12
|
+
* but the placement is also an attribute, as a string like a GFF value, so an
|
|
13
|
+
* encoding reads `type` or a host's own field the way it reads a GFF column 9
|
|
14
|
+
* key.
|
|
15
|
+
*/
|
|
16
|
+
export function featuresToAnnotations(features: Feature[]): Annotation[] {
|
|
17
|
+
return features.map(feature => {
|
|
18
|
+
const cached = converted.get(feature)
|
|
19
|
+
if (cached) {
|
|
20
|
+
return cached
|
|
21
|
+
}
|
|
22
|
+
const annotation = featureAnnotation(feature)
|
|
23
|
+
converted.set(feature, annotation)
|
|
24
|
+
return annotation
|
|
25
|
+
})
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function featureAnnotation(feature: Feature): Annotation {
|
|
29
|
+
const { row, start, end, name, description, type, strand, color } = feature
|
|
30
|
+
const accession = name ?? `${row}:${start}-${end}`
|
|
31
|
+
const attributes = Object.fromEntries(
|
|
32
|
+
Object.entries(feature)
|
|
33
|
+
.filter(([key, value]) => !placement.has(key) && value !== undefined)
|
|
34
|
+
.map(([key, value]) => [key, String(value)]),
|
|
35
|
+
)
|
|
36
|
+
return {
|
|
37
|
+
id: row,
|
|
38
|
+
accession,
|
|
39
|
+
name: name ?? accession,
|
|
40
|
+
description: description ?? name ?? accession,
|
|
41
|
+
featureType: type,
|
|
42
|
+
start,
|
|
43
|
+
end,
|
|
44
|
+
strand: strand === '+' ? 1 : strand === '-' ? -1 : undefined,
|
|
45
|
+
color,
|
|
46
|
+
attributes,
|
|
47
|
+
}
|
|
48
|
+
}
|
package/src/hierarchy.ts
CHANGED
|
@@ -25,16 +25,16 @@ export interface HierarchyNode<T = NodeWithIds> extends CoreHierarchyNode<T> {
|
|
|
25
25
|
parent: HierarchyNode<T> | null
|
|
26
26
|
x?: number
|
|
27
27
|
y?: number
|
|
28
|
-
// the span of `x` over this node's subtree
|
|
29
|
-
//
|
|
30
|
-
//
|
|
28
|
+
// the span of `x` over this node's subtree. Leaves are laid out in order, so
|
|
29
|
+
// a subtree occupies one contiguous run of rows and a renderer can skip the
|
|
30
|
+
// whole thing when the run misses its block.
|
|
31
31
|
xMin?: number
|
|
32
32
|
xMax?: number
|
|
33
33
|
len?: number
|
|
34
34
|
depthToLeaf?: number
|
|
35
35
|
_children?: HierarchyNode<T>[] | null
|
|
36
36
|
// pixel x-position of the farthest tip of a collapsed subtree, where the base
|
|
37
|
-
// of the collapsed-clade triangle goes
|
|
37
|
+
// of the collapsed-clade triangle goes
|
|
38
38
|
collapsedTipXFar?: number
|
|
39
39
|
}
|
|
40
40
|
|
|
@@ -111,6 +111,153 @@ export function clusterLayout<T>(
|
|
|
111
111
|
}
|
|
112
112
|
}
|
|
113
113
|
|
|
114
|
+
export interface TreeScale {
|
|
115
|
+
readonly rowHeight: number
|
|
116
|
+
readonly treeWidth: number
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
interface LayoutFrame {
|
|
120
|
+
scale: TreeScale
|
|
121
|
+
rootToTipLength: number
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* A node of `layoutTree`: its source node's fields plus its place in unit
|
|
126
|
+
* space, rows down and branch length across. The pixel getters multiply by the
|
|
127
|
+
* scale on every read, so a zoom leaves the nodes valid, and an observer
|
|
128
|
+
* reading `x` observes whatever the scale reads.
|
|
129
|
+
*/
|
|
130
|
+
export class LaidOutNode<T> implements HierarchyNode<T> {
|
|
131
|
+
data: T
|
|
132
|
+
depth: number
|
|
133
|
+
height: number
|
|
134
|
+
value?: number
|
|
135
|
+
children: LaidOutNode<T>[] | null
|
|
136
|
+
depthToLeaf = 0
|
|
137
|
+
row = 0
|
|
138
|
+
rowMin = 0
|
|
139
|
+
rowMax = 0
|
|
140
|
+
depthFraction = 0
|
|
141
|
+
branchLength = 0
|
|
142
|
+
collapsedBranchLength?: number
|
|
143
|
+
|
|
144
|
+
constructor(
|
|
145
|
+
source: HierarchyNode<T>,
|
|
146
|
+
readonly parent: LaidOutNode<T> | null,
|
|
147
|
+
private readonly frame: LayoutFrame,
|
|
148
|
+
) {
|
|
149
|
+
this.data = source.data
|
|
150
|
+
this.depth = source.depth
|
|
151
|
+
this.height = source.height
|
|
152
|
+
this.value = source.value
|
|
153
|
+
this.children = source.children ? [] : null
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
private get pxPerBranchLength() {
|
|
157
|
+
const { scale, rootToTipLength } = this.frame
|
|
158
|
+
return rootToTipLength ? scale.treeWidth / rootToTipLength : 0
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
get x() {
|
|
162
|
+
return this.row * this.frame.scale.rowHeight
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
get xMin() {
|
|
166
|
+
return this.rowMin * this.frame.scale.rowHeight
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
get xMax() {
|
|
170
|
+
return this.rowMax * this.frame.scale.rowHeight
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
get y() {
|
|
174
|
+
return this.depthFraction * this.frame.scale.treeWidth
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
get len() {
|
|
178
|
+
return this.branchLength * this.pxPerBranchLength
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
get collapsedTipXFar() {
|
|
182
|
+
return this.collapsedBranchLength === undefined
|
|
183
|
+
? undefined
|
|
184
|
+
: this.collapsedBranchLength * this.pxPerBranchLength
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* The layout `clusterLayout` and `setBrLength` write, on new nodes: tips one
|
|
190
|
+
* row apart, a parent at the mean of its children, and branch length measured
|
|
191
|
+
* from the root, whose own branch is not drawn. A collapsed node records the
|
|
192
|
+
* farthest tip of its hidden subtree, where the base of its triangle goes.
|
|
193
|
+
*/
|
|
194
|
+
export function layoutTree<T extends { length?: number }>(
|
|
195
|
+
root: HierarchyNode<T>,
|
|
196
|
+
scale: TreeScale,
|
|
197
|
+
) {
|
|
198
|
+
const frame: LayoutFrame = { scale, rootToTipLength: 0 }
|
|
199
|
+
const nodes: LaidOutNode<T>[] = []
|
|
200
|
+
const leafNodes: LaidOutNode<T>[] = []
|
|
201
|
+
const rootHeight = root.height
|
|
202
|
+
const stack: { source: HierarchyNode<T>; parent: LaidOutNode<T> | null }[] = [
|
|
203
|
+
{ source: root, parent: null },
|
|
204
|
+
]
|
|
205
|
+
while (stack.length > 0) {
|
|
206
|
+
const { source, parent } = stack.pop()!
|
|
207
|
+
const node = new LaidOutNode(source, parent, frame)
|
|
208
|
+
nodes.push(node)
|
|
209
|
+
node.depthFraction =
|
|
210
|
+
rootHeight === 0 ? 1 : (source.depth - root.depth) / rootHeight
|
|
211
|
+
if (parent) {
|
|
212
|
+
parent.children!.push(node)
|
|
213
|
+
node.branchLength =
|
|
214
|
+
parent.branchLength + Math.max(source.data.length || 0, 0)
|
|
215
|
+
frame.rootToTipLength = Math.max(frame.rootToTipLength, node.branchLength)
|
|
216
|
+
}
|
|
217
|
+
if (source._children) {
|
|
218
|
+
node.collapsedBranchLength =
|
|
219
|
+
node.branchLength + collapsedSubtreeMaxLength(source)
|
|
220
|
+
}
|
|
221
|
+
if (source.children) {
|
|
222
|
+
for (let i = source.children.length - 1; i >= 0; i--) {
|
|
223
|
+
stack.push({ source: source.children[i]!, parent: node })
|
|
224
|
+
}
|
|
225
|
+
} else {
|
|
226
|
+
node.row = leafNodes.length + 0.5
|
|
227
|
+
node.depthToLeaf = source.depthToLeaf ?? 0
|
|
228
|
+
leafNodes.push(node)
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
for (let i = nodes.length - 1; i >= 0; i--) {
|
|
233
|
+
const node = nodes[i]!
|
|
234
|
+
if (node.children) {
|
|
235
|
+
let sum = 0
|
|
236
|
+
let min = Infinity
|
|
237
|
+
let max = -Infinity
|
|
238
|
+
let depthToLeaf = 0
|
|
239
|
+
for (const child of node.children) {
|
|
240
|
+
sum += child.row
|
|
241
|
+
min = Math.min(min, child.rowMin)
|
|
242
|
+
max = Math.max(max, child.rowMax)
|
|
243
|
+
depthToLeaf = Math.max(depthToLeaf, 1 + child.depthToLeaf)
|
|
244
|
+
}
|
|
245
|
+
node.row = sum / node.children.length
|
|
246
|
+
node.rowMin = min
|
|
247
|
+
node.rowMax = max
|
|
248
|
+
node.depthToLeaf = depthToLeaf
|
|
249
|
+
} else {
|
|
250
|
+
node.rowMin = node.row
|
|
251
|
+
node.rowMax = node.row
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
return {
|
|
255
|
+
root: nodes[0]!,
|
|
256
|
+
leaves: leafNodes,
|
|
257
|
+
rootToTipLength: frame.rootToTipLength,
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
114
261
|
export function collapse<T>(node: HierarchyNode<T>) {
|
|
115
262
|
if (node.children) {
|
|
116
263
|
// memoize the real subtree depth onto the node before detaching its
|
package/src/index.ts
CHANGED
|
@@ -36,7 +36,7 @@ export type { MsaSvgFigure } from './useMsaSvgFigure.ts'
|
|
|
36
36
|
export type { MountedViewer } from './mount.tsx'
|
|
37
37
|
export { type MsaViewModel, default as MSAModelF } from './model.ts'
|
|
38
38
|
export type { MSAFormat, MSAParserType } from 'msa-parsers'
|
|
39
|
-
export type { HierarchyNode } from './hierarchy.ts'
|
|
39
|
+
export type { HierarchyNode, LaidOutNode, TreeScale } from './hierarchy.ts'
|
|
40
40
|
// InterProScanResults is the EBI wire format that setDomains still accepts;
|
|
41
41
|
// Annotation is the source-agnostic shape everything else is written against
|
|
42
42
|
export type { InterProScanResults } from 'msa-parsers'
|
|
@@ -60,13 +60,16 @@ export type {
|
|
|
60
60
|
Cell,
|
|
61
61
|
Clade,
|
|
62
62
|
CladeMark,
|
|
63
|
+
ColorScheme,
|
|
63
64
|
ColumnTrackSpec,
|
|
64
65
|
DomainBand,
|
|
65
66
|
Encoding,
|
|
66
67
|
EncodingChannel,
|
|
68
|
+
Feature,
|
|
67
69
|
Highlight,
|
|
68
70
|
Legend,
|
|
69
71
|
LegendEntry,
|
|
72
|
+
MsaSelection,
|
|
70
73
|
Node,
|
|
71
74
|
MappedStructure,
|
|
72
75
|
NodeWithIds,
|
|
@@ -80,12 +83,16 @@ export type {
|
|
|
80
83
|
ResolvedEncoding,
|
|
81
84
|
ResolvedHighlight,
|
|
82
85
|
ResolvedRowPanel,
|
|
86
|
+
ResolvedSelection,
|
|
87
|
+
RowDataInput,
|
|
83
88
|
RowPanelSpec,
|
|
84
89
|
RowResidue,
|
|
85
90
|
StructureResidue,
|
|
86
91
|
TextTrackModel,
|
|
87
92
|
TidyDomainAnnotation,
|
|
88
93
|
TrackKind,
|
|
94
|
+
TreeOrder,
|
|
95
|
+
TreeRoot,
|
|
89
96
|
UnshareableData,
|
|
90
97
|
Viewport,
|
|
91
98
|
} from './types.ts'
|
package/src/model/DataModel.ts
CHANGED
|
@@ -9,7 +9,7 @@ import { maxInlineSnapshotBytes } from '../constants.ts'
|
|
|
9
9
|
* data will not be persisted in saved session snapshots, it will be fetched
|
|
10
10
|
* from msaFilehandle at startup
|
|
11
11
|
*/
|
|
12
|
-
export function DataModelF() {
|
|
12
|
+
export function DataModelF(maxInlineBytes = maxInlineSnapshotBytes) {
|
|
13
13
|
return types
|
|
14
14
|
.model({
|
|
15
15
|
/**
|
|
@@ -56,12 +56,12 @@ export function DataModelF() {
|
|
|
56
56
|
},
|
|
57
57
|
}))
|
|
58
58
|
.postProcessSnapshot(snap =>
|
|
59
|
-
// drops any field over
|
|
59
|
+
// drops any field over maxInlineBytes; the parent model's
|
|
60
60
|
// `unshareableData` reports what was dropped
|
|
61
61
|
Object.fromEntries(
|
|
62
62
|
Object.entries(snap).map(([key, text]) => [
|
|
63
63
|
key,
|
|
64
|
-
text && text.length >
|
|
64
|
+
text && text.length > maxInlineBytes ? undefined : text,
|
|
65
65
|
]),
|
|
66
66
|
),
|
|
67
67
|
)
|
package/src/model/msaModel.ts
CHANGED
|
@@ -29,6 +29,13 @@ export function MSAModelF() {
|
|
|
29
29
|
*/
|
|
30
30
|
colorSchemeName: stripDefault(types.string, defaultColorSchemeName),
|
|
31
31
|
|
|
32
|
+
/**
|
|
33
|
+
* #property
|
|
34
|
+
* a color per residue letter, which replaces the `colorSchemeName` table
|
|
35
|
+
* while set. A letter the map leaves out takes no color
|
|
36
|
+
*/
|
|
37
|
+
customColorScheme: types.frozen<Record<string, string> | undefined>(),
|
|
38
|
+
|
|
32
39
|
/**
|
|
33
40
|
* #property
|
|
34
41
|
* show a tooltip with the hovered column's value for a track while
|
|
@@ -49,10 +56,21 @@ export function MSAModelF() {
|
|
|
49
56
|
.actions(self => ({
|
|
50
57
|
/**
|
|
51
58
|
* #action
|
|
52
|
-
*
|
|
59
|
+
* pick a scheme from the built-in table, which clears
|
|
60
|
+
* `customColorScheme`
|
|
53
61
|
*/
|
|
54
62
|
setColorSchemeName(name: string) {
|
|
55
63
|
self.colorSchemeName = name
|
|
64
|
+
self.customColorScheme = undefined
|
|
65
|
+
},
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* #action
|
|
69
|
+
* color residues from a map of letter to color, or pass undefined to
|
|
70
|
+
* return to `colorSchemeName`
|
|
71
|
+
*/
|
|
72
|
+
setCustomColorScheme(map?: Record<string, string>) {
|
|
73
|
+
self.customColorScheme = map
|
|
56
74
|
},
|
|
57
75
|
|
|
58
76
|
/**
|
package/src/model/treeModel.ts
CHANGED
|
@@ -8,12 +8,16 @@ import {
|
|
|
8
8
|
defaultLabelsAlignRight,
|
|
9
9
|
defaultOverviewHeight,
|
|
10
10
|
defaultShowBranchLen,
|
|
11
|
+
defaultTreeOrder,
|
|
11
12
|
defaultShowTreeOverview,
|
|
12
13
|
defaultTreeAreaWidth,
|
|
13
14
|
defaultTreeWidth,
|
|
15
|
+
treeOrders,
|
|
14
16
|
} from '../constants.ts'
|
|
15
17
|
import { stripDefault } from '../stripDefault.ts'
|
|
16
18
|
|
|
19
|
+
import type { TreeOrder } from '../types.ts'
|
|
20
|
+
|
|
17
21
|
/**
|
|
18
22
|
* #stateModel Tree
|
|
19
23
|
*/
|
|
@@ -51,6 +55,17 @@ export function TreeModelF() {
|
|
|
51
55
|
*/
|
|
52
56
|
showBranchLen: stripDefault(types.boolean, defaultShowBranchLen),
|
|
53
57
|
|
|
58
|
+
/**
|
|
59
|
+
* #property
|
|
60
|
+
* the order each node's children draw in: `branchLength` (shortest
|
|
61
|
+
* first), `input` (as the file gives them), `ladderize` (fewest tips
|
|
62
|
+
* first) or `ladderizeReverse` (most tips first)
|
|
63
|
+
*/
|
|
64
|
+
treeOrder: stripDefault(
|
|
65
|
+
types.enumeration<TreeOrder>('TreeOrder', [...treeOrders]),
|
|
66
|
+
defaultTreeOrder,
|
|
67
|
+
),
|
|
68
|
+
|
|
54
69
|
/**
|
|
55
70
|
* #property
|
|
56
71
|
* draw tree, boolean
|
|
@@ -133,6 +148,13 @@ export function TreeModelF() {
|
|
|
133
148
|
self.showBranchLen = arg
|
|
134
149
|
},
|
|
135
150
|
|
|
151
|
+
/**
|
|
152
|
+
* #action
|
|
153
|
+
*/
|
|
154
|
+
setTreeOrder(order: TreeOrder) {
|
|
155
|
+
self.treeOrder = order
|
|
156
|
+
},
|
|
157
|
+
|
|
136
158
|
/**
|
|
137
159
|
* #action
|
|
138
160
|
*/
|