import { html } from 'htl'; import type { navigation } from './navigation'; export type PageData = { page: number; nav: ReturnType }; type PageProp = | string | Element | ((data: PageData, $holder: HTMLDivElement) => string | Element | void) | undefined; export type PageObject = { template?: 'title' | 'full'; content?: PageProp; footer?: PageProp; background?: PageProp; [key: string]: PageProp; }; type PageElement = HTMLElement & { $title: HTMLElement; $content: HTMLElement; $footer: HTMLElement; render: () => void; }; export const defaultFooter = ({ page, nav }: PageData) => { const $number = html`${page}`; const $range = html``; const $form = html``; $form.addEventListener('pointerup', (e) => e.stopPropagation()); $form.addEventListener('change', (e) => { e.stopPropagation(); nav.page($range.valueAsNumber - 1); $range.valueAsNumber = page; $number.innerHTML = '' + page; }); $range.addEventListener('input', () => { $number.innerHTML = $range.value; }); return $form; }; function SimplePage( { template = 'full', ...props }: PageObject, data?: T ): PageElement { const $title = html`

${resolve(props.title, data)}

`; const $content = html`
`; const $footer = html``; const $background = html`
`; const $page = html`
${$background}
${$title}${$content}${$footer}
`; $page.classList.toggle('page-centered', template === 'title'); $content.classList.toggle('page-full', template === 'full'); const $RenderSimplePage = Object.assign($page, { $title, $content, $footer, render() { const $el = resolve(props.content, data, $content); const $bg = resolve(props.background, data, $background); if ($el) $content.append($el); if ($bg) $background.append($bg); $RenderSimplePage.render = () => {}; }, }); return $RenderSimplePage; } export type PageState = PageObject | ((data?: D) => PageObject); export function Presentation({ lazy = 2, Template = SimplePage, }: { lazy?: number; Template?: (props: PageObject, data?: any) => PageElement; } = {}) { const cache = new Map, PageElement>(); const history = new Map, number>(); let steps = 0; const $container = html`
`; return Object.assign($container, { load(newState: PageState, data?: T) { // console.log('load', cache.has(newState), data); let currentPage; if (cache.has(newState)) { // checks in cache currentPage = cache.get(newState)!; } else { const props = typeof newState === 'object' ? newState : newState(data); currentPage = Template(props, data); cache.set(newState, currentPage); } if (history.size > 0) // replace $container.insertBefore(currentPage, $container.firstChild); else $container.append(currentPage); // append currentPage.render(); // add to history steps++; history.set(newState, steps); history.forEach((v, props) => { // delete expired if (v < steps - lazy) { $container.removeChild(cache.get(props)!); history.delete(props); cache.delete(props); } }); }, preload(newState: PageState, data?: T) { if (!cache.has(newState)) { // checks in cache const props = typeof newState === 'object' ? newState : newState(data); const page = Template(props, data); cache.set(newState, page); $container.append(page); page.render(); } history.set(newState, steps); // update ranking in history }, }); } export function resolve( res: any, ...rest: [T, ...any] ): null | string | Node { if (!res) return null; if (typeof res === 'string') return res; if (res instanceof Text) return res; if (res instanceof DocumentFragment) return res; if (res instanceof Element) return res; if (res.node && typeof res.node === 'function') return res.node(); return resolve(res(...rest), ...rest); }