From dc55e3f2f0a23e6d41919e6d036ae1e854e5d14a Mon Sep 17 00:00:00 2001 From: Heather Pfeiffer Date: Wed, 24 Apr 2024 19:20:35 -0400 Subject: [PATCH 01/18] stashed changes to new branch --- app/src/components/left/ComponentDrag.tsx | 4 +++- app/src/components/main/CanvasContainer.tsx | 2 +- app/src/components/main/DemoRender.tsx | 13 ++++++++++--- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/app/src/components/left/ComponentDrag.tsx b/app/src/components/left/ComponentDrag.tsx index 0f370e60..d30be99b 100644 --- a/app/src/components/left/ComponentDrag.tsx +++ b/app/src/components/left/ComponentDrag.tsx @@ -21,7 +21,7 @@ const useStyles = makeStyles({ color: '#fff' }, darkThemeFontColor: { - color: '#fff' + color: '#00008B,' } }); @@ -30,6 +30,8 @@ const ComponentDrag = ({ isVisible, isThemeLight }): JSX.Element | null => { const state = useSelector((store: RootState) => store.appState); const isFocus = (targetId: Number) => { + console.log('targetID line 33', targetId) + console.log('componentID line 34', state.canvasFocus.componentId) return state.canvasFocus.componentId === targetId ? true : false; }; diff --git a/app/src/components/main/CanvasContainer.tsx b/app/src/components/main/CanvasContainer.tsx index ad059e89..dd59c01f 100644 --- a/app/src/components/main/CanvasContainer.tsx +++ b/app/src/components/main/CanvasContainer.tsx @@ -38,7 +38,7 @@ function CanvasContainer(props: CanvasContainerProps): JSX.Element { backgroundSize: '8px 8px', backgroundPosition: '-19px -19px', borderBottom: 'none', - overflow: 'auto' + overflow: 'auto', }; //containerRef references the container that will ultimately have the scroll functionality diff --git a/app/src/components/main/DemoRender.tsx b/app/src/components/main/DemoRender.tsx index 8022b7ff..83474b89 100644 --- a/app/src/components/main/DemoRender.tsx +++ b/app/src/components/main/DemoRender.tsx @@ -13,6 +13,7 @@ import { Component } from '../../interfaces/Interfaces'; import ReactDOMServer from 'react-dom/server'; import { RootState } from '../../redux/store'; import { changeFocus } from '../../redux/reducers/slice/appStateSlice'; +import { blue } from '@mui/material/colors'; // DemoRender is the full sandbox demo of our user's custom built React components. DemoRender references the design specifications stored in state to construct // real react components that utilize hot module reloading to depict the user's prototype application. @@ -79,11 +80,12 @@ const DemoRender = (): JSX.Element => { `; window.onmessage = (event) => { + // console.log('event', event) // If event.data or event.data.data is undefined, return early if (!event.data || typeof event.data.data !== 'string') return; const component: string = event.data.data.split('/').at(-1); - + console.log('component', component) // If components aren't defined or component isn't a string, return early if (!state.components || !component) return; @@ -102,10 +104,13 @@ const DemoRender = (): JSX.Element => { const componentBuilder = (array: any, key: number = 0) => { const componentsToRender = []; for (const element of array) { + console.log("array") + console.log(array) if (element.name !== 'separator') { const elementType = element.name; const childId = element.childId; const elementStyle = element.style; + console.log("elementStyle", elementStyle) const innerText = element.attributes.compText; const classRender = element.attributes.cssClasses; const activeLink = element.attributes.compLink; @@ -188,6 +193,7 @@ const DemoRender = (): JSX.Element => { key += 1; } } + console.log('componentstoRender array line 196', componentsToRender) return componentsToRender; }; @@ -198,16 +204,17 @@ const DemoRender = (): JSX.Element => { const currComponent = state.components.find( (element) => element.id === state.canvasFocus.componentId ); - + //writes each component to the code componentBuilder(currComponent.children).forEach((element) => { try { code += ReactDOMServer.renderToString(element); + console.log('code line 211', code) } catch { return; } }); - + console.log('code line 215', code) //writes our stylesheet from state to the code code += ``; From 1da9056f515bf50a9c2af33befa026aca7f07290 Mon Sep 17 00:00:00 2001 From: Jesse Wowczuk Date: Thu, 25 Apr 2024 10:18:04 -0700 Subject: [PATCH 02/18] Jesse 4/25, MUI Props Tab added in file BottomTabs.tsx,re-adjust the bottom panel min and max height resize draggable, Created MUIProps.ts file under the bottom folder and created MUI Props panel. > > Co-author-by: Heather Pfeiffer Co-author-by: Jesse Wowczuk Co-author-by: Zack Vandiver Co-author-by: Sean Ryan Co-author-by: Austin Alvarez --- app/src/components/bottom/BottomPanel.tsx | 23 ++++++---- app/src/components/bottom/BottomTabs.tsx | 51 ++++++++++------------- app/src/components/bottom/MUIProps.tsx | 47 +++++++++++++++++++++ 3 files changed, 85 insertions(+), 36 deletions(-) create mode 100644 app/src/components/bottom/MUIProps.tsx diff --git a/app/src/components/bottom/BottomPanel.tsx b/app/src/components/bottom/BottomPanel.tsx index 753ff3a7..efb3da42 100644 --- a/app/src/components/bottom/BottomPanel.tsx +++ b/app/src/components/bottom/BottomPanel.tsx @@ -33,11 +33,13 @@ const BottomPanel = (props): JSX.Element => { if (!props.bottomShow) return; // prevent drag calculation to occur when bottom menu is not showing const dy = y - e.clientY; + const newHeight = h + dy; - const newVal = h + dy; - const styles = window.getComputedStyle(node.current); - const min = parseInt(styles.minHeight, 10); - node.current.style.height = newVal > min ? `${h + dy}px` : `${min}px`; + const styles = window.getComputedStyle(node.current); + const minHeight = parseInt(styles.minHeight, 10); + const maxHeight = window.innerHeight * 0.8; // Set a maximum height, e.g., 90% of window height + + node.current.style.height = `${Math.max(minHeight, Math.min(maxHeight, newHeight))}px`; }; const mouseUpHandler = function () { @@ -49,13 +51,18 @@ const BottomPanel = (props): JSX.Element => { }; useEffect(() => { - node.current.style.height = '50vh'; - node.current.style.minHeight = '50vh'; - }, []); + if (props.bottomShow) { + node.current.style.height = '50vh'; // Initial height when bottom panel is open + node.current.style.minHeight = '20vh'; // Minimum height when bottom panel is open + } else { + node.current.style.height = '0.1'; // Initial height when bottom panel is closed + node.current.style.minHeight = '0.1'; // Minimum height when bottom panel is closed + } + }, [props.bottomShow]); return ( <> -
+
{ const { setBottomShow, isThemeLight } = props; @@ -73,54 +75,52 @@ const BottomTabs = (props): JSX.Element => { indicator: classes.tabsIndicator }} variant="scrollable" - scrollButtons="auto" + scrollButtons="auto" > - +
@@ -161,13 +161,8 @@ const BottomTabs = (props): JSX.Element => { {tab === 3 && } {tab === 4 && } {tab === 5 && } - {tab === 6 && ( - - )} + {tab === 6 && ()} + {tab === 7 && }
diff --git a/app/src/components/bottom/MUIProps.tsx b/app/src/components/bottom/MUIProps.tsx new file mode 100644 index 00000000..2b238bc3 --- /dev/null +++ b/app/src/components/bottom/MUIProps.tsx @@ -0,0 +1,47 @@ +import React, { useState } from 'react'; +import { Button, TextField } from "@mui/material"; +import { Send } from "@mui/icons-material"; +import MUITypes from "../../redux/MUITypes"; +import { MUIType } from '../../interfaces/Interfaces'; +import { emitEvent } from '../../helperFunctions/socket'; + +const MUIProps = (props): JSX.Element => { + const [selectedComponent, setSelectedComponent] = useState(null); + + const handleComponentSelect = (component: MUIType) => { + setSelectedComponent(component); + }; + + const handleSend = () => { + if (selectedComponent) { + emitEvent("add-component", selectedComponent, {placeholder: "Placeholder"}); + } + }; + + return ( +
+
+ {MUITypes.map((component) => ( + + ))} +
+ +
+ ); +}; + +export default MUIProps; + From db0e1d38006c5075280d06ce09be4c6f550d450e Mon Sep 17 00:00:00 2001 From: Sean Ryan Date: Thu, 25 Apr 2024 12:04:20 -0700 Subject: [PATCH 03/18] Sean 4/25 updated MUIType interface and MUITypes to include default props and prop options; reworked formating in generateCode.ts to handle MUI Components; generateCode.ts now properly includes MUI JSX and Import Statements > > Co-authored-by: Heather Pfeiffer Co-authored-by: Jesse Wowczuk Co-authored-by: Zack Vandiver Co-authored-by: Sean Ryan Co-authored-by: Austin Alvarez --- app/src/components/main/Canvas.tsx | 1 - app/src/helperFunctions/generateCode.ts | 842 ++++++++++++--------- app/src/helperFunctions/renderChildren.tsx | 5 - app/src/interfaces/Interfaces.ts | 5 +- app/src/redux/MUITypes.ts | 57 +- 5 files changed, 534 insertions(+), 376 deletions(-) diff --git a/app/src/components/main/Canvas.tsx b/app/src/components/main/Canvas.tsx index 3936c3d2..c491dfd3 100644 --- a/app/src/components/main/Canvas.tsx +++ b/app/src/components/main/Canvas.tsx @@ -211,7 +211,6 @@ const Canvas = forwardRef(({ zoom }, ref) => { } // if item dropped is going to be a new instance (i.e. it came from the left panel), then create a new child component if (item.newInstance && item.instanceType !== 'Component') { - console.log('inside not component check', item); dispatch( //update state addChild({ diff --git a/app/src/helperFunctions/generateCode.ts b/app/src/helperFunctions/generateCode.ts index 35bbf93e..0d8fdd92 100644 --- a/app/src/helperFunctions/generateCode.ts +++ b/app/src/helperFunctions/generateCode.ts @@ -11,6 +11,7 @@ declare global { api: any; } } + // generate code based on component hierarchy and then return the rendered code const generateCode = ( components: Component[], @@ -51,379 +52,500 @@ const generateUnformattedCode = ( const currComponent = components.find((elem) => elem.id === componentId); // find the unique components that we need to import into this component file let imports: any = []; + let muiImports = new Set(); let providers: string = ''; let context: string = ''; let links: boolean = false; let images: boolean = false; const isRoot = rootComponents.includes(componentId); let importReactRouter = false; + // returns an array of objects which may include components, html elements, and/or route links - const getEnrichedChildren = (currentComponent: Component | ChildElement) => { - // declare an array of enriched children - const enrichedChildren = currentComponent.children?.map((elem: any) => { - //enrichedChildren is iterating through the children array - const child: ChildElement = { ...elem }; - // check if child is a component - if (child.type === 'Component') { - // verify that the child is in the components array in state - const referencedComponent = components.find( - (elem) => elem.id === child.typeId - ); - // check if imports array include the referenced component, if not, add its name to the imports array (e.g. the name/tag of the component/element) - if (!imports.includes(referencedComponent.name)) - imports.push(referencedComponent.name); - child['name'] = referencedComponent.name; - return child; - } else if (child.type === 'HTML Element') { - const referencedHTML = HTMLTypes.find( - (elem) => elem.id === child.typeId - ); - child['tag'] = referencedHTML.tag; - if ( - referencedHTML.tag === 'h1' || - referencedHTML.tag === 'h2' || - referencedHTML.tag === 'a' || - referencedHTML.tag === 'p' || - referencedHTML.tag === 'button' || - referencedHTML.tag === 'span' || - referencedHTML.tag === 'div' || - referencedHTML.tag === 'separator' || - referencedHTML.tag === 'form' || - referencedHTML.tag === 'ul' || - referencedHTML.tag === 'ol' || - referencedHTML.tag === 'menu' || - referencedHTML.tag === 'li' || - referencedHTML.tag === 'Link' || - referencedHTML.tag === 'Switch' || - referencedHTML.tag === 'Route' - ) { - child.children = getEnrichedChildren(child); - } + const getEnrichedChildren = (currentComponent) => { + const enrichedChildren = []; - // when we see a Switch or LinkTo, import React Router - if ( - (referencedHTML.tag === 'Switch' || referencedHTML.tag === 'Route') && - projectType === 'Classic React' - ) - importReactRouter = true; - else if (referencedHTML.tag === 'Link') links = true; - if (referencedHTML.tag === 'Image') images = true; - return child; - } else if (child.type === 'MUI Component') { - const referencedMUI = MUITypes.find((elem) => elem.id === child.typeId); - child['tag'] = referencedMUI.tag; - if ( - referencedMUI.tag === 'button' || - referencedMUI.tag === 'card' || - referencedMUI.tag === 'typography' || - referencedMUI.tag === 'input' || - referencedMUI.tag === 'Link' || - referencedMUI.tag === 'Switch' || - referencedMUI.tag === 'Route' - ) { - child.children = getEnrichedChildren(child); - } + currentComponent.children?.forEach((child) => { + const newChild = { ...child }; // Copy to avoid mutating original data - // when we see a Switch or LinkTo, import React Router - if ( - (referencedMUI.tag === 'Switch' || referencedMUI.tag === 'Route') && - projectType === 'Classic React' - ) - importReactRouter = true; - else if (referencedMUI.tag === 'Link') links = true; - if (referencedMUI.tag === 'Image') images = true; - return child; - } else if (child.type === 'Route Link') { - links = true; - child.name = components.find( - (comp: Component) => comp.id === child.typeId - ).name; - return child; + switch (child.type) { + case 'Component': + const component = components.find((c) => c.id === child.typeId); + if (component && !imports.includes(component.name)) { + imports.push(component.name); // Track imports to avoid duplicates + } + newChild.name = component?.name; // Assign the name for rendering + break; + + case 'HTML Element': + const htmlElement = HTMLTypes.find((h) => h.id === child.typeId); + newChild.tag = htmlElement?.tag; + if (htmlElement) { + // If this HTML element can contain children, process them recursively + if ( + [ + 'h1', + 'h2', + 'a', + 'p', + 'button', + 'span', + 'div', + 'form', + 'ul', + 'ol', + 'menu', + 'li', + 'Link', + 'Switch', + 'Route' + ].includes(htmlElement.tag) + ) { + newChild.children = getEnrichedChildren({ + children: child.children + }); + } + + // Additional flags for special types + if ( + ['Switch', 'Route'].includes(htmlElement.tag) && + projectType === 'Classic React' + ) { + importReactRouter = true; + } else if (htmlElement.tag === 'Link') { + links = true; + } else if (htmlElement.tag === 'Image') { + images = true; + } + } + break; + + case 'MUI Component': + const muiComponent = MUITypes.find((m) => m.id === child.typeId); + newChild.tag = muiComponent?.tag; + if (muiComponent) { + // Recursively process MUI components that can have children + if ( + ['mui button', 'card', 'typography', 'textfield'].includes( + muiComponent.tag + ) + ) { + newChild.children = getEnrichedChildren({ + children: child.children + }); + collectMUIImports(child, MUITypes, muiImports); + } + } + break; + + case 'Route Link': + links = true; + newChild.name = components.find((c) => c.id === child.typeId)?.name; + break; + + default: + // Handle other types or add error handling + break; } + enrichedChildren.push(newChild); // Add the processed child to the list }); + return enrichedChildren; }; + // Raised formatStyles so that it is declared before it is referenced. It was backwards. // format styles stored in object to match React inline style format - const formatStyles = (styleObj: ChildStyle) => { - if (Object.keys(styleObj).length === 0) return ``; - const formattedStyles: String[] = []; - let styleString: String; - for (let i in styleObj) { - if (i === 'style') { - styleString = i + '=' + '{' + JSON.stringify(styleObj[i]) + '}'; - formattedStyles.push(styleString); - } - } - return formattedStyles; + const formatStyles = (styleObj) => { + if (Object.keys(styleObj).length === 0) return ''; + return `style={{${Object.entries(styleObj) + .map(([key, value]) => `${key}: '${value}'`) + .join(', ')}}}`; }; // function to dynamically add classes, ids, and styles to an element if it exists. - //LEGACY PD: CAN ADD PROPS HERE AS JSX ATTRIBUTE - const elementTagDetails = (childElement: ChildElement) => { - let customizationDetails = ''; - let passedInPropsString = ''; - if (childElement.type === 'Component') { - let childComponent: Component; - for (let i = 0; i < components.length; i++) { - if (childElement.name === components[i].name) { - childComponent = components[i]; - } - } - childComponent.passedInProps.forEach((prop) => { - passedInPropsString += `${prop.key} = {${prop.key}} `; - }); + // LEGACY PD: CAN ADD PROPS HERE AS JSX ATTRIBUTE + const elementTagDetails = (childElement) => { + const details = []; + + if (childElement.childId && childElement.tag !== 'Route') { + details.push(`id="${childElement.childId}"`); } - if (childElement.childId && childElement.tag !== 'Route') - customizationDetails += - ' ' + `id="${+childElement.childId}" ` + `${passedInPropsString}`; - - if ( - childElement.attributes && - childElement.attributes.cssClasses && - !tailwind - ) { - customizationDetails += - ' ' + `className="${childElement.attributes.cssClasses}"`; + if (childElement.attributes && childElement.attributes.cssClasses) { + details.push(`className="${childElement.attributes.cssClasses}"`); } - if ( - childElement.style && - Object.keys(childElement.style).length > 0 && - tailwind === false - ) - customizationDetails += ' ' + formatStyles(childElement.style); - if ( - childElement.style && - Object.keys(childElement.style).length > 0 && - tailwind === true - ) { - let { - height, - alignItems, - backgroundColor, - display, - flexDirection, - width, - justifyContent - } = childElement.style; - let w: String, - h: String, - items: String, - bg: String, - d: String, - flexDir: String, - justCon: String, - cssClasses: String; - if (childElement.style.alignItems) { - if (alignItems === 'center') items = 'items-center '; - else if (alignItems === 'flex-start') items = 'items-start '; - else if (alignItems === 'flex-end') items = 'items-end '; - else if (alignItems === 'stretch') items = 'items-stretch '; - } - if (childElement.style.backgroundColor) { - bg = `bg-[${backgroundColor}] `; - } - if (childElement.style.display) { - if (display === 'flex') d = 'flex '; - else if (display === 'inline-block') d = 'inline-block '; - else if (display === 'block') d = 'block '; - else if (display === 'none') d = 'hidden '; - } - if (childElement.style.flexDirection) { - if (flexDirection === 'column') flexDir = 'flex-col '; - } - if (childElement.style.height) { - if (height === '100%') h = 'h-full '; - else if (height === '50%') h = 'h-1/2 '; - else if (height === '25%') h = 'h-1/4 '; - else if (height === 'auto') h = 'auto '; - } - if (childElement.style.justifyContent) { - if (justifyContent === 'center') justCon = 'justify-center '; - else if (justifyContent === 'flex-start') justCon = 'justify-start '; - else if (justifyContent === 'space-between') - justCon = 'justify-between '; - else if (justifyContent === 'space-around') justCon = 'justify-around '; - else if (justifyContent === 'flex-end') justCon = 'justify-end '; - else if (justifyContent === 'space-evenly') justCon = 'justify-evenly '; - } - if (childElement.style.width) { - if (width === '100%') w = 'w-full '; - else if (width === '50%') w = 'w-1/2 '; - else if (width === '25%') w = 'w-1/4 '; - else if (width === 'auto') w = 'w-auto '; - } - if (childElement.attributes && childElement.attributes.cssClasses) { - cssClasses = `${childElement.attributes.cssClasses} `; + + if (childElement.style && Object.keys(childElement.style).length > 0) { + if (tailwind) { + // Assuming 'tailwind' variable is globally available + const { + height, + alignItems, + backgroundColor, + display, + flexDirection, + width, + justifyContent + } = childElement.style; + const classMap = { + alignItems: { + center: 'items-center', + 'flex-start': 'items-start', + 'flex-end': 'items-end', + stretch: 'items-stretch' + }, + display: { + flex: 'flex', + 'inline-block': 'inline-block', + block: 'block', + none: 'hidden' + }, + flexDirection: { + column: 'flex-col' + }, + justifyContent: { + center: 'justify-center', + 'flex-start': 'justify-start', + 'space-between': 'justify-between', + 'space-around': 'justify-around', + 'flex-end': 'justify-end', + 'space-evenly': 'justify-evenly' + }, + height: { + '100%': 'h-full', + '50%': 'h-1/2', + '25%': 'h-1/4', + auto: 'auto' + }, + width: { + '100%': 'w-full', + '50%': 'w-1/2', + '25%': 'w-1/4', + auto: 'w-auto' + } + }; + + let classes = [ + classMap.alignItems[alignItems], + classMap.display[display], + classMap.flexDirection[flexDirection], + classMap.height[height], + classMap.justifyContent[justifyContent], + classMap.width[width], + backgroundColor ? `bg-[${backgroundColor}]` : '' + ] + .filter(Boolean) + .join(' '); + + if (childElement.attributes && childElement.attributes.cssClasses) { + classes += ` ${childElement.attributes.cssClasses}`; + } + + details.push(`className="${classes}"`); + } else { + details.push(formatStyles(childElement.style)); } - customizationDetails += - ' ' + - `className = "${cssClasses ? cssClasses : ''} ${w ? w : ''}${ - h ? h : '' - }${justCon ? justCon : ''}${flexDir ? flexDir : ''}${d ? d : ''}${ - bg ? bg : '' - }${items ? items : ''}"`; } - if (childElement.events && Object.keys(childElement.events).length > 0) { - // SPACE BETWEEN ATTRIBUTE EXPRESSIONS - for (const [event, funcName] of Object.entries(childElement.events)) { - customizationDetails += ' ' + `${event}={(event) => ${funcName}()}`; - } + if (childElement.events) { + Object.entries(childElement.events).forEach(([event, funcName]) => { + details.push(`${event}={${funcName}}`); + }); } - return customizationDetails; + return details.join(' '); }; + // function to fix the spacing of the ace editor for new lines of added content. This was breaking on nested components, leaving everything right justified. - const tabSpacer = (level: number) => { - let tabs = ' '; - for (let i = 0; i < level; i++) tabs += ' '; - return tabs; - }; + const tabSpacer = (level) => ' '.repeat(level); // function to dynamically generate the appropriate levels for the code preview - const levelSpacer = (level: number, spaces: number) => { - if (level === 2) return `\n${tabSpacer(spaces)}`; - else return ''; - }; + const levelSpacer = (level) => `\n${tabSpacer(level)}`; // function to dynamically generate a complete html (& also other library type) elements - const elementGenerator = (childElement: ChildElement, level: number = 2) => { + const elementGenerator = (childElement, level = 0) => { + const jsxArray = []; + const indentation = ' '.repeat(level); + + // Immediately return an empty array if the tag is 'separator' + if (childElement.tag === 'separator') { + return jsxArray; // Returns empty, adding nothing to output + } + let innerText = ''; let activeLink = '""'; + if (childElement.attributes && childElement.attributes.compText) { - if (childElement.stateUsed && childElement.stateUsed.compText) { - innerText = '{' + childElement.stateUsed.compText + '}'; + innerText = + childElement.stateUsed && childElement.stateUsed.compText + ? `{${childElement.stateUsed.compText}}` + : childElement.attributes.compText; + } + + if (childElement.attributes && childElement.attributes.compLink) { + activeLink = + childElement.stateUsed && childElement.stateUsed.compLink + ? `{${childElement.stateUsed.compLink}}` + : `"${childElement.attributes.compLink}"`; + } + + const nestableTags = [ + 'h1', + 'h2', + 'a', + 'span', + 'button', + 'p', + 'div', + 'form', + 'ol', + 'ul', + 'menu', + 'li', + 'Switch', + 'Route' + ]; + const isNestable = nestableTags.includes(childElement.tag); + + const tagDetails = elementTagDetails(childElement); + if (isNestable) { + if (childElement.children) { + const childJsx = writeNestedElements(childElement.children, level + 1); + jsxArray.push(`${indentation}<${childElement.tag} ${tagDetails}>`); + jsxArray.push(...childJsx); + jsxArray.push(`${indentation}`); } else { - innerText = childElement.attributes.compText; + jsxArray.push(`${indentation}<${childElement.tag} ${tagDetails} />`); } + } else { + jsxArray.push( + `${indentation}<${childElement.tag} ${tagDetails}>${innerText}` + ); } - if (childElement.attributes && childElement.attributes.compLink) { - if (childElement.stateUsed && childElement.stateUsed.compLink) { - activeLink = '{' + childElement.stateUsed.compLink + '}'; + + return jsxArray; + }; + + function insertNestedJsxBeforeClosingTag( + parentJsx, + nestedJsx, + indentationLevel + ) { + // Find the index of the closing tag of the parent component + const closingTagIndex = parentJsx.lastIndexOf(' `${indent}${line}`) + .join('\n'); + + // Insert the nested JSX before the closing tag of the parent component + return [ + parentJsx.slice(0, closingTagIndex), + indentedNestedJsx, + parentJsx.slice(closingTagIndex) + ].join('\n'); + } + + function modifyAndIndentJsx(jsxAry, newProps, childId, name) { + // Define a regular expression to match the start tag of the specified child element + const tagRegExp = new RegExp(`^<${name}(\\s|>)`); + + // Iterate through each line of JSX code + const modifiedJsx = jsxAry.map((line, index) => { + // Capture the original indentation by extracting leading spaces + const originalIndent = line.match(/^[\s]*/)[0]; + const trimmedLine = line.trim(); + // Find the position right after the component name to insert id and newProps + let insertIndex = trimmedLine.indexOf(`<${name}`) + `<${name}`.length; + // Condition for the first line (index 0) + if (index === 0) { + // Check if the line contains the start tag of the specified child element + if (tagRegExp.test(trimmedLine)) { + // Check and insert id if not already present + if (!trimmedLine.includes(`id=`) && childId) { + line = `${trimmedLine.substring( + 0, + insertIndex + )} ${childId}${trimmedLine.substring(insertIndex)}`; + // Adjust insertIndex for the next insertion + insertIndex += childId.length + 1; + } + + // Insert newProps at the updated insertion index + if (newProps) { + line = `${line.substring( + 0, + insertIndex + )} ${newProps}${line.substring(insertIndex)}`; + } + } else { + // If the regex test fails but it's the first line, just add the id + if (!trimmedLine.includes(`id=`) && childId) { + let spaceIndex = trimmedLine.indexOf(' '); + if (spaceIndex === -1) { + // If no space found, use position before '>' + spaceIndex = trimmedLine.indexOf('>'); + } + line = `${trimmedLine.substring( + 0, + spaceIndex + )} ${childId} ${trimmedLine.substring(spaceIndex)}`; + } + } + } else if (index > 0 && tagRegExp.test(trimmedLine)) { + insertIndex = line.indexOf(`<${name}`) + `<${name}`.length; + if (newProps) { + line = `${line.substring(0, insertIndex)} ${newProps}${line.substring( + insertIndex + )}`; + } } else { - activeLink = '"' + childElement.attributes.compLink + '"'; + // For other lines, no changes are made + line = trimmedLine; } + // Return the line with its original indentation preserved + return originalIndent + line; // Avoid trimming here as line may already include necessary spaces + }); + return modifiedJsx; + } + + const muiGenerator = (child, level = 0) => { + let childId = ''; + let passedInPropsString = ''; + + const MUIComp = MUITypes.find((el) => el.tag === child.name); + const MUIName = MUIComp.name; + // 'passedInProps' will be where the props from the MUI Props panel will saved + child.passedInProps.forEach((prop) => { + passedInPropsString += `${prop.key}={${prop.key}} `; + }); + + MUIComp.defaultProps.forEach((prop) => { + passedInPropsString += `${prop}`; + }); + + if (child.childId) { + childId = `id="${+child.childId}"`; } - const nestable = - childElement.tag === 'h1' || - childElement.tag === 'h2' || - childElement.tag === 'a' || - childElement.tag === 'span' || - childElement.tag === 'button' || - childElement.tag === 'p' || - childElement.tag === 'div' || - childElement.tag === 'form' || - childElement.tag === 'ol' || - childElement.tag === 'ul' || - childElement.tag === 'menu' || - childElement.tag === 'li' || - childElement.tag === 'Switch' || - childElement.tag === 'Route'; - - if (childElement.tag === 'img') { - return `${levelSpacer(level, 5)}<${ - childElement.tag - } src=${activeLink} ${elementTagDetails(childElement)}/>${levelSpacer( - 2, - 3 + level - )}`; - } else if (childElement.tag === 'a') { - return `${levelSpacer(level, 5)}<${ - childElement.tag - } href=${activeLink} ${elementTagDetails(childElement)}>${innerText}${levelSpacer(2, 3 + level)}`; - } else if (childElement.tag === 'input') { - return `${levelSpacer(level, 5)}<${childElement.tag}${elementTagDetails( - childElement - )}>${levelSpacer(2, 3 + level)}`; - } else if (childElement.tag === 'Link' && projectType === 'Classic React') { - return `${levelSpacer(level, 5)} - ${tabSpacer(level)}${writeNestedElements( - childElement.children, - level + 1 - )}${innerText} - ${tabSpacer(level - 1)}${levelSpacer(2, 3 + level)}`; - } else if (childElement.tag === 'Link' && projectType === 'Next.js') { - return `${levelSpacer( - level, - 5 - )} - ${tabSpacer(level)}${innerText}${writeNestedElements( - childElement.children, + // Indent the JSX generated for MUI components based on the provided level + const indentedJSX = MUIComp.jsx.map( + (line) => `${' '.repeat(level)}${line}` + ); + let modifiedJSx = modifyAndIndentJsx( + indentedJSX, + passedInPropsString, + childId, + MUIName + ); + + // Handle nested components, if any + if (child.children && child.children.length > 0) { + const nestedJsx = writeNestedElements(child.children, level + 1); + modifiedJSx = insertNestedJsxBeforeClosingTag( + modifiedJSx.join('\n'), + nestedJsx, level + 1 - )} - ${tabSpacer(level - 1)}${levelSpacer(2, 3 + level)}`; - } else if (childElement.tag === 'Image') { - return `${levelSpacer(level, 5)}<${ - childElement.tag - } src=${activeLink} ${elementTagDetails(childElement)}/>`; - } else if (nestable) { + ).split('\n'); + } + return modifiedJSx.join('\n'); + }; + + const handleRouteLink = (child, level) => { + const jsxArray = []; + const indentation = ' '.repeat(level); + + if (projectType === 'Next.js') { + // Next.js uses Link with the 'href' attribute and requires an tag inside + jsxArray.push( + `` + ); + jsxArray.push( + `${indentation} ${child.displayName || child.name}` + ); + jsxArray.push(`${indentation}`); + } else if (projectType === 'Gatsby.js') { + // Gatsby uses Link with the 'to' attribute + jsxArray.push( + `${ + child.displayName || child.name + }` + ); + } else if (projectType === 'Classic React') { + // Classic React might use react-router-dom's Link or another routing method + jsxArray.push( + `${ + child.displayName || child.name + }` + ); + } else { + // Fallback or default handling, such as a simple anchor tag + jsxArray.push( + `${ + child.displayName || child.name + }` + ); + } + + // Apply indentation to each line and join them with new lines + return jsxArray.map((line) => `${indentation}${line}`).join('\n'); + }; + + // Function to collect MUI imports as components are processed + function collectMUIImports(component, MUITypes, muiImports) { + if (component.type === 'MUI Component') { + const muiComponent = MUITypes.find((m) => m.id === component.typeId); if ( - (childElement.tag === 'Route' || childElement.tag === 'Switch') && - projectType === 'Next.js' + muiComponent && + muiComponent.imports && + muiComponent.imports.length > 0 ) { - return `${writeNestedElements(childElement.children, level)}`; + muiComponent.imports.forEach((importStatement) => { + if (!muiImports.has(importStatement)) { + muiImports.add(importStatement); + } + }); + } + + // Recursively collect imports from child components if they exist + if (component.children) { + component.children.forEach((child) => + collectMUIImports(child, MUITypes, muiImports) + ); } - const routePath = - childElement.tag === 'Route' ? ' ' + 'exact path=' + activeLink : ''; - return `${levelSpacer(level, 5)}<${childElement.tag}${elementTagDetails( - childElement - )}${routePath}> - ${tabSpacer(level)}${innerText} - ${tabSpacer(level)}${writeNestedElements( - childElement.children, - level + 1 - )} - ${tabSpacer(level - 1)}${levelSpacer( - 2, - 3 + level - )}`; - } else if (childElement.tag !== 'separator') { - return `${levelSpacer(level, 5)}<${childElement.tag}${elementTagDetails( - childElement - )}>${innerText}${levelSpacer(2, 3 + level)}`; } + } + // Generate MUI import strings from a set of import statements + function generateMUIImportStatements(muiImports) { + return Array.from(muiImports).join('\n'); + } + + const writeNestedElements = (enrichedChildren, level = 0) => { + return enrichedChildren.flatMap((child) => { + if (child.type === 'Component') { + return [`<${child.name} ${elementTagDetails(child)} />`]; + } else if (child.type === 'HTML Element') { + return elementGenerator(child, level); + } else if (child.type === 'MUI Component') { + return muiGenerator(child, level); + } else if (child.type === 'Route Link') { + return handleRouteLink(child, level); + } + return []; // Return empty array for unhandled cases + }); }; - // write all code that will be under the "return" of the component - const writeNestedElements = (enrichedChildren: any, level: number = 2) => { - return `${enrichedChildren - .map((child: ChildElement) => { - if (child.type === 'Component') { - return `<${child.name} ${elementTagDetails(child)}/>`; - } else if (child.type === 'HTML Element') { - return elementGenerator(child, level); - } - // route links are for gatsby.js and next.js feature. if the user creates a route link and then switches projects, generate code for a normal link instead - else if (child.type === 'Route Link') { - if (projectType === 'Next.js') { - // if route link points to index, to go endpoint / rather than /index - if (child.name === 'index') - return ``; - else - return ``; - } else if (projectType === 'Gatsby.js') { - if (child.name === 'index') - return `
${child.name}
`; - else - return `
${child.name}
`; - } else return ``; - } - }) - .filter((element) => !!element) - .join('')}`; - }; + // function to properly incorporate the user created state that is stored in the application state const writeStateProps = (stateArray: String[]) => { let stateToRender: String = ''; for (const element of stateArray) { - stateToRender += levelSpacer(2, 2) + element + ';'; + stateToRender += levelSpacer(2) + element + ';'; } return stateToRender; }; const enrichedChildren: ChildElement[] = getEnrichedChildren(currComponent); + // import statements differ between root (pages) and regular components (components) const importsMapped = projectType === 'Next.js' || projectType === 'Gatsby.js' @@ -439,14 +561,6 @@ const generateUnformattedCode = ( return `import ${comp} from './${comp}'`; }) .join('\n'); - const createState = (stateProps: StateProp[]) => { - let state = '{'; - stateProps.forEach((ele) => { - state += ele.key + ':' + JSON.stringify(ele.value) + ', '; - }); - state = state.substring(0, state.length - 2) + '}'; - return state; - }; // create final component code. component code differs between classic react, next.js, gatsby.js // classic react code @@ -468,48 +582,53 @@ const generateUnformattedCode = ( return acc; }, {}); - //return a string with all contexts provider in component's body const createRender = () => { - let result = `${writeNestedElements(enrichedChildren)}`; - if (importReactRouter) result = `\n ${result}\n `; - if (allContext.length < 1) return result; - - if (currComponent.name === 'App') { - allContext.reverse().forEach((el, i) => { - let tabs = `\t\t`; - if (i === allContext.length - 1) { - tabs = `\t\t\t`; - } - result = `${tabs.repeat(allContext.length - i)}<${ - el.name - }Provider>\n ${result}\n ${tabs.repeat(allContext.length - i)}`; - }); + const jsxElements = writeNestedElements(enrichedChildren); + + let jsxString = jsxElements.join('\n'); + + // Adding Router wrapper if necessary + if (importReactRouter) { + jsxString = `\n${indentLinesExceptFirst( + jsxString, + 1 + )}\n`; } - return result; - }; - //decide which imports statements to use for which components - const createContextImport = () => { - if (!(currComponent.name in componentContext)) return ''; + // Add context providers if the component is 'App' and contexts are defined + if (allContext.length > 0 && currComponent.name === 'App') { + allContext + .slice() + .reverse() + .forEach((el, index) => { + const indent = ' '.repeat(index + 1); + jsxString = `${indent}<${ + el.name + }Provider>\n${indentLinesExceptFirst( + jsxString, + index + 1 + )}\n${indent}`; + }); + } - let importStr = ''; - componentContext[currComponent.name].forEach((context) => { - importStr += `import { ${context} } from '../contexts/${context}.js'\n`; - }); + return jsxString; + }; - return importStr; + const indentLinesExceptFirst = (text, level) => { + const lines = text.split('\n'); + const firstLine = lines.shift(); // Remove the first line + const indentation = ' '.repeat(level); + const indentedLines = lines.map((line) => `${indentation}${line}`); + return `${firstLine}\n${indentedLines.join('\n')}`; }; - //call use context hooks for components that are consuming contexts - //LEGACY PD: - const createUseContextHook = () => { + //decide which imports statements to use for which components + const createContextImport = () => { if (!(currComponent.name in componentContext)) return ''; let importStr = ''; componentContext[currComponent.name].forEach((context) => { - importStr += ` const [${context}Val] = useContext(${context})\n`; + importStr += `import { ${context} } from '../contexts/${context}.js'\n`; }); return importStr; @@ -540,7 +659,7 @@ const generateUnformattedCode = ( return importStr; }; - + const muiImportStatements = generateMUIImportStatements(muiImports); let generatedCode = "import React, { useState, useEffect, useContext} from 'react';\n\n"; generatedCode += currComponent.name === 'App' ? contextImports : ''; @@ -549,6 +668,7 @@ const generateUnformattedCode = ( : ``; generatedCode += createContextImport() ? `${createContextImport()}\n` : ''; generatedCode += importsMapped ? `${importsMapped}\n` : ''; + generatedCode += muiImportStatements ? `${muiImportStatements}\n\n` : ''; // below is the return statement of the codepreview generatedCode += `const ${currComponent.name} = (props) => {\n`; generatedCode += writeStateProps(currComponent.useStateCodes) @@ -560,7 +680,7 @@ const generateUnformattedCode = ( generatedCode += ` return( <> - ${createRender()} + ${indentLinesExceptFirst(createRender(), 3)} );`; generatedCode += `\n}`; diff --git a/app/src/helperFunctions/renderChildren.tsx b/app/src/helperFunctions/renderChildren.tsx index 9a19901a..08a5711e 100644 --- a/app/src/helperFunctions/renderChildren.tsx +++ b/app/src/helperFunctions/renderChildren.tsx @@ -19,12 +19,7 @@ const renderChildren = (children: ChildElement[]) => { return children.map((child: ChildElement, i: number) => { const { type, style, childId, children, attributes, name } = child; - // console.log('state components', state.components); - // console.log('state MUI type', state.MUITypes); let { typeId } = child; - // console.log('typeID', typeId); - // console.log('name', name); - // console.log('child.name', state.components[typeId - 1].name); if (name === '') child.name = state.components[typeId - 1].name; // A DirectChildComponent is an instance of a top level component // This component will render IndirectChild components (div/components rendered inside a child component) diff --git a/app/src/interfaces/Interfaces.ts b/app/src/interfaces/Interfaces.ts index 1c38b7f9..b5a9bb0e 100644 --- a/app/src/interfaces/Interfaces.ts +++ b/app/src/interfaces/Interfaces.ts @@ -115,7 +115,10 @@ export interface MUIType { framework: string; nestable: boolean; imports: any[]; - props: any[]; + propOptions: any[]; + defaultProps: any[]; + jsx: any[]; + children: []; } export interface DragItem extends DragObjectWithType { newInstance: boolean; diff --git a/app/src/redux/MUITypes.ts b/app/src/redux/MUITypes.ts index 6d31525f..e7371525 100644 --- a/app/src/redux/MUITypes.ts +++ b/app/src/redux/MUITypes.ts @@ -13,7 +13,8 @@ const MUITypes: MUIType[] = [ framework: 'reactClassic', nestable: true, imports: ["import Button from '@mui/material/Button'"], - props: [ + defaultProps: ['variant="contained"'], + propOptions: [ 'children', 'classes', 'color', @@ -29,7 +30,9 @@ const MUITypes: MUIType[] = [ 'startIcon', 'sx', 'variant' - ] + ], + jsx: [``], + children: [] }, // do not move this separator element out of index 1 in this array // in componentReducer.ts, separator is referenced as 'initialState.HTMLTypes[1]' @@ -44,7 +47,10 @@ const MUITypes: MUIType[] = [ framework: '', nestable: true, imports: [], - props: [] + propOptions: [], + defaultProps: [], + jsx: [], + children: [] }, { id: 31, @@ -60,7 +66,8 @@ const MUITypes: MUIType[] = [ "import Box from '@mui/material/Box'", "import TextField from '@mui/material/TextField'" ], - props: [ + defaultProps: ['variant="outlined"'], + propOptions: [ 'autoComplete', 'autoFocus', 'classes', @@ -93,7 +100,13 @@ const MUITypes: MUIType[] = [ 'type', 'value', 'variant' - ] + ], + jsx: [ + ` :not(style)': { m: 1, width: '25ch' }}} noValidate autoComplete="off">`, + ` `, + `` + ], + children: [] }, { id: 41, @@ -113,7 +126,32 @@ const MUITypes: MUIType[] = [ "import Button from '@mui/material/Button'", "import Typography from '@mui/material/Typography'" ], - props: ['children', 'classes', 'raised', 'sx'] + defaultProps: ['sx={{ minWidth: 275 }}'], + propOptions: ['children', 'classes', 'raised', 'sx'], + jsx: [ + ``, + ` `, + ` `, + ` Word of the Day`, + ` `, + ` `, + ` benevolent`, + ` `, + ` `, + ` adjective`, + ` `, + ` `, + ` well meaning and kindly.`, + `
`, + ` {'"a benevolent smile"'}`, + `
`, + `
`, + ` `, + ` `, + ` `, + `
` + ], + children: [] }, { id: 51, @@ -129,7 +167,8 @@ const MUITypes: MUIType[] = [ "import Box from '@mui/material/Box'", "import Typography from '@mui/material/Typography'" ], - props: [ + defaultProps: ['variant="h1"'], + propOptions: [ 'align', 'children', 'classes', @@ -140,7 +179,9 @@ const MUITypes: MUIType[] = [ 'sx', 'variant', 'variantMapping' - ] + ], + jsx: [` h1. Heading `], + children: [] } ]; From 2afa4d9554d8abb5f6238ddd1cdca00fca128977 Mon Sep 17 00:00:00 2001 From: Heather Pfeiffer Date: Thu, 25 Apr 2024 15:21:11 -0400 Subject: [PATCH 04/18] research demoRender --- app/src/components/left/ComponentDrag.tsx | 25 ++++++++++++++++++++++- app/src/components/main/DemoRender.tsx | 6 +++--- app/src/containers/CustomizationPanel.tsx | 1 + app/src/helperFunctions/generateCode.ts | 1 + 4 files changed, 29 insertions(+), 4 deletions(-) diff --git a/app/src/components/left/ComponentDrag.tsx b/app/src/components/left/ComponentDrag.tsx index d30be99b..c370217a 100644 --- a/app/src/components/left/ComponentDrag.tsx +++ b/app/src/components/left/ComponentDrag.tsx @@ -5,6 +5,7 @@ import makeStyles from '@mui/styles/makeStyles'; import { useSelector } from 'react-redux'; import ComponentPanelItem from '../right/ComponentPanelItem'; +console.log('line 8 ComponentDrag') const useStyles = makeStyles({ panelWrapper: { @@ -12,7 +13,7 @@ const useStyles = makeStyles({ flexDirection: 'column', alignItems: 'center', flexGrow: 1, - overflow: 'auto' + overflow: 'auto', }, panelWrapperList: { minHeight: 'auto' @@ -28,6 +29,12 @@ const useStyles = makeStyles({ const ComponentDrag = ({ isVisible, isThemeLight }): JSX.Element | null => { const classes = useStyles(); const state = useSelector((store: RootState) => store.appState); + console.log("isVisible", isVisible) + console.log("isThemeLight", isThemeLight) + console.log("isVisible", isVisible) + console.log("state", state) + console.log("stateMUI", state.MUITypes) + const isFocus = (targetId: Number) => { console.log('targetID line 33', targetId) @@ -73,3 +80,19 @@ const ComponentDrag = ({ isVisible, isThemeLight }): JSX.Element | null => { export default ComponentDrag; + +const propsArray4 = [ + { Name: "align", + Type: "'center' | 'inherit' | 'justify' | 'left' | 'right'", + Default: "'inherit'", + Description: "Set the text-align on the component." }, + { Name: "children", Type: "node" }, + { Name: "classes", Type: "object" }, + { Name: "component", Type: "elementType", Description: "The component used for the root node." }, + { Name: "gutterBottom", Type: "bool", Default: "false", Description: "If true, the text will have a bottom margin." }, + { Name: "noWrap", Type: "bool", Default: "false", Description: "If true, the text will not wrap, but instead will truncate with a text overflow ellipsis." }, + { Name: "paragraph", Type: "bool", Default: "false", Description: "If true, the element will be a paragraph element." }, + { Name: "sx", Type: "Array | func | object" }, + { Name: "variant", Type: "'body1' | 'body2' | 'button' | 'caption' | 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'inherit' | 'overline' | 'subtitle1' | 'subtitle2' | string", Default: "'body1'", Description: "Applies the theme typography styles." }, + { Name: "variantMapping", Type: "object", Default: "{ h1: 'h1', h2: 'h2', h3: 'h3', h4: 'h4', h5: 'h5', h6: 'h6', subtitle1: 'h6', subtitle2: 'h6', body1: 'p', body2: 'p', inherit: 'p' }", Description: "The component maps the variant prop to a range of different HTML element types." } +]; diff --git a/app/src/components/main/DemoRender.tsx b/app/src/components/main/DemoRender.tsx index 83474b89..a31eeb7f 100644 --- a/app/src/components/main/DemoRender.tsx +++ b/app/src/components/main/DemoRender.tsx @@ -85,7 +85,6 @@ const DemoRender = (): JSX.Element => { if (!event.data || typeof event.data.data !== 'string') return; const component: string = event.data.data.split('/').at(-1); - console.log('component', component) // If components aren't defined or component isn't a string, return early if (!state.components || !component) return; @@ -110,6 +109,7 @@ const DemoRender = (): JSX.Element => { const elementType = element.name; const childId = element.childId; const elementStyle = element.style; + console.log("elementType", elementType) console.log("elementStyle", elementStyle) const innerText = element.attributes.compText; const classRender = element.attributes.cssClasses; @@ -167,7 +167,7 @@ const DemoRender = (): JSX.Element => { > {innerText} {renderedChildren} - + , ); else if (elementType === 'Switch') componentsToRender.push({renderedChildren}); @@ -214,7 +214,7 @@ const DemoRender = (): JSX.Element => { return; } }); - console.log('code line 215', code) + console.log('This is the CODE on line 215 of DemoRender', code) //writes our stylesheet from state to the code code += ``; diff --git a/app/src/containers/CustomizationPanel.tsx b/app/src/containers/CustomizationPanel.tsx index 49748638..53e04036 100644 --- a/app/src/containers/CustomizationPanel.tsx +++ b/app/src/containers/CustomizationPanel.tsx @@ -42,6 +42,7 @@ import { RootState } from '../redux/store'; import { emitEvent } from '../helperFunctions/socket'; import { Number } from 'mongoose'; + // Previously named rightContainer, Renamed to Customizationpanel this now hangs on BottomTabs // need to pass in props to use the useHistory feature of react router const CustomizationPanel = ({ isThemeLight }): JSX.Element => { diff --git a/app/src/helperFunctions/generateCode.ts b/app/src/helperFunctions/generateCode.ts index 35bbf93e..03630071 100644 --- a/app/src/helperFunctions/generateCode.ts +++ b/app/src/helperFunctions/generateCode.ts @@ -47,6 +47,7 @@ const generateUnformattedCode = ( contextParam: any ) => { const components = [...comps]; + console.log("hi from line 50 in the generateCode") // find the component that we're going to generate code for const currComponent = components.find((elem) => elem.id === componentId); // find the unique components that we need to import into this component file From 01330c6c32e21d2c9ab28bc300354a82efbb84ca Mon Sep 17 00:00:00 2001 From: Heather Pfeiffer Date: Sat, 27 Apr 2024 18:48:35 -0400 Subject: [PATCH 05/18] iframe preview updates and logs --- app/src/components/left/ComponentDrag.tsx | 8 - app/src/components/main/DemoRender.tsx | 1165 +++++++++++++++++--- app/src/helperFunctions/generateCode.ts | 1 - app/src/interfaces/Interfaces.ts | 9 +- app/src/redux/MUITypes.ts | 95 ++ app/src/serverConfig.d.ts | 4 + app/src/serverConfig.ts | 19 + server/controllers/userStylesController.ts | 1 + server/routers/stylesRouter.ts | 1 + 9 files changed, 1149 insertions(+), 154 deletions(-) create mode 100644 app/src/serverConfig.d.ts create mode 100644 app/src/serverConfig.ts diff --git a/app/src/components/left/ComponentDrag.tsx b/app/src/components/left/ComponentDrag.tsx index c370217a..6b0ae66f 100644 --- a/app/src/components/left/ComponentDrag.tsx +++ b/app/src/components/left/ComponentDrag.tsx @@ -5,7 +5,6 @@ import makeStyles from '@mui/styles/makeStyles'; import { useSelector } from 'react-redux'; import ComponentPanelItem from '../right/ComponentPanelItem'; -console.log('line 8 ComponentDrag') const useStyles = makeStyles({ panelWrapper: { @@ -29,16 +28,9 @@ const useStyles = makeStyles({ const ComponentDrag = ({ isVisible, isThemeLight }): JSX.Element | null => { const classes = useStyles(); const state = useSelector((store: RootState) => store.appState); - console.log("isVisible", isVisible) - console.log("isThemeLight", isThemeLight) - console.log("isVisible", isVisible) - console.log("state", state) - console.log("stateMUI", state.MUITypes) const isFocus = (targetId: Number) => { - console.log('targetID line 33', targetId) - console.log('componentID line 34', state.canvasFocus.componentId) return state.canvasFocus.componentId === targetId ? true : false; }; diff --git a/app/src/components/main/DemoRender.tsx b/app/src/components/main/DemoRender.tsx index a31eeb7f..80006aab 100644 --- a/app/src/components/main/DemoRender.tsx +++ b/app/src/components/main/DemoRender.tsx @@ -7,13 +7,14 @@ import { } from 'react-router-dom'; import React, { useEffect, useRef } from 'react'; import { useDispatch, useSelector } from 'react-redux'; - +import MUITypes from '../../redux/MUITypes'; import Box from '@mui/material/Box'; import { Component } from '../../interfaces/Interfaces'; import ReactDOMServer from 'react-dom/server'; import { RootState } from '../../redux/store'; import { changeFocus } from '../../redux/reducers/slice/appStateSlice'; import { blue } from '@mui/material/colors'; +import serverConfig from '../../serverConfig'; // DemoRender is the full sandbox demo of our user's custom built React components. DemoRender references the design specifications stored in state to construct // real react components that utilize hot module reloading to depict the user's prototype application. @@ -36,51 +37,114 @@ const DemoRender = (): JSX.Element => { const html = ` - - - -
-
- - - + + + + + + + + + + + +
+ + + `; + // document.addEventListener('DOMContentLoaded', function() { + + // function logToParentConsole(...args) { + // const payload = args.map(arg => (typeof arg === 'object' && arg !== null) ? JSON.stringify(arg) : arg); + // window.parent.postMessage({ type: 'log', data: payload }, '*'); + // } + // console.log = logToParentConsole; + + // const MaterialUI = window.MaterialUI; + // const ReactRouterDOM = window.ReactRouterDOM; + + // console.log('MaterialUI:', MaterialUI); + // console.log('ReactRouterDOM:', ReactRouterDOM); + // const componentMap = { + // Box: MaterialUI?.Box, + // Button: MaterialUI?.Button, + // Link: ReactRouterDOM?.Link, + // TextField: MaterialUI?.TextField, + // Card: MaterialUI?.Card, + // CardContent: MaterialUI?.CardContent, + // Typography: MaterialUI?.Typography, + // CardActions: MaterialUI?.CardActions + // }; + // const createElementFromData = (data) => { + // const { type, props, children } = data; + // const Component = componentMap[type] || 'div'; + // return React.createElement( + // Component, + // props, + // children && (Array.isArray(children) ? children.map(createElementFromData) : createElementFromData(children)) + // ); + // }; + // window.addEventListener('message', (event) => { + // console.log('event', event); + // const dataArr = event.data.replace(/}{/g, '},,{').replace(/}{/g, '>,,{').split(',,'); + // console.log('dataArr', dataArr); + // const container = document.getElementById('app'); + // dataArr.forEach(segment => { + // if(segment.trim().startsWith('{') && segment.trim().endsWith('}')) { + // try { + // const jsonData = JSON.parse(segment); + // console.log('jsonData', jsonData); + // const component = createElementFromData(jsonData); + // console.log('component', component); + // ReactDOM.render(component, container); + // } catch (err) { + // console.error("Error parsing JSON:", err); + // } + // } else { + // container.insertAdjacentHTML('beforeend', segment); + // container.querySelectorAll('a').forEach(element => { + // element.addEventListener('click', (event) => { + // event.preventDefault(); + // window.top.postMessage(event.currentTarget.href, '*'); + // }); + // }); + // } + // }); + // }); + + // const handleClickInsideIframe = () => { + // window.parent.postMessage('iframeClicked', '*'); + // }; + // const handleMouseUpInsideIframe = () => { + // window.parent.postMessage('iframeMouseUp', '*'); + // }; + // const handleMouseMoveInsideIframe = (e) => { + // const msgData = { + // type: 'iframeMouseMove', + // clientY: e.clientY + 70 // Adjust according to your needs + // }; + // window.parent.postMessage(msgData, '*'); + // }; + + // window.addEventListener('click', handleClickInsideIframe); + // window.addEventListener('mouseup', handleMouseUpInsideIframe); + // window.addEventListener('mousemove', handleMouseMoveInsideIframe); + // }); + window.onmessage = (event) => { - // console.log('event', event) // If event.data or event.data.data is undefined, return early if (!event.data || typeof event.data.data !== 'string') return; @@ -100,103 +164,201 @@ const DemoRender = (): JSX.Element => { // This function is the heart of DemoRender it will take the array of components stored in state and dynamically construct the desired React component for the live demo // Material UI is utilized to incorporate the apporpriate tags with specific configuration designs as necessitated by HTML standards. - const componentBuilder = (array: any, key: number = 0) => { + // const componentBuilder = (array: any, key: number = 0) => { + // const componentsToRender = []; + // for (const element of array) { + // if (element.name !== 'separator') { + // const elementType = element.name; + // console.log('elementType', elementType) + // const childId = element.childId; + // const elementStyle = element.style; + // const innerText = element.attributes.compText; + // const classRender = element.attributes.cssClasses; + // const activeLink = element.attributes.compLink; + // let renderedChildren; + // if (element.type === 'MUI Component') { + // collectMUIImports(element, MUITypes, muiImports) + // } + // if ( + // elementType !== 'input' && + // elementType !== 'img' && + // elementType !== 'Image' && + // element.children.length > 0 + // ) { + // renderedChildren = componentBuilder(element.children); + // } + // if (elementType === 'input') + // componentsToRender.push( + // + // ); + // else if (elementType === 'img') + // componentsToRender.push( + // + // ); + // else if (elementType === 'Image') + // componentsToRender.push( + // + // ); + // else if (elementType === 'a' || elementType === 'Link') + // componentsToRender.push( + // + // {innerText} + // {renderedChildren} + // , + // ); + // else if (elementType === 'Switch') + // componentsToRender.push({renderedChildren}); + // else if (elementType === 'Route') + // componentsToRender.push( + // + // {renderedChildren} + // + // ); + // else + // componentsToRender.push( + // + // {innerText} + // {renderedChildren} + // + // ); + // key += 1; + // } + // } + // console.log('componentstoRender array line 196', componentsToRender) + // return componentsToRender; + // }; + + // window.addEventListener("message", receiveMessage, false); + + // function receiveMessage(event) { + // // Always verify origin + // if (event.origin !== serverConfig.API_BASE_URL2) return; + + // console.log("Received:", event.data); + // } + + window.addEventListener('message', function(event) { + if (event.origin !== serverConfig.API_BASE_URL2) return; // Ensure you replace this with your actual iframe origin + + if (event.data.type === 'log') { + console.log('Iframe log:', event.data.data.map(item => { + try { + return JSON.parse(item); // Try to parse each item in case it is a JSON string + } catch { + return item; // If not a JSON string, return the original item + } + })); + } +}); + + const componentBuilder2 = (array, key = 0) => { const componentsToRender = []; for (const element of array) { - console.log("array") - console.log(array) - if (element.name !== 'separator') { - const elementType = element.name; + if (element.name === 'separator') continue; + const elementType = element.name; const childId = element.childId; const elementStyle = element.style; - console.log("elementType", elementType) - console.log("elementStyle", elementStyle) const innerText = element.attributes.compText; const classRender = element.attributes.cssClasses; const activeLink = element.attributes.compLink; - let renderedChildren; - if ( - elementType !== 'input' && - elementType !== 'img' && - elementType !== 'Image' && - element.children.length > 0 - ) { - renderedChildren = componentBuilder(element.children); - } - if (elementType === 'input') - componentsToRender.push( - - ); - else if (elementType === 'img') - componentsToRender.push( - - ); - else if (elementType === 'Image') - componentsToRender.push( - - ); - else if (elementType === 'a' || elementType === 'Link') - componentsToRender.push( - - {innerText} - {renderedChildren} - , - ); - else if (elementType === 'Switch') - componentsToRender.push({renderedChildren}); - else if (elementType === 'Route') - componentsToRender.push( - - {renderedChildren} - - ); - else + let renderedChildren = element.children && element.children.length > 0 ? componentBuilder2(element.children, ++key) : undefined; + const shouldSerialize = (element.type === 'MUI Component') ? true : false; + if (shouldSerialize) { + const baseData = MUITypes.find((m) => m.tag === elementType).componentData; + console.log('baseData', baseData) + if (!baseData) return null; + const componentData = { ...baseData, + props: { + ...(baseData.props || {}), + key: key++, + }, + children: renderedChildren, + } + componentsToRender.push(JSON.stringify(componentData)); + + // const serializedHtmlContent = JSON.stringify(htmlContent); + + } else { + let Component; + switch (elementType) { + case 'input': + Component = 'input' + break; + case 'img': + case 'Image': + Component = 'img' + break; + case 'a': + Component = 'a' + break; + case 'Switch': + Component = 'Switch' + break; + case 'Route': + Component = 'Route' + break; + default: + Component = elementType + break; + } + const childrenContent = []; + if (innerText) childrenContent.push(innerText); + if (renderedChildren) childrenContent.push(...renderedChildren); + const props = { + ...element.attributes, + className: classRender, + style: elementStyle, + key: key, + id: `rend${childId}`, + ...(elementType === 'img' || elementType === 'Image' ? { src: activeLink } : {}), + ...(elementType === 'a' || elementType === 'Link' ? { href: activeLink } : {}), + } + componentsToRender.push( - - {innerText} - {renderedChildren} - + + {childrenContent.length > 0 ? childrenContent.map((child, index) => ( + {child} + )) : null} + ); - key += 1; - } + } + key++; } - console.log('componentstoRender array line 196', componentsToRender) - return componentsToRender; - }; - + return componentsToRender + } //initializes our 'code' which will be whats actually in the iframe in the demo render //this will reset every time we make a change let code = ''; @@ -205,19 +367,23 @@ const DemoRender = (): JSX.Element => { (element) => element.id === state.canvasFocus.componentId ); - //writes each component to the code - componentBuilder(currComponent.children).forEach((element) => { - try { - code += ReactDOMServer.renderToString(element); - console.log('code line 211', code) - } catch { - return; + componentBuilder2(currComponent.children).forEach((element) => { + if (typeof element === 'string') { + console.log('element', element) + code += element; + } else if (React.isValidElement(element)) { + console.log('valid react element', element) + try { + const reactDomStringRender = ReactDOMServer.renderToString(element); + code += ReactDOMServer.renderToString(element); + } catch { + return; + } } }); - console.log('This is the CODE on line 215 of DemoRender', code) - //writes our stylesheet from state to the code - code += ``; + //writes our stylesheet from state to the code + // code += ``; //adds the code into the iframe useEffect(() => { //load the current state code when the iframe is loaded and when code changes @@ -227,12 +393,26 @@ const DemoRender = (): JSX.Element => { iframe.current.contentWindow.postMessage(code, '*'); }, [code]); +// useEffect(() => { +// const handleLoad = () => { +// iframe.current.contentWindow.postMessage(code, '*'); +// }; + +// const iframeElement = iframe.current; +// iframeElement.addEventListener('load', handleLoad); + +// // Clean up the event listener when the component unmounts or code changes +// return () => { +// iframeElement.removeEventListener('load', handleLoad); +// }; +// }, [code]); + return ( <>