Spaces:
Running on CPU Upgrade
Running on CPU Upgrade
File size: 3,588 Bytes
2355b2b 4decad9 2355b2b 4decad9 2355b2b 4decad9 2355b2b 49da3aa 2355b2b | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 | /**
* Merge meta.json `layout_prop_schema[layout].defaults` under stored layoutProps,
* matching `backend/app/services/remotion.py` (`write_remotion_data`, ~983–1002).
*
* Keeps frontend preview / SceneEditModal in sync with the rendered MP4 for
* chart colors, axis captions, example tables, font sizes, etc.
*/
export type LayoutPropSchema = Record<string, { defaults?: Record<string, unknown> }>;
// Economist wordmark/dateline/teasers are LLM-authored content, never defaults —
// mirror `_ECONOMIST_CONTENT_SKIP_KEYS` in backend/app/services/remotion.py so the
// preview never injects the sample "The Brief" wordmark onto the cover / sign-off.
const ECONOMIST_CONTENT_SKIP_KEYS = new Set(["wordmark", "dateline", "teasers"]);
function resolveDefaultsForAspect(
defaults: Record<string, unknown>,
aspectRatio: string,
): Record<string, unknown> {
const ar = aspectRatio === "portrait" ? "portrait" : "landscape";
const resolved: Record<string, unknown> = {};
for (const [key, value] of Object.entries(defaults)) {
if (
value &&
typeof value === "object" &&
!Array.isArray(value) &&
"portrait" in value &&
"landscape" in value
) {
const responsive = value as { portrait?: unknown; landscape?: unknown };
resolved[key] = responsive[ar] ?? responsive.landscape;
} else {
resolved[key] = value;
}
}
return resolved;
}
/**
* Apply layout schema defaults under `layoutProps` (stored props win on conflict).
*/
export function mergeLayoutSchemaDefaults(
layoutProps: Record<string, unknown>,
layoutId: string | null | undefined,
schema: LayoutPropSchema | null | undefined,
aspectRatio = "landscape",
templateId?: string | null,
): Record<string, unknown> {
if (!layoutId || !schema || Object.keys(schema).length === 0) return layoutProps;
const defaults = schema[layoutId]?.defaults;
if (!defaults || Object.keys(defaults).length === 0) return layoutProps;
const resolved = resolveDefaultsForAspect(defaults, aspectRatio);
// Drop content-only defaults the renderer also refuses to inject, so the
// preview matches the MP4 (e.g. economist never auto-stamps a brand wordmark).
if (String(templateId ?? "").toLowerCase() === "economist") {
for (const key of ECONOMIST_CONTENT_SKIP_KEYS) delete resolved[key];
}
const merged = { ...resolved, ...layoutProps };
// Keep example chart data when stored chartTable has no rows (legacy partial merge).
const existingTable = layoutProps.chartTable;
const existingTableHasRows =
existingTable &&
typeof existingTable === "object" &&
Array.isArray((existingTable as { rows?: unknown }).rows) &&
((existingTable as { rows: unknown[] }).rows.length > 0);
if (!existingTableHasRows && resolved.chartTable && typeof resolved.chartTable === "object") {
merged.chartTable = resolved.chartTable;
}
const existingTickerTable = layoutProps.tickerTable;
const existingTickerTableHasRows =
existingTickerTable &&
typeof existingTickerTable === "object" &&
Array.isArray((existingTickerTable as { rows?: unknown }).rows) &&
((existingTickerTable as { rows: unknown[] }).rows.some((row) =>
Array.isArray(row) && row.some((cell) => String(cell ?? "").trim() !== ""),
));
if (!existingTickerTableHasRows && resolved.tickerTable && typeof resolved.tickerTable === "object") {
merged.tickerTable = resolved.tickerTable;
}
if (!("chartType" in layoutProps) && typeof resolved.chartType === "string") {
merged.chartType = resolved.chartType;
}
return merged;
}
|