import type { FolderNode, TreeNode } from "#types/folder.types"; import { LINE_INFIX, SOURCE_LOADED_EVENT, TREE_LINK_SELECTOR } from "#configuration/constants/anatomy.constants"; import { appendChildren, createElement } from "#core/factories/element.factory"; import { buildTree, flatten, markSelected, renderTree } from "#presentation/components/folder.component"; import { ANATOMY_PAGE } from "#core/ids/page.ids"; import { HREF_ATTRIBUTE } from "#configuration/constants/element.constants"; import { NODE_REQUESTED } from "#core/ids/walk.ids"; import { TREE_TAB } from "#core/ids/anatomy.ids"; import type { Tab } from "#types/document.types"; import { isStaticRender } from "#core/predicates/document.predicate"; import { renderNode } from "#presentation/renderers/folder.renderer"; import { resolveTarget } from "#domain/loaders/anatomy.loader"; import { revealLine } from "#presentation/components/code.component"; import { subscribeEvent } from "#core/buses/base.bus"; import { tabLink } from "#assets/link.assets"; import { targetOf } from "#core/converters/folder.converter"; const CLICK_EVENT = "click"; const revealWhenLoaded = function revealWhenLoaded(view: Element, line: number): void { if (revealLine(view, line)) { return; } view.addEventListener( SOURCE_LOADED_EVENT, () => { revealLine(view, line); }, { once: true }, ); }; const enableSelection = function enableSelection( article: Element, tree: Element, view: Element, root: FolderNode, nodes: ReadonlyMap, ): void { const built = new Map(); const select = function select(node: TreeNode, line: number | null): void { const element = built.get(node.id) ?? renderNode(root, node); built.set(node.id, element); view.replaceChildren(element); markSelected(tree, node.id); if (line !== null) { revealWhenLoaded(view, line); } }; const onClick = function onClick(event: Event): void { if (!article.isConnected) { document.removeEventListener(CLICK_EVENT, onClick, true); return; } const link = event.target instanceof Element ? event.target.closest(TREE_LINK_SELECTOR) : null; const href = link === null ? null : link.getAttribute(HREF_ATTRIBUTE); const requested = href === null ? null : targetOf(href); const target = requested === null ? null : resolveTarget(requested); const node = target === null ? undefined : nodes.get(target.id); if (node === undefined || href === null || target === null) { return; } event.preventDefault(); event.stopPropagation(); window.history.replaceState({}, "", href); select(node, target.line); }; document.addEventListener(CLICK_EVENT, onClick, true); const release = subscribeEvent(NODE_REQUESTED, (event) => { if (!article.isConnected) { release(); return; } const node = nodes.get(event.id); if (node === undefined) { return; } const suffix = event.line === null ? "" : LINE_INFIX + String(event.line); window.history.replaceState({}, "", tabLink(ANATOMY_PAGE, TREE_TAB, event.id + suffix)); select(node, event.line); }); const hashed = targetOf(window.location.hash); const requested = hashed === null ? null : resolveTarget(hashed); const node = requested === null ? undefined : nodes.get(requested.id); select(node ?? root, node === undefined || requested === null ? null : requested.line); }; export const renderFilesystem = function renderFilesystem(tab: Tab): Element { const root = buildTree(tab.sections); const article = createElement("article", { className: "filesystem" }); if (root === null) { return article; } const nodes = new Map(flatten(root).map((node) => [node.id, node] as const)); const tree = renderTree(root); const view = createElement("div", { className: "filesystem-view" }); article.append(createElement("h1", { className: "explorer-title", text: tab.label })); article.append(createElement("div", { children: [tree, view], className: "filesystem-body" })); if (isStaticRender()) { appendChildren( view, [...nodes.values()].map((node) => renderNode(root, node)), ); markSelected(tree, root.id); return article; } enableSelection(article, tree, view, root, nodes); return article; };