presentation/renderers/anatomy.renderer.ts
presentation/renderers/anatomy.renderer.ts is a file in Bane's Lab Site. 169 lines of code and 35 definitions.
import type { FolderNode, TreeNode } from "#types/folder.types";
import {
LINE_INFIX,
NODE_ATTRIBUTE,
SOURCE_LOADED_EVENT,
TREE_LINK_SELECTOR,
} from "#configuration/constants/anatomy.constants";
import { REPOSITORY_LINK_LABEL, repositoryLinkName } from "#configuration/strings/folder.strings";
import { flatten, isFolder, markSelected, renderTree, treeNodesOf } from "#presentation/components/folder.component";
import { nodePath, tabLink } from "#core/assets/link.assets";
import { ANATOMY_PAGE } from "#core/ids/page.ids";
import { HREF_ATTRIBUTE } from "#configuration/constants/element.constants";
import { ICON_BASE_CLASS } from "#configuration/icons/element.icons";
import { NODE_REQUESTED } from "#core/ids/walk.ids";
import { REPOSITORY_ICON } from "#configuration/icons/anatomy.icons";
import type { Tab } from "#types/document.types";
import { createElement } from "#core/factories/element.factory";
import { createExternalLink } from "#presentation/components/link.component";
import { isStaticRender } from "#core/predicates/document.predicate";
import { missingRootLink } from "#configuration/strings/report.strings";
import { renderNode } from "#presentation/renderers/folder.renderer";
import { revealLine } from "#presentation/components/code.component";
import { subscribeEvent } from "#core/buses/base.bus";
import { targetOf } from "#core/converters/folder.converter";
import { treeLabelOf } from "#domain/converters/source.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 },
);
};
interface Explorer {
readonly article: Element;
readonly nodes: ReadonlyMap<string, TreeNode>;
readonly repository: RepositoryLink | null;
readonly root: FolderNode;
readonly tab: string;
readonly tree: Element;
readonly view: Element;
}
interface RepositoryLink {
readonly link: HTMLAnchorElement;
readonly urls: Readonly<Record<string, string>>;
}
const repositoryKey = function repositoryKey(node: TreeNode): string {
return isFolder(node) ? node.section.id : (node.sub.id ?? node.id);
};
const pointRepository = function pointRepository(repository: RepositoryLink | null, node: TreeNode): void {
const url = repository?.urls[repositoryKey(node)];
if (repository !== null && url !== undefined) {
repository.link.setAttribute(HREF_ATTRIBUTE, url);
}
};
const repositoryLink = function repositoryLink(tab: Tab, root: FolderNode): RepositoryLink | null {
const urls = tab.repository;
if (urls === undefined) {
return null;
}
const first = urls[repositoryKey(root)];
if (first === undefined) {
throw new Error(missingRootLink(tab.id));
}
const link = createExternalLink(first, "explorer-repository", [
createElement("i", { className: `${ICON_BASE_CLASS} ${REPOSITORY_ICON}` }),
createElement("span", { text: REPOSITORY_LINK_LABEL }),
]);
link.setAttribute("aria-label", repositoryLinkName(treeLabelOf(tab.id)));
return { link, urls };
};
const enableSelection = function enableSelection(explorer: Explorer): void {
const { article, nodes, repository, root, tab, tree, view } = explorer;
const built = new Map<string, Element>();
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);
pointRepository(repository, node);
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<HTMLAnchorElement>(TREE_LINK_SELECTOR) : null;
const href = link === null ? null : link.getAttribute(HREF_ATTRIBUTE);
const target = href === null ? null : targetOf(href);
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, tab, event.id + suffix));
select(node, event.line);
});
const requested = targetOf(window.location.hash);
const node = requested === null ? undefined : nodes.get(requested.id);
select(node ?? root, node === undefined || requested === null ? null : requested.line);
};
const NODES_BY_TAB = new WeakMap<Tab, { readonly nodes: ReadonlyMap<string, TreeNode>; readonly root: FolderNode }>();
export const renderFilesystemSection = function renderFilesystemSection(tab: Tab, id: string): Element | null {
const held = NODES_BY_TAB.get(tab);
const root = held?.root ?? treeNodesOf(tab.sections, tab.folders ?? {});
if (root === null) {
return null;
}
const nodes = held?.nodes ?? new Map(flatten(root).map((node) => [node.id, node] as const));
NODES_BY_TAB.set(tab, { nodes, root });
const node = nodes.get(id);
return node === undefined ? null : renderNode(root, node);
};
export const renderFilesystem = function renderFilesystem(tab: Tab): Element {
const root = treeNodesOf(tab.sections, tab.folders ?? {});
const article = createElement("article", { className: "filesystem" });
if (root === null) {
return article;
}
const view = createElement("div", { className: "filesystem-view" });
const repository = repositoryLink(tab, root);
const title = createElement("h2", { className: "explorer-title", text: tab.label });
article.append(
repository === null
? title
: createElement("div", { children: [title, repository.link], className: "explorer-title-row" }),
);
if (isStaticRender()) {
view.append(renderNode(root, root));
const paged = new Set(tab.pages ?? []);
for (const link of view.querySelectorAll(`[${NODE_ATTRIBUTE}]`)) {
const node = link.getAttribute(NODE_ATTRIBUTE) ?? "";
if (paged.has(node)) {
link.setAttribute(HREF_ATTRIBUTE, nodePath(ANATOMY_PAGE, tab.id, node));
}
}
article.append(createElement("div", { children: [view], className: "filesystem-body" }));
return article;
}
const tree = renderTree(root);
article.append(createElement("div", { children: [tree, view], className: "filesystem-body" }));
const nodes = new Map(flatten(root).map((node) => [node.id, node] as const));
enableSelection({ article, nodes, repository, root, tab: tab.id, tree, view });
return article;
};