core/factories/page.factory.ts
core/factories/page.factory.ts is a file in Bane's Lab Social Share. 147 lines of code and 16 definitions.
import "@banes-lab/web/presentation/records/records.barrel.ts";
import type {
CardSpec,
ColumnBox,
ColumnEntry,
ColumnRow,
FrameContext,
Layer,
PageCardOptions,
Placement,
} from "#types/card.types";
import { MISSING_COLUMN_ROW, SUBJECT_SEPARATOR, UNKNOWN_PAGE_ENTRY } from "#configuration/strings/card.strings";
import {
PAGE_COLUMN,
PAGE_GLOW_BASE,
PAGE_GLOW_OPACITY,
PAGE_GLOW_SWING,
PAGE_LAYOUT,
PAGE_SHIMMER_SPAN,
PAGE_TIMELINE,
} from "#configuration/data/page.data";
import { SITE_NAME, TITLE_SEPARATOR } from "@banes-lab/web/configuration/strings/page.strings.ts";
import {
alignAt,
fontAt,
orientationOf,
percent,
pixels,
placeAt,
wave,
} from "#core/evaluators/card.fragment.evaluator";
import { columnOf, pageAddress } from "#core/evaluators/page.evaluator";
import type { PageEntry } from "@banes-lab/web/types/page.types.ts";
import { SITE_BYLINE } from "#configuration/data/site.data";
import { createCard } from "#core/factories/card.factory";
import { fieldLayers } from "#core/factories/field.factory";
import { getPage } from "@banes-lab/web/domain/registries/page.registry.ts";
import stylesheet from "../styles/page.style.css?raw";
const QUARTER_TURN = -0.25;
const SHEET_SEPARATOR = "\n";
const breath = function breath(progress: number): number {
return wave(progress, 1, QUARTER_TURN);
};
const entryOf = function entryOf(page: string): PageEntry {
const entry = getPage(page);
if (entry === undefined) {
throw new Error(UNKNOWN_PAGE_ENTRY + SUBJECT_SEPARATOR + page);
}
return entry;
};
const entriesOf = function entriesOf(entry: PageEntry): readonly ColumnEntry[] {
const { headline, tagline } = entry.share;
return [
...(headline === SITE_NAME ? [] : [{ row: "kicker" as const, text: SITE_NAME }]),
{ row: "title", text: headline },
{ row: "subtitle", text: tagline },
{ row: "rule", text: "" },
];
};
const footerText = function footerText(slot: "byline" | "domain", text: string): Layer {
return {
className: `share-${slot}`,
id: slot,
kind: "text",
placement: placeAt(PAGE_LAYOUT, slot),
style: { fontSize: fontAt(PAGE_LAYOUT, slot), textAlign: alignAt(PAGE_LAYOUT, slot) },
text,
};
};
const columnPlacement = function columnPlacement(entries: readonly ColumnEntry[], row: ColumnRow): Placement {
const box = (frame: FrameContext): ColumnBox => {
const found = columnOf(PAGE_COLUMN[orientationOf(frame)], entries, frame).get(row);
if (found === undefined) {
throw new Error(MISSING_COLUMN_ROW + SUBJECT_SEPARATOR + row);
}
return found;
};
return {
anchor: "start",
height: (frame) => (row === "rule" ? box(frame).height : 0),
width: (frame) => box(frame).width,
x: (frame) => box(frame).x,
y: (frame) => box(frame).y,
};
};
const columnText = function columnText(entries: readonly ColumnEntry[], row: ColumnRow, text: string): Layer {
return {
className: `share-${row}`,
id: row,
kind: "text",
placement: columnPlacement(entries, row),
style: {
fontSize: (frame) => pixels(frame.profile.width * PAGE_COLUMN[orientationOf(frame)].rows[row].size),
lineHeight: (frame) => String(PAGE_COLUMN[orientationOf(frame)].rows[row].leading),
textAlign: (frame) => PAGE_COLUMN[orientationOf(frame)].align,
},
text,
};
};
const markLayer = function markLayer(entry: PageEntry): Layer {
return {
alt: SITE_NAME,
className: "share-mark",
id: "mark",
kind: "animation",
placement: placeAt(PAGE_LAYOUT, "mark", { square: true }),
source: entry.mark,
};
};
const identityLayers = function identityLayers(entry: PageEntry, mark: Layer): readonly Layer[] {
const entries = entriesOf(entry);
const { headline, tagline } = entry.share;
return [
mark,
...entries.filter((item) => item.row === "kicker").map((item) => columnText(entries, "kicker", item.text)),
{
...columnText(entries, "title", headline),
className: "share-title share-glow",
effects: [{ amount: (frame) => PAGE_GLOW_BASE + PAGE_GLOW_SWING * breath(frame.progress), kind: "blur" }],
id: "glow",
opacity: (frame) => PAGE_GLOW_OPACITY * breath(frame.progress),
},
columnText(entries, "title", headline),
columnText(entries, "subtitle", tagline),
{
className: "share-rule",
id: "rule",
kind: "box",
placement: columnPlacement(entries, "rule"),
style: { backgroundPositionX: (frame) => percent(PAGE_SHIMMER_SPAN * breath(frame.progress)) },
},
footerText("byline", SITE_BYLINE),
footerText("domain", pageAddress(entry.id)),
];
};
export const createPageCard = function createPageCard(options: PageCardOptions): CardSpec {
const entry = entryOf(options.page);
return createCard({
alt: entry.share.headline + TITLE_SEPARATOR + entry.share.tagline,
id: options.id,
layers: [...fieldLayers(options.field), ...identityLayers(entry, options.mark ?? markLayer(entry))],
page: entry.id,
stylesheet: [stylesheet, options.stylesheet ?? ""].join(SHEET_SEPARATOR),
timeline: { ...PAGE_TIMELINE, ...options.timeline },
tone: entry.accent,
});
};