Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 | 2x 2x 2x 2x 86x 86x 8x 86x 8x 2x 2x 342x 342x 342x 342x | /**
* The place the pointer is on, wherever it is (ยง25).
*
* A store rather than a callback threaded through the panels, for the reason
* `ui/readout.ts` is one: the surface that produces the place and the surfaces
* that light it are on opposite sides of the app, and neither should have to
* know the other exists. A map cell names *two* places โ a contact is a pair โ
* and a track, a bead or the 1D browser's axis names one, so the store carries
* one or two loci and says which surface they came from, and every subscriber
* ignores its own.
*
* The deck's own cell-to-cell echo (`panels/PanelDeck.ts`) is untouched by
* this and deliberately so: it carries a pointer between maps in cells,
* converted through base pairs, and a structure has no cells to convert into.
* What a structure needs is the place itself, which is what this carries โ the
* same decision the deck took, one level up.
*/
import type { Locus } from '../render/locus'
export interface HoverPlace {
/** One place, or the two ends of a contact. */
loci: readonly [Locus] | readonly [Locus, Locus]
/**
* Who is pointing: `map` for any contact map (the deck already echoes
* between maps), `browser` for the 1D browser, or a structure cell's own id.
*/
source: string
}
/** What a contact map calls itself when it publishes the place under its pointer. */
export const HOVER_SOURCE_MAP = 'map'
let current: HoverPlace | null = null
const listeners = new Set<(place: HoverPlace | null) => void>()
/** Publish where the pointer is, or `null` for nowhere. */
export function setHoverPlace(place: HoverPlace | null): void {
if (place === null && current === null) return
if (place !== null && current !== null && samePlace(place, current)) return
current = place
for (const listener of listeners) listener(current)
}
/** Where the pointer is now. */
export function getHoverPlace(): HoverPlace | null {
return current
}
/** Subscribe, receiving the current value immediately. Returns the teardown. */
export function subscribeHoverPlace(listener: (place: HoverPlace | null) => void): () => void {
listeners.add(listener)
listener(current)
return () => listeners.delete(listener)
}
function samePlace(a: HoverPlace, b: HoverPlace): boolean {
if (a.source !== b.source || a.loci.length !== b.loci.length) return false
return a.loci.every((locus, at) => {
const other = b.loci[at]
return other !== undefined && other.contig === locus.contig && other.bp === locus.bp
})
}
|