// This file was moved from the `server` package to here and `server` has `strictNullChecks` disabled. // This code was originally written with the assumption that `widget.children` is never undefined, despite the type saying otherwise. // So we are using `!` to assert that `widget.children` is never undefined. // Note that `!` does not change the runtime behavior of the code so the code will work the same way as before. import type { PageDSL6, WidgetProps6 } from '../../../types/index.js'; import { generatePredictableId } from '../../../utils/appDSL.js'; const v7MainSection = () => { // This needs to be consistent on each autosave, otherwise the changelog will indicate diffs const firstSectionId = '7dsm1k3e8w'; return { type: 'SECTION_WIDGET', widgetId: firstSectionId, parentId: '0', widgetName: 'Section1', detachFromLayout: true, isLoading: false, isVisible: true, canExtend: true, dragDisabled: true, shouldScrollContents: true, children: [ { widgetName: 'Canvas1', backgroundColor: 'none', detachFromLayout: true, dragDisabled: true, parentId: firstSectionId, widgetId: generatePredictableId('Canvas1'), isLoading: false, isVisible: true, topRow: 0, bottomRow: 99, containerStyle: 'none', snapColumns: 96, snapRows: 99, minHeight: 1292, type: 'CANVAS_WIDGET', canExtend: true, dynamicBindingPathList: [], children: [] } ], // The properties below will be removed as part of Mark's migration // to use the new Dimension type, but they are needed for now otherwise the // DSL rendering breaks due to dependencies on these properties // even though they are unused by the widget itself topRow: 0, bottomRow: 99, snapColumns: 96, snapRows: 99, leftColumn: 0, rightColumn: 96 } as WidgetProps6; }; const generateSectionsLayout = (): PageDSL6 => { return { version: 6, type: 'PAGE_WIDGET', // PAGE_WIDGET is always id '0'. It's the only one that exists and cannot be deleted widgetId: '0', widgetName: 'Page', backgroundColor: 'none', // is this needed? Seems worth keeping for now detachFromLayout: true, children: [v7MainSection()], apis: { apiMap: {} }, stateVars: { stateVarMap: {} }, timers: { timerMap: {} }, canExtend: true, dynamicBindingPathList: [], // The properties below will be removed as part of Mark's migration // to use the new Dimension type, but they are needed for now otherwise the // DSL rendering breaks due to dependencies on these properties // even though they are unused by the widget itself topRow: 0, bottomRow: 99, snapColumns: 96, snapRows: 99 }; }; const createPage = (): PageDSL6 => generateSectionsLayout(); export const findAllInstancesOfWidgetNameWithPrefix = (dsl: PageDSL6, prefix: string): string[] => { const names: string[] = []; const traverseWidgets = (widget: WidgetProps6) => { // Check widgetName at the root of the widget. if (widget.widgetName && typeof widget.widgetName === 'string' && widget.widgetName.toLowerCase().startsWith(prefix)) { names.push(widget.widgetName); } // Only traverse the children property if it exists. if (Array.isArray(widget.children)) { for (const child of widget.children) { traverseWidgets(child); } } }; traverseWidgets(dsl); return names; }; export const findNextWidgetNameWithSuffix = (startingName: string, existingWidgetNamesWithPrefix: string[], suffix?: string): string => { // Numbered items start at 1, not 0 // Suffixed items start without a suffix let nextWidgetName = startingName + (suffix ? '' : 1); if (existingWidgetNamesWithPrefix.length > 0) { let findNextName = true; let index = 1; while (findNextName) { if (!existingWidgetNamesWithPrefix.find((n) => n.toLocaleLowerCase() === nextWidgetName.toLowerCase())) { findNextName = false; } else { if (suffix) { nextWidgetName = `${startingName}${suffix.repeat(index)}`; } else { nextWidgetName = `${startingName}${index}`; } index++; } } } return nextWidgetName; }; const v7SlideoutOrModalSection = (nextName: string, children: WidgetProps6[] | WidgetProps6[]) => { const id = generatePredictableId(nextName); return { type: 'SECTION_WIDGET', widgetId: id, widgetName: nextName, children: children.map((child) => { child.parentId = id; return child; }), detachFromLayout: true, isVisible: true, isLoading: true, canExtend: true, dragDisabled: true, shouldScrollContents: true, snapColumns: 96, topRow: 0, bottomRow: 99 } as WidgetProps6; }; // Insert a new "Page" widget at the top level. // This will be the new root level widget containing all other widgets. // Then insert a single "Section" widget into the Page children. All v7 DSL apps must have at least a single // Section that widgets live in. // Then move the v6 DSL CANVAS root widget ("MainCanvasWidget") into the Section. // Canvas widgets are now just a type of widget that can live inside a Section and they are considered the Section's "columns" export const migrateToSections = (page: PageDSL6): PageDSL6 => { const { apis, stateVars, timers, cachedData, ...originalMainCanvasWidget } = page; // Rename the widgetId to a random UUID as "0" is reserved for the page delete (originalMainCanvasWidget as { version?: typeof originalMainCanvasWidget.version }).version; originalMainCanvasWidget.widgetId = generatePredictableId(originalMainCanvasWidget.widgetName); originalMainCanvasWidget.widgetName = findNextWidgetNameWithSuffix('Canvas', findAllInstancesOfWidgetNameWithPrefix(page, 'canvas')); const dsl = createPage(); // Even though our widget naming system is case sensitive, we want to make naming clear so we won't // use Page if a widget with "page" (lowercase P) exists dsl.widgetName = findNextWidgetNameWithSuffix('Page', findAllInstancesOfWidgetNameWithPrefix(page, 'page'), '_'); dsl.children![0].widgetId = generatePredictableId(dsl.children![0].widgetName); // Move the main canvas widget into the first section // this will overwrite the default empty canvas that is already in the default layout dsl.children![0].children = [originalMainCanvasWidget]; originalMainCanvasWidget.parentId = dsl.children![0].widgetId; // Update parent id of all children of the original main canvas widget for (const child of originalMainCanvasWidget.children!) { child.parentId = originalMainCanvasWidget.widgetId; } const upgradeModalsAndSlideouts = (widget: WidgetProps6) => { for (const child of widget.children ?? []) { if (child.type === 'SLIDEOUT_WIDGET' || child.type === 'MODAL_WIDGET') { const firstChild = child.children?.[0]; const names = findAllInstancesOfWidgetNameWithPrefix(dsl, 'body'); const nextName = findNextWidgetNameWithSuffix('Body', names); const bodySection = v7SlideoutOrModalSection(nextName, child.children!); bodySection.parentId = child.widgetId; // Copy over the position properties from the first child const keys: Array = ['topRow', 'bottomRow', 'leftColumn', 'rightColumn', 'snapColumns', 'snapRows']; keys.forEach((prop) => { if (firstChild?.[prop] !== undefined) bodySection[prop] = firstChild?.[prop]; }); child.children = [bodySection]; } upgradeModalsAndSlideouts(child); } }; upgradeModalsAndSlideouts(dsl); return { ...dsl, apis: { apiMap: apis?.apiMap ?? {} }, stateVars: stateVars ?? { stateVarMap: {} }, timers: timers ?? { timerMap: {} }, ...(cachedData ? { cachedData } : {}) }; };