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 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 | 1x 1x 1x 32x 32x 32x 32x 1x 57x 57x 57x 57x 57x 57x 57x 57x 57x 57x 57x 57x 1x 76x 76x 76x 76x | /**
* What the tab strip remembers between visits.
*
* `sessionStorage`, like everything else the app remembers that is not a
* preference (`state/UiMemory.ts`, spec §8.6): a set of open files is a train of
* thought and it belongs to the window it was opened in, not to the browser
* profile. Closing the tab is meant to end it.
*
* What is stored is a **serialised view state per session, and which one was
* active** — nothing else. That is deliberately the same string the address bar
* carries, so a remembered tab and a shared link are the same kind of thing and
* there is one parser for both. It also bounds what a restore can go wrong
* about: a stored entry is either a legal view state or it is dropped.
*
* A restored tab is *named*, not opened. A remote file re-opens itself when the
* tab is clicked; a local one cannot — no browser re-reads a dropped file
* without a fresh gesture — and comes back as the picker with the link's own
* explanation, which is the behaviour a pasted link to a local file has always
* had.
*/
const KEY = 'chromosomix:sessions'
export interface SessionSnapshot {
id: string
/** The session's view state, serialised exactly as the URL hash carries it. */
hash: string
}
export interface StoredSessions {
sessions: SessionSnapshot[]
/** The id that was on screen, or `null` when nothing was. */
active: string | null
}
const EMPTY: StoredSessions = { sessions: [], active: null }
export function writeSessions(value: StoredSessions): void {
try {
window.sessionStorage.setItem(KEY, JSON.stringify(value))
} catch {
/* a browser that refuses storage still runs; it just starts fresh */
}
}
/**
* What a previous visit left, validated.
*
* Untrusted input, exactly like the URL hash: stored state outlives the build
* that wrote it, so every field is checked and anything that fails is dropped
* rather than repaired into a tab pointing at nothing.
*/
export function readSessions(): StoredSessions {
let raw: unknown
try {
const text = window.sessionStorage.getItem(KEY)
if (text === null) return EMPTY
raw = JSON.parse(text)
} catch {
return EMPTY
}
if (typeof raw !== 'object' || raw === null) return EMPTY
const record = raw as Record<string, unknown>
const list = Array.isArray(record['sessions']) ? record['sessions'] : []
const sessions: SessionSnapshot[] = []
const seen = new Set<string>()
for (const entry of list) {
if (typeof entry !== 'object' || entry === null) continue
const row = entry as Record<string, unknown>
const id = row['id']
const hash = row['hash']
if (typeof id !== 'string' || typeof hash !== 'string' || id === '' || hash === '') continue
// Two tabs with one id would share every scope keyed on it — their tracks,
// their filters, their marks. A duplicate is corruption, not a session.
if (seen.has(id)) continue
seen.add(id)
sessions.push({ id, hash })
}
const active = record['active']
return {
sessions,
active: typeof active === 'string' && seen.has(active) ? active : null,
}
}
/**
* The number a fresh session id must start above, so a restored `s3` and a new
* one cannot collide.
*
* Ids are minted per page load and the counter starts at 1, so restoring three
* tabs and opening a fourth would otherwise hand out `s1` twice.
*/
export function highestId(sessions: readonly SessionSnapshot[]): number {
let high = 0
for (const session of sessions) {
const match = /^s(\d+)$/.exec(session.id)
if (match !== null) high = Math.max(high, Number(match[1]))
}
return high
}
|