All files / src/ui diagonalLock.ts

90.9% Statements 30/33
83.33% Branches 10/12
100% Functions 5/5
90.9% Lines 30/33

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 65 66                                          2x   2x 2x     2x 279x 34x 34x 34x     34x 279x 279x     2x 8x 8x 8x 8x     8x 8x   2x 2x 2x 2x 2x     2x 127x 127x 127x     2x 30x 30x  
/**
 * Whether the maps are held on their diagonal (§7.2): Space, the button beside
 * the turn, and the palette's *Diagonal lock*.
 *
 * Locked, a map goes onto its diagonal at the zoom it is at and stays there:
 * a drag, a scroll, a pinch and the arrow keys all move it along the line, a
 * sideways scroll slides down it, and an Option-drag draws its box on it as
 * Option and Shift do (`render/heatmap.ts`). Unlocked, a sideways scroll is the
 * columns alone and every gesture goes where the hand does.
 *
 * One switch for every map on screen rather than one per map, for the reason
 * *Refine on zoom*'s is (`ui/refine.ts`): it is a way of reading, and a deck's
 * maps move as one (§16.2), so a lead held on its diagonal beside a companion
 * that was not would be dragged off it by its own neighbour. It outlives a
 * navigation — a step to the next sequence rebuilds the map, and the reader
 * stepping along a genome on its diagonal means the next one on it too — and so
 * it is kept where *Refine on zoom* is, in `localStorage` and not the hash: a
 * link shares what is being looked at, and how the recipient likes to move
 * about in it is not that.
 */
 
const KEY = 'chromosomix:diagonal-lock'
 
let locked: boolean | null = null
const listeners = new Set<(on: boolean) => void>()
 
/** Whether the maps are held on their diagonal. Off until the reader asks. */
export function diagonalLocked(): boolean {
  if (locked === null) {
    try {
      locked = window.localStorage.getItem(KEY) === 'on'
    } catch {
      locked = false
    }
  }
  return locked
}
 
/** Lock or let go, store it, and tell every mounted map. */
export function setDiagonalLocked(on: boolean): void {
  locked = on
  try {
    window.localStorage.setItem(KEY, on ? 'on' : 'off')
  } catch {
    /* a browser that refuses storage still honours the choice for this session */
  }
  for (const listener of [...listeners]) listener(on)
}
 
export function toggleDiagonalLock(): boolean {
  const next = !diagonalLocked()
  setDiagonalLocked(next)
  return next
}
 
/** Hear the lock taken and released; returns the unsubscribe. */
export function subscribeDiagonalLock(listener: (on: boolean) => void): () => void {
  listeners.add(listener)
  return () => listeners.delete(listener)
}
 
/** Forget the stored choice, for a test that wants a reader who never pressed it. */
export function resetDiagonalLock(): void {
  locked = null
}