# core/loaders/panel.loader.ts

> 59 lines of code and 11 definitions.

Tree: Bane's Lab Content
Language: typescript
Layer: infrastructure
Canonical: https://banes-lab.com/anatomy/content#file-content-core-loaders-panel-loader-ts
Source text: https://banes-lab.com/source/content/core/loaders/panel.loader.ts.txt

Listed in [core/loaders](https://banes-lab.com/api/source/content/core/loaders.md), after [core/loaders/link.loader.ts](https://banes-lab.com/source/content/core/loaders/link.loader.ts.md) and before [core/loaders/payload.loader.ts](https://banes-lab.com/source/content/core/loaders/payload.loader.ts.md).

## Definitions

- `blocksOf` (lexical_declaration, line 32)
- `sectionOf` (lexical_declaration, line 39)
- `readPanelSections` (lexical_declaration, line 53, exported)
- `isPanel` (lexical_declaration, line 22)
- `panelOf` (lexical_declaration, line 26)
- `kind` (lexical_declaration, line 27)
- `tag` (lexical_declaration, line 28)
- `id` (lexical_declaration, line 40)
- `isChapterTab` (lexical_declaration, line 48, exported)
- `layout` (lexical_declaration, line 49, exported)
- `content` (lexical_declaration, line 54, exported)

## Contained in

- [core/loaders](https://banes-lab.com/anatomy/content/folder-content-core-loaders.md)

## Uses

- [core/converters/section.converter.ts](https://banes-lab.com/source/content/core/converters/section.converter.ts.md)
- [core/selectors/payload.selector.ts](https://banes-lab.com/source/content/core/selectors/payload.selector.ts.md)

## Linked from

- [core/converters](https://banes-lab.com/anatomy/content/folder-content-core-converters.md)
- [core/loaders](https://banes-lab.com/anatomy/content/folder-content-core-loaders.md)
- [core/selectors](https://banes-lab.com/anatomy/content/folder-content-core-selectors.md)

## Source

```typescript
import {
    CHAPTER_LAYOUT,
    CHAPTER_PANEL_LAYOUTS,
    PANEL_KINDS,
    PAYLOAD_BLOCKS_KEY,
    PAYLOAD_LAYOUT_KEY,
    PAYLOAD_SUBSECTIONS_KEY,
    TAG_FIELD_BY_KIND,
} from "#configuration/constants/panel.constants";
import {
    PAYLOAD_ID_KEY,
    PAYLOAD_KIND_KEY,
    PAYLOAD_SECTIONS_KEY,
    WORD_SEPARATOR,
} from "#configuration/constants/leak.constants";
import type { PanelSection, PayloadPanel } from "#types/panel.types";
import { contentOf, listAt, tabsIn } from "#core/selectors/payload.selector";
import { payloadFileOf } from "#core/loaders/payload.loader";
import { readJsonOrNull } from "#core/persistence/report.persistence";
import { stringsOf } from "#core/converters/section.converter";

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 blocksOf = function blocksOf(section: Record<string, unknown>): Record<string, unknown>[] {
    return [
        ...listAt(section, PAYLOAD_BLOCKS_KEY),
        ...listAt(section, PAYLOAD_SUBSECTIONS_KEY).flatMap((sub) => listAt(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, isPanel).join(WORD_SEPARATOR),
    };
};

export const isChapterTab = function isChapterTab(tab: Record<string, unknown>): boolean {
    const layout = tab[PAYLOAD_LAYOUT_KEY];
    return layout === undefined || (typeof layout === "string" && CHAPTER_PANEL_LAYOUTS.has(layout));
};

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