presentation/widgets/home.widget.ts

presentation/widgets/home.widget.ts is a file in Bane's Lab Site. 77 lines of code and 22 definitions.

import { JUMP_SELECTOR, MAP_SELECTOR } from "#core/ids/home.ids";
import type { LaidStop, LearningView, PlacedStop } from "#types/learning.types";
import { ROUTE_CHANGED, ROUTE_REQUESTED } from "#core/ids/route.ids";
import { emitEvent, subscribeEvent } from "#core/buses/base.bus";
import type { Disposer } from "#types/base.types";
import { HOME_PAGE } from "#core/ids/page.ids";
import { HREF_ATTRIBUTE } from "#configuration/constants/element.constants";
import { LEARNING } from "#core/generated/learning.generated";
import { attachHome } from "#presentation/components/home.component";
import { attachLearningMap } from "#presentation/components/learning.component";
import { attachPrerequisites } from "#presentation/components/learning.fragment.component";
import { layoutLearning } from "#domain/converters/learning.converter";
import { pageFromPath } from "#core/assets/link.assets";

const VECTOR_SELECTOR = "svg";
const TRACE_SELECTOR = ".learning-trace";

const placedOf = function placedOf(shell: Element, stop: LaidStop, block: PlacedStop["block"]): PlacedStop | null {
    const node = shell.querySelector(`#${stop.id}`);
    return node === null ? null : { ...stop, block, node };
};

const viewOf = function viewOf(shell: Element): LearningView | null {
    const vector = shell.querySelector(VECTOR_SELECTOR);
    const trace = shell.querySelector(TRACE_SELECTOR);
    if (vector === null || trace === null) {
        return null;
    }
    const stops = layoutLearning(LEARNING).blocks.flatMap((block) =>
        block.stops.flatMap((stop) => {
            const placed = placedOf(shell, stop, block);
            return placed === null ? [] : [placed];
        }),
    );
    return { stops, trace, vector };
};

const idle: Disposer = () => {};

const onClick = function onClick(event: Event): void {
    const { target } = event;
    const jump = target instanceof Element ? target.closest(JUMP_SELECTOR) : null;
    const href = jump?.getAttribute(HREF_ATTRIBUTE) ?? "";
    if (href.length === 0) {
        return;
    }
    event.preventDefault();
    emitEvent({ name: ROUTE_REQUESTED, page: pageFromPath(href, HOME_PAGE), path: href });
};

const routeMap = function routeMap(shell: Element): Disposer {
    shell.addEventListener("click", onClick);
    return () => {
        shell.removeEventListener("click", onClick);
    };
};

const wire = function wire(main: Element): Disposer {
    const shell = main.querySelector(MAP_SELECTOR);
    const view = shell === null ? null : viewOf(shell);
    const disposers = [attachHome(main)];
    if (shell !== null) {
        disposers.push(routeMap(shell));
    }
    if (shell !== null && view !== null) {
        disposers.push(attachLearningMap(shell, view), attachPrerequisites(view));
    }
    return () => {
        for (const dispose of disposers) {
            dispose();
        }
    };
};

export const mountHome = function mountHome(main: Element): Disposer {
    let dispose = idle;
    const unsubscribe = subscribeEvent(ROUTE_CHANGED, (event) => {
        dispose();
        dispose = event.page === HOME_PAGE ? wire(main) : idle;
    });
    return () => {
        dispose();
        unsubscribe();
    };
};