|
| 1 | +// Scope help (Help → Show scope help): a "?" in every panel head opens a short card – what the |
| 2 | +// scope is, what it is for, how to read it, one small task to try with the test pattern or a |
| 3 | +// camera, and a common pitfall. Tasks can be ticked off; the count shows what is left to try. |
| 4 | +// Texts: src/i18n/<lang>/help.ts, checked against the sources in docs/research/scope-hilfe.md. |
| 5 | + |
| 6 | +import { checkbox, h, hint, kicker, type Kid } from '../ui'; |
| 7 | +import { hasKey, t, type Key } from '../i18n'; |
| 8 | +import type { ScopeType } from '../graticule'; |
| 9 | + |
| 10 | +const ON_KEY = 'lz-scopes.help'; |
| 11 | +const DONE_KEY = 'lz-scopes.help.done'; |
| 12 | + |
| 13 | +export function helpOn(): boolean { |
| 14 | + try { return localStorage.getItem(ON_KEY) !== '0'; } catch { return true; } |
| 15 | +} |
| 16 | +export function setHelpOn(on: boolean) { |
| 17 | + try { localStorage.setItem(ON_KEY, on ? '1' : '0'); } catch { /* private mode */ } |
| 18 | +} |
| 19 | + |
| 20 | +function doneSet(): Set<string> { |
| 21 | + try { const v = JSON.parse(localStorage.getItem(DONE_KEY) ?? '[]'); return new Set(Array.isArray(v) ? v.map(String) : []); } catch { return new Set(); } |
| 22 | +} |
| 23 | +function setDone(scope: string, on: boolean) { |
| 24 | + const s = doneSet(); if (on) s.add(scope); else s.delete(scope); |
| 25 | + try { localStorage.setItem(DONE_KEY, JSON.stringify([...s])); } catch { /* private mode */ } |
| 26 | +} |
| 27 | + |
| 28 | +const k = (scope: string, part: string) => `help.${scope}.${part}` as Key; |
| 29 | +/** Scopes with a help text (the "?" appears only there). */ |
| 30 | +export const hasHelp = (scope: ScopeType | string) => hasKey(k(scope, 'one')); |
| 31 | + |
| 32 | +/** All scopes that have a task, for the progress count. */ |
| 33 | +export function helpScopes(all: readonly string[]) { return all.filter((s) => hasKey(k(s, 'try'))); } |
| 34 | + |
| 35 | +/** Rows of the help card for one scope (rebuilt each time the popover opens). */ |
| 36 | +export function helpCard(scope: ScopeType, all: readonly string[]): Kid[] { |
| 37 | + if (!hasHelp(scope)) return [hint(t('help.none'))]; |
| 38 | + const lines = (part: string) => (hasKey(k(scope, part)) ? t(k(scope, part)).split('\n').filter(Boolean) : []); |
| 39 | + const done = doneSet(); |
| 40 | + const tasks = helpScopes(all); |
| 41 | + const progress = h('p', { class: 'hint help-progress' }, t('help.progress', { done: tasks.filter((s) => done.has(s)).length, all: tasks.length })); |
| 42 | + return [ |
| 43 | + h('p', { class: 'help-one' }, t(k(scope, 'one'))), |
| 44 | + ...lines('what').map((l) => h('p', {}, l)), |
| 45 | + lines('read').length ? kicker(t('help.read')) : null, |
| 46 | + lines('read').length ? h('ul', { class: 'help-list' }, ...lines('read').map((l) => h('li', {}, l))) : null, |
| 47 | + hasKey(k(scope, 'try')) ? h('div', { class: 'help-try' }, |
| 48 | + kicker(t('help.try')), |
| 49 | + h('p', {}, t(k(scope, 'try'))), |
| 50 | + hasKey(k(scope, 'check')) ? h('p', { class: 'hint' }, t('help.checkPrefix'), ' ', t(k(scope, 'check'))) : null, |
| 51 | + checkbox(done.has(scope), t('help.done'), (v) => { setDone(scope, v); const n = doneSet(); progress.textContent = t('help.progress', { done: tasks.filter((s) => n.has(s)).length, all: tasks.length }); }), |
| 52 | + progress) : null, |
| 53 | + hasKey(k(scope, 'pitfall')) ? h('p', { class: 'help-pitfall' }, h('strong', {}, t('help.pitfall')), ' ', t(k(scope, 'pitfall'))) : null, |
| 54 | + ].filter(Boolean) as Kid[]; |
| 55 | +} |
0 commit comments