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 { renderGlossaryTab, renderLetterJumps } from "#presentation/renderers/glossary.renderer"; import { renderTabSection, sectionLabel, subsectionId, subsectionLabel, } from "#presentation/renderers/section.renderer"; import { COPY_ALL_TITLE } from "#configuration/strings/tab.strings"; import { IGNORED_SELECTORS } from "#configuration/constants/document.constants"; import { animated } from "#presentation/renderers/element.renderer"; import { convertText } from "#core/converters/text.converter"; import { createCopyButton } from "#presentation/components/clipboard.component"; import { observePin } from "#core/observers/tab.observer"; import { renderChapter } from "#presentation/renderers/chapter.renderer"; import { renderExplorer } from "#presentation/renderers/ontology.renderer"; import { renderFilesystem } from "#presentation/renderers/anatomy.renderer"; import { tabPath } from "#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 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 Element> = new Map([ ["chapter", renderChapterContent], ["glossary", renderGlossaryContent], ["ontology", renderExplorerContent], ["tree", renderTreeContent], ]); export const renderContent = function renderContent(layout: TabLayout, tab: Tab): Element { const render = CONTENT_BY_LAYOUT.get(layoutOf(layout, tab)); return render === undefined ? renderTabContent(tab.sections) : render(tab); }; 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") { return footerBar(sectionJumps(tab.sections)); } return renderTabFooter(tab.sections); }; export const renderCopyAll = function renderCopyAll(content: Element): Element { return createCopyButton( () => convertText(content, IGNORED_SELECTORS), "copy-button large copy-all", COPY_ALL_TITLE, ); };