import { isEmpty, produce } from '@knapsack/utils'; import { type KsAppClientData, type KsAppClientDataNoMeta, type RenderDataDemo, type ContentStateForRendering, type DemoWithData, isDemoWithData, } from '@knapsack/types'; import { convertSlotOptions } from './slot-layout-options-utils.js'; export function getContentStateFromAppClientData({ patterns, demosById, }: { patterns: KsAppClientDataNoMeta['patternsState']['patterns']; demosById: KsAppClientDataNoMeta['db']['demos']['byId']; }): ContentStateForRendering { return { patterns: Object.values(patterns).reduce( (acc, pattern) => { acc[pattern.id] = { id: pattern.id, templateDemos: pattern.templates?.flatMap((t) => { if (!t?.demoIds) { return []; } return t.demoIds .map((demoId) => { const demo = demosById[demoId]; return { ...demo, templateId: t.id, templateLanguageId: t.templateLanguageId, }; }) .filter((d) => d.type === 'template'); }) || [], templates: pattern.templates?.map( ({ id, alias, path, templateLanguageId, spec }) => ({ id, alias, path, templateLanguageId, spec, }), ) || [], }; return acc; }, {} as ContentStateForRendering['patterns'], ), }; } /** * What we're going to do here is look at all the slots data and grab * the ACTUAL demo data we have locally here instead of just sending up the * demoId - b/c that could be wrong on the server due to changes that were made * in the UI to that demo that is in a slot for the demo we're currently rendering. * A good example is if the Button was changed and we are now rendering a Card * that uses the Button. */ export function inlineDemoData({ demosById, demo, collectionsParentKey, }: { demosById: KsAppClientData['db']['demos']['byId']; demo: Extract; collectionsParentKey: string; }): Extract { if (Object.keys(demo.data.slots || {}).length === 0) return demo; return produce(demo, (draft) => { Object.entries(draft.data.slots || {}).forEach( ([slotName, slottedDatas]) => { const slotOptions = draft.data.slotsOptions?.[slotName]; if (!isEmpty(slotOptions)) { draft.data.slotsOptionsComputed ||= {}; draft.data.slotsOptionsComputed[slotName] = convertSlotOptions({ slotOptions, collectionsParentKey, }); } slottedDatas.forEach((slottedData) => { if (slottedData.type !== 'template-demo') return; const slottedDemo = demosById[slottedData.demoId]; if (!slottedDemo) { throw new Error( `slottedDemo, ${slottedData.demoId} not found in ${draft.id}`, ); } if (isDemoWithData(slottedDemo)) { // For backwards compatibility with older App Client's we're putting these ids on the slottedData. // It was removed in commit linked below since we have had it in `slottedDemo` for a while now. // https://github.com/knapsack-labs/app-monorepo/commit/bb623913f9f317d1c4262355764972b6727f0951#diff-869a9617126324391ef651d0f7eaf12f0c6b2fc427dfd26dcb3862e4ee6e24c7 // @ts-expect-error see above slottedData.patternId = slottedDemo.patternId; // @ts-expect-error see above slottedData.templateId = slottedDemo.templateId; // recursively grab the data of the sub-components of our sub-components slottedData.demo = inlineDemoData({ demosById, demo: slottedDemo, collectionsParentKey, }); } }); }, ); }); }