presentation/renderers/tab.renderer.ts
presentation/renderers/tab.renderer.ts is a file in Bane's Lab Site. 151 lines of code and 32 definitions.
import { FADE_IN, FADE_IN_UP } from "#configuration/constants/element.constants";
import type { Section, Tab, TabLayout, TabbedMeta } from "#types/document.types";
import { appendChildren, createElement } from "#core/factories/element.factory";
import { createJumpButton, createTabButton, scroller } from "#presentation/components/tab.component";
import { renderChapter, renderChapterIndex, renderChapterSectionOf } from "#presentation/renderers/chapter.renderer";
import { renderExplorer, renderExplorerSection } from "#presentation/renderers/ontology.renderer";
import { renderFilesystem, renderFilesystemSection } from "#presentation/renderers/anatomy.renderer";
import { renderGlossaryTab, renderLetterJumps } from "#presentation/renderers/glossary.renderer";
import {
renderTabSection,
sectionLabel,
subsectionId,
subsectionLabel,
} from "#presentation/renderers/section.renderer";
import { animated } from "#presentation/renderers/element.renderer";
import { isStaticRender } from "#core/predicates/document.predicate";
import { observePin } from "#core/observers/tab.observer";
import { tabPath } from "#core/assets/link.assets";
const DELAY_TITLE = 50;
const DELAY_SUBTITLE = 100;
const DELAY_TABS = 100;
const DELAY_CONTENT = 150;
export const renderTabHeader = function renderTabHeader(meta: TabbedMeta): Element {
const header = createElement("header", { className: "tab-page-header" });
if (meta.emblem !== undefined) {
header.append(
animated(
"img",
{ attributes: { alt: meta.title, src: meta.emblem }, className: "tab-page-emblem" },
FADE_IN,
),
);
}
appendChildren(header, [
animated("h1", { className: "tab-page-title", text: meta.title }, FADE_IN_UP, DELAY_TITLE),
animated("p", { className: "tab-page-subtitle", text: meta.subtitle }, FADE_IN, DELAY_SUBTITLE),
]);
return header;
};
export const renderTabs = function renderTabs(page: string, tabs: readonly Tab[], active: string): Element {
const bar = animated("nav", { className: "tab-bar" }, FADE_IN_UP, DELAY_TABS);
appendChildren(
bar,
tabs.map((tab) => createTabButton(page, tab, tab.id === active, tabPath(page, tabs, tab.id))),
);
const anchor = createElement("div", { children: [bar], className: "tab-anchor" });
const main = scroller();
if (main !== null) {
void observePin(main, anchor, bar);
}
return anchor;
};
export const renderTabContent = function renderTabContent(sections: readonly Section[]): Element {
const content = animated("div", { className: "tab-content" }, FADE_IN, DELAY_CONTENT);
appendChildren(content, sections.map(renderTabSection));
return content;
};
const renderChapterContent = function renderChapterContent(tab: Tab): Element {
const content = animated("div", { className: "tab-content chapter-content" }, FADE_IN, DELAY_CONTENT);
content.append(renderChapter(tab));
return content;
};
const renderSectionedContent = function renderSectionedContent(tab: Tab, page: string): Element {
const content = animated("div", { className: "tab-content chapter-content" }, FADE_IN, DELAY_CONTENT);
content.append(isStaticRender() ? renderChapterIndex(tab, page) : renderChapter(tab));
return content;
};
const renderGlossaryContent = function renderGlossaryContent(tab: Tab): Element {
const content = animated("div", { className: "tab-content glossary-content" }, FADE_IN, DELAY_CONTENT);
content.append(renderGlossaryTab(tab));
return content;
};
export const layoutOf = function layoutOf(page: TabLayout, tab: Tab): TabLayout {
return tab.layout ?? page;
};
const renderExplorerContent = function renderExplorerContent(tab: Tab): Element {
const content = animated("div", { className: "tab-content explorer-content" }, FADE_IN, DELAY_CONTENT);
content.append(renderExplorer(tab));
return content;
};
const renderTreeContent = function renderTreeContent(tab: Tab): Element {
const content = animated("div", { className: "tab-content filesystem-content" }, FADE_IN, DELAY_CONTENT);
content.append(renderFilesystem(tab));
return content;
};
const CONTENT_BY_LAYOUT: ReadonlyMap<TabLayout, (tab: Tab, page: string) => Element> = new Map([
["chapter", renderChapterContent],
["glossary", renderGlossaryContent],
["ontology", renderExplorerContent],
["sectioned", renderSectionedContent],
["tree", renderTreeContent],
]);
export const renderContent = function renderContent(layout: TabLayout, tab: Tab, page: string): Element {
const render = CONTENT_BY_LAYOUT.get(layoutOf(layout, tab));
return render === undefined ? renderTabContent(tab.sections) : render(tab, page);
};
const SECTION_BY_LAYOUT: ReadonlyMap<
TabLayout,
(tab: Tab, id: string, rows?: readonly [number, number]) => Element | null
> = new Map([
["ontology", renderExplorerSection],
["sectioned", renderChapterSectionOf],
["tree", renderFilesystemSection],
]);
const SECTION_PAGE_LAYOUTS: ReadonlySet<TabLayout> = new Set<TabLayout>(["ontology", "sectioned"]);
export const hasSectionPages = function hasSectionPages(layout: TabLayout, tab: Tab): boolean {
return SECTION_PAGE_LAYOUTS.has(layoutOf(layout, tab));
};
export const hasOwnSections = function hasOwnSections(layout: TabLayout, tab: Tab): boolean {
return SECTION_BY_LAYOUT.has(layoutOf(layout, tab));
};
export const renderOwnSection = function renderOwnSection(
layout: TabLayout,
tab: Tab,
id: string,
rows?: readonly [number, number],
): Element | null {
return SECTION_BY_LAYOUT.get(layoutOf(layout, tab))?.(tab, id, rows) ?? null;
};
const renderJumps = function renderJumps(section: Section, sectionIndex: number): readonly Element[] {
return [
createJumpButton(sectionLabel(sectionIndex), section.title, section.id),
...section.subsections.map((sub, index) =>
createJumpButton(subsectionLabel(sectionIndex, index), sub.title, subsectionId(section.id, index, sub.id)),
),
];
};
const footerBar = function footerBar(jumps: readonly Element[]): Element {
const bar = createElement("div", { className: "tab-bar" });
appendChildren(bar, jumps);
return createElement("div", { children: [bar], className: "tab-anchor tab-footer" });
};
export const renderTabFooter = function renderTabFooter(sections: readonly Section[]): Element {
return footerBar(sections.flatMap(renderJumps));
};
const sectionJumps = function sectionJumps(sections: readonly Section[]): readonly Element[] {
return sections.map((section, index) => createJumpButton(sectionLabel(index), section.title, section.id));
};
export const renderFooterFor = function renderFooterFor(layout: TabLayout, tab: Tab): Element | null {
const resolved = layoutOf(layout, tab);
if (resolved === "tree") {
return null;
}
if (resolved === "glossary") {
return footerBar(renderLetterJumps(tab));
}
if (resolved === "ontology" || (resolved === "sectioned" && isStaticRender())) {
return footerBar(sectionJumps(tab.sections));
}
return renderTabFooter(tab.sections);
};