# core/loaders/panel.loader.ts

> 76 lines of code and 14 definitions.

Tree: Build tree
Language: typescript
Layer: infrastructure
Canonical: https://banes-lab.com/anatomy/build#file-build-core-loaders-panel-loader-ts
Source text: https://banes-lab.com/assets/sources/source.06a4307981ef84a7c18a02cf3c5c3366bc5afb96110cf4ba922f49e959380fac.generated.txt

## Definitions

- `listOf` (lexical_declaration, line 25)
- `isRecord` (lexical_declaration, line 21)
- `blocksOf` (lexical_declaration, line 52)
- `stringsOf` (lexical_declaration, line 39)
- `isPanel` (lexical_declaration, line 29)
- `readPanelSections` (lexical_declaration, line 72, exported)
- `sectionOf` (lexical_declaration, line 59)
- `panelOf` (lexical_declaration, line 33)
- `kind` (lexical_declaration, line 34)
- `tag` (lexical_declaration, line 35)
- `id` (lexical_declaration, line 60)
- `isChapterTab` (lexical_declaration, line 68)
- `parsed` (lexical_declaration, line 73, exported)
- `content` (lexical_declaration, line 77, exported)

## Source

```typescript
import {
    CHAPTER_LAYOUT,
    PANEL_KINDS,
    PAYLOAD_BLOCKS_KEY,
    PAYLOAD_LAYOUT_KEY,
    PAYLOAD_SUBSECTIONS_KEY,
    TAG_FIELD_BY_KIND,
} from "#configuration/constants/panel.constants";
import {
    PAYLOAD_CONTENT_KEY,
    PAYLOAD_ID_KEY,
    PAYLOAD_KIND_KEY,
    PAYLOAD_SECTIONS_KEY,
    PAYLOAD_TABS_KEY,
    WORD_SEPARATOR,
} from "#configuration/constants/leak.constants";
import type { PanelSection, PayloadPanel } from "#types/panel.types";
import { payloadFileOf } from "#core/loaders/payload.loader";
import { readJsonOrNull } from "#core/persistence/report.persistence";

const isRecord = function isRecord(value: unknown): value is Record<string, unknown> {
    return typeof value === "object" && value !== null;
};

const listOf = function listOf(value: unknown): Record<string, unknown>[] {
    return Array.isArray(value) ? value.filter(isRecord) : [];
};

const isPanel = function isPanel(block: Record<string, unknown>): boolean {
    return typeof block[PAYLOAD_KIND_KEY] === "string" && PANEL_KINDS.has(block[PAYLOAD_KIND_KEY]);
};

const panelOf = function panelOf(block: Record<string, unknown>): PayloadPanel {
    const kind = typeof block[PAYLOAD_KIND_KEY] === "string" ? block[PAYLOAD_KIND_KEY] : "";
    const tag = block[TAG_FIELD_BY_KIND.get(kind) ?? ""];
    return { kind, tag: typeof tag === "string" && tag.length > 0 ? tag : null };
};

const stringsOf = function stringsOf(value: unknown): string[] {
    if (typeof value === "string") {
        return [value];
    }
    if (Array.isArray(value)) {
        return value.flatMap((entry: unknown) => stringsOf(entry));
    }
    if (!isRecord(value) || isPanel(value)) {
        return [];
    }
    return Object.values(value).flatMap((entry) => stringsOf(entry));
};

const blocksOf = function blocksOf(section: Record<string, unknown>): Record<string, unknown>[] {
    return [
        ...listOf(section[PAYLOAD_BLOCKS_KEY]),
        ...listOf(section[PAYLOAD_SUBSECTIONS_KEY]).flatMap((sub) => listOf(sub[PAYLOAD_BLOCKS_KEY])),
    ];
};

const sectionOf = function sectionOf(section: Record<string, unknown>): PanelSection {
    const id = section[PAYLOAD_ID_KEY];
    return {
        id: typeof id === "string" ? id : "",
        panels: blocksOf(section).filter(isPanel).map(panelOf),
        prose: stringsOf(section).join(WORD_SEPARATOR),
    };
};

const isChapterTab = function isChapterTab(tab: Record<string, unknown>): boolean {
    return tab[PAYLOAD_LAYOUT_KEY] === undefined || tab[PAYLOAD_LAYOUT_KEY] === CHAPTER_LAYOUT;
};

export const readPanelSections = function readPanelSections(page: string): PanelSection[] | null {
    const parsed = readJsonOrNull(payloadFileOf(page));
    if (!isRecord(parsed) || !isRecord(parsed[PAYLOAD_CONTENT_KEY])) {
        return null;
    }
    const content = parsed[PAYLOAD_CONTENT_KEY];
    if (content[PAYLOAD_LAYOUT_KEY] !== CHAPTER_LAYOUT) {
        return [];
    }
    return listOf(content[PAYLOAD_TABS_KEY])
        .filter(isChapterTab)
        .flatMap((tab) => listOf(tab[PAYLOAD_SECTIONS_KEY]))
        .map(sectionOf);
};
```
