Files
AtreidesAssetsFree/client/pages/guide.js
T
2026-10-29 11:57:29 +01:00

149 lines
6.5 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Generated, do not edit. sha256:a22e862b7b1defbcc74c1844981b5027dbd831d7e549f1dff573576ab6b2f26e
import { t, locale } from '../i18n/index.js';
import { ROUTE_KEYS, TARGETS, PLACE_LABELS, targetHash } from '../../shared/config.js';
import { GUIDE_SECTIONS, GUIDE_GROUPS, splitPlaceTokens, guideText } from '../../shared/guide.js';
import { create as createTabs } from '../ui/tabs.js';
import { esc } from '../ui/escape.js';
import { CHEVRON_RIGHT, ADJUST } from '../ui/icons.js';
import { GUIDE_FIGURE_DRAWINGS } from '../ui/guide-figures.js';
import { placeAvailable } from '../routes.js';
export const title = () => t('guide.title');
export const headerInColumn = true;
const ROUTE = ROUTE_KEYS.GUIDE;
const IDS = GUIDE_SECTIONS.map(s => s.id);
const BY_ID = new Map(GUIDE_SECTIONS.map(s => [s.id, s]));
const GROUP_OF = new Map(GUIDE_GROUPS.flatMap(g => g.sections.map(id => [id, g.id])));
const texts = () => guideText(locale());
const titleOf = id => texts().sections[id].title;
const shown = id => { const p = BY_ID.get(id)?.place; return !p || placeAvailable(TARGETS[p]); };
const parseHash = () => (location.hash || '').replace(/^#\/?/, '').split('/')[1] || null;
const hashFor = id => targetHash({ route: ROUTE, tab: id });
const segLabel = id => { try { return t(PLACE_LABELS[id] || `nav.${id}`); } catch { return id; } };
const segsOf = key => { const { route, tab, sub } = TARGETS[key]; return [route, tab, sub].filter(Boolean); };
const placeName = key => segLabel(segsOf(key).at(-1));
function placeNameUnique(key) {
const naam = placeName(key);
const dubbel = Object.keys(TARGETS).some(k => k !== key && placeName(k) === naam);
if (!dubbel) return naam;
const segs = segsOf(key);
return `${segLabel(segs.at(-2) ?? segs[0])} › ${naam}`;
}
const placeLink = (key, label, cls = '', after = '') =>
`<a${cls ? ` class="${cls}"` : ''} href="${esc(targetHash(TARGETS[key]))}" target="_blank" rel="noopener" `
+ `title="${esc(t('guide.newTab'))}">${esc(label)}${after}</a>`;
const ICONS = Object.freeze({ adjust: ADJUST });
function inline(text) {
return splitPlaceTokens(text).map(part => {
if (typeof part === 'string') return esc(part);
if (part.icon) return ICONS[part.icon] ? `<span class="guide-icon">${ICONS[part.icon]}</span>` : '';
return placeAvailable(TARGETS[part.place]) ? placeLink(part.place, placeName(part.place)) : esc(placeName(part.place));
}).join('');
}
const lead = (term, text) => `<strong>${esc(term)}</strong> ${inline(text)}`;
function block(b) {
if (typeof b === 'string') return `<p>${inline(b)}</p>`;
if (b.heading) return `<h3 class="guide-h">${esc(b.heading)}</h3>`;
if (b.code) return `<pre class="guide-code"><code>${esc(b.code)}</code></pre>`;
if (b.note) return `<div class="guide-note">${inline(b.note)}</div>`;
if (b.figure) {
const draw = GUIDE_FIGURE_DRAWINGS[b.figure];
return draw ? `<figure style="margin:0.4rem 0 1.2rem">`
+ `<div style="overflow-x:auto;padding:0.8rem;border:1px solid var(--border-mid);border-radius:var(--radius-sm)">${draw(b.caption)}</div>`
+ `<figcaption style="margin-top:0.4rem;font-size:0.85rem;color:var(--text-sec)">${inline(b.caption)}</figcaption></figure>` : '';
}
if (b.list && b.list.every(Array.isArray)) {
return `<dl class="guide-terms">${b.list.map(([term, text]) => `<dt>${esc(term)}</dt><dd>${inline(text)}</dd>`).join('')}</dl>`;
}
if (b.term) return `<p>${lead(b.term, b.text)}</p>`;
if (b.list) {
return `<ul>${b.list.map(item => `<li>${Array.isArray(item) ? lead(item[0], item[1]) : inline(item)}</li>`).join('')}</ul>`;
}
return '';
}
function sectionHtml(s) {
const naar = s.place
? placeLink(s.place, t('guide.goTo', { plek: placeNameUnique(s.place) }), 'btn btn-ghost btn-sm guide-goto', CHEVRON_RIGHT)
: '';
const related = s.related.filter(shown);
const zie = related.length
? `<div class="guide-related"><span class="t-label">${esc(t('guide.related'))}</span> ${related
.map(id => `<a href="${esc(hashFor(id))}" data-section="${esc(id)}">${esc(titleOf(id))}</a>`).join(', ')}</div>`
: '';
return `
<article class="guide-section">
<h2 class="t-h3">${esc(titleOf(s.id))}</h2>
<div class="t-body guide-body">${texts().sections[s.id].body.map(block).join('')}</div>
${naar || zie ? `<footer class="guide-foot">${zie}${naar}</footer>` : ''}
</article>`;
}
export async function render(el) {
const groups = GUIDE_GROUPS.map(g => ({ ...g, sections: g.sections.filter(shown) })).filter(g => g.sections.length);
let current = parseHash();
if (!IDS.includes(current) || !shown(current)) {
current = groups[0].sections[0];
history.replaceState(null, '', hashFor(current));
}
el.innerHTML = `
<div class="page-layout">
<aside class="page-aside guide-aside">
<div class="guide-groups" data-group-bar></div>
<nav class="tabset guide-sections" role="tablist" data-sections></nav>
</aside>
<div class="page-body">
<div class="page-header"><h1 class="page-title">${esc(title())}</h1></div>
<div data-groups></div>
</div>
</div>`;
const menu = el.querySelector('[data-sections]');
let panel = null;
let ready = false;
const show = (sid) => {
current = sid;
panel.innerHTML = sectionHtml(BY_ID.get(sid));
menu.querySelectorAll('[data-section]').forEach(b => {
const on = b.dataset.section === sid;
b.classList.toggle('is-on', on);
b.setAttribute('aria-selected', String(on));
});
if (ready && location.hash !== hashFor(sid)) history.pushState(null, '', hashFor(sid));
};
const tabs = createTabs(el.querySelector('[data-groups]'), {
barHost: el.querySelector('[data-group-bar]'),
active: GROUP_OF.get(current),
items: groups.map(g => ({ id: g.id, label: esc(texts().groups[g.id]) })),
render: (gid, host) => {
panel = host;
const groep = groups.find(g => g.id === gid);
menu.innerHTML = groep.sections.map(sid => `
<button type="button" class="tabset-tab" role="tab" data-section="${esc(sid)}">${esc(titleOf(sid))}</button>`).join('');
show(GROUP_OF.get(current) === gid ? current : groep.sections[0]);
},
});
ready = true;
el.querySelector('.page-layout').onclick = e => {
const doel = e.target.closest('[data-section]');
if (!doel || !BY_ID.has(doel.dataset.section)) return;
e.preventDefault();
const sid = doel.dataset.section;
if (tabs && GROUP_OF.get(sid) !== tabs.active) { current = sid; tabs.select(GROUP_OF.get(sid)); }
else show(sid);
};
}