All files / views/readout rows.ts

100% Statements 22/22
100% Branches 2/2
100% Functions 17/17
100% Lines 21/21

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 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81                                                      1x       128x   1546x 1494x 26x 26x   52x 118x     1x 51x       51x                             1x       128x 1686x 663x   128x 77x 51x       1x   1x 256x  
import { Option } from 'effect'
import type { Html, HtmlBuilder } from 'foldkit/html'
 
import { Message } from '@/message'
import { CursorState, type Model } from '@/model'
import { Button } from '@foldkit/ui'
 
import type { LatLon } from '../../core/domain'
import * as Icon from '../ui/icon'
 
export type CellTone = 'Live' | 'Stale' | 'Cue'
 
/**
 * One position the readout reports. Each row says where its position comes from and how its
 * cells look; the columns (one per coordinate system) are in columns.ts.
 */
export type ReadoutRow = Readonly<{
  id: 'Cursor' | 'Cue'
  label: string
  iconView: (h: HtmlBuilder<Message>) => Html
  /** Whether the row applies at all; the cursor row needs a pointer that can hover. */
  isAvailable: (model: Model) => boolean
  point: (model: Model) => Option.Option<LatLon.LatLon>
  tone: (model: Model) => CellTone
  actionView: (model: Model, h: HtmlBuilder<Message>) => Html
}>
 
const cursorRow: ReadoutRow = {
  id: 'Cursor',
  label: 'Cursor',
  iconView: Icon.cross,
  isAvailable: model => model.pointer === 'Hover',
  point: model =>
    CursorState.match(model.cursor, {
      Idle: () => Option.none(),
      OnMap: ({ point }) => Option.some(point),
      OffMap: ({ point }) => Option.some(point),
    }),
  tone: model => (CursorState.guards.OnMap(model.cursor) ? 'Live' : 'Stale'),
  actionView: (_model, h) => h.empty,
}
 
const clearCueButtonView = (h: HtmlBuilder<Message>): Html =>
  Button.view(
    {
      onClick: Message.ClickedClearCue(),
      toView: attributes =>
        h.button(
          [
            ...attributes.button,
            h.AriaLabel('Clear the cue'),
            h.Title('Clear the cue'),
            h.Class(
              'ml-auto min-h-6 min-w-6 rounded px-1 text-faint outline-hidden hover:text-ink focus-visible:ring-2 focus-visible:ring-focus',
            ),
          ],
          ['×'],
        ),
    },
    h,
  )
 
const cueRow: ReadoutRow = {
  id: 'Cue',
  label: 'Cue',
  iconView: Icon.cue,
  isAvailable: () => true,
  point: model => model.maybeCue,
  tone: () => 'Cue',
  actionView: (model, h) =>
    Option.match(model.maybeCue, {
      onNone: () => h.empty,
      onSome: () => clearCueButtonView(h),
    }),
}
 
export const rows: ReadonlyArray<ReadoutRow> = [cursorRow, cueRow]
 
export const availableRows = (model: Model): ReadonlyArray<ReadoutRow> =>
  rows.filter(row => row.isAvailable(model))