- {renderChildren(currentComponent.children)}
- {remoteCursors.map(
- (cursor, idx) =>
- cursor.isVisible && (
-
- { }
- {cursor.remoteUserName}
-
- )
- )}
-
- {userList.length > 1 && (
-
- {toggleText === 'on' ? 'View Cursors' : 'Hide Cursors'}
-
- )}
-
-
- );
-});
-
-export default Canvas;
+import { Component, DragItem } from '../../interfaces/Interfaces';
+import { DropTargetMonitor, useDrop } from 'react-dnd';
+import React, { useEffect, useState, forwardRef } from 'react';
+import {
+ addChild,
+ changeFocus,
+ snapShotAction
+} from '../../redux/reducers/slice/appStateSlice';
+import { useDispatch, useSelector } from 'react-redux';
+import { debounce, throttle } from 'lodash';
+
+import Arrow from './Arrow';
+import { ItemTypes } from '../../constants/ItemTypes';
+import { RootState } from '../../redux/store';
+import { combineStyles } from '../../helperFunctions/combineStyles';
+import renderChildren from '../../helperFunctions/renderChildren';
+import { emitEvent, getSocket } from '../../helperFunctions/socket';
+import { FaMousePointer } from 'react-icons/fa';
+import { display } from 'html2canvas/dist/types/css/property-descriptors/display';
+import { ZoomIn, ZoomOut } from '@mui/icons-material';
+import { Button } from '@mui/material';
+
+interface CanvasProps {
+ zoom: number;
+}
+
+const Canvas = forwardRef(({ zoom }, ref) => {
+ const state = useSelector((store: RootState) => store.appState);
+ const contextParam = useSelector((store: RootState) => store.contextSlice);
+ const roomCode = useSelector((store: RootState) => store.roomSlice.roomCode);
+ const userName = useSelector((store: RootState) => store.roomSlice.userName);
+ const userList = useSelector((store: RootState) => store.roomSlice.userList);
+
+ //remote cursor data
+ const [remoteCursors, setRemoteCursors] = useState([]);
+
+ // Toggle switch for live cursor tracking
+ const [toggleSwitch, setToggleSwitch] = useState(true);
+
+ // Toggle button text for live cursor tracking button - on/off
+ const [toggleText, setToggleText] = useState('off');
+ const toggleButton = () => {
+ setToggleText(toggleText === 'on' ? 'off' : 'on');
+ };
+
+ // Prevents lagging and provides smoother user experience got live cursor tracking (milliseconds can be adjusted but 500ms is most optimal)
+ const debounceSetPosition = debounce((newX, newY) => {
+ //emit socket event every 300ms when cursor moves
+ if (userList.length > 1)
+ emitEvent('cursorData', roomCode, { x: newX, y: newY, userName });
+ }, 500);
+
+ const handleMouseMove = (e) => {
+ debounceSetPosition(e.clientX, e.clientY);
+ };
+
+ const handleCursorDataFromServer = (remoteData) => {
+ setRemoteCursors((prevState) => {
+ //check if received cursor data is from an existing user in the room
+ const cursorIdx = prevState.findIndex(
+ (cursor) => cursor.remoteUserName === remoteData.userName
+ );
+ //existing user
+ if (cursorIdx >= 0) {
+ //check if cursor position has changed
+ if (
+ prevState[cursorIdx].x !== remoteData.x ||
+ prevState[cursorIdx].y !== remoteData.y
+ ) {
+ //update existing user's cursor position
+ const updatedCursors = [...prevState];
+ updatedCursors[cursorIdx] = {
+ ...prevState[cursorIdx],
+ x: remoteData.x,
+ y: remoteData.y
+ };
+ return updatedCursors;
+ } else {
+ //return previous state if no change
+ return prevState;
+ }
+ } else {
+ //new user: add new user's cursor
+ return [
+ ...prevState,
+ {
+ x: remoteData.x,
+ y: remoteData.y,
+ remoteUserName: remoteData.userName,
+ isVisible: true
+ }
+ ];
+ }
+ });
+ };
+
+ // Removes the mouse cursor of the user that leaves the collaboration room.
+ const handleCursorDeleteFromServer = () => {
+ setRemoteCursors((prevRemoteCursors) =>
+ // filter cursors to include only those in the userList
+ prevRemoteCursors.filter((cursor) =>
+ userList.includes(cursor.remoteUserName)
+ )
+ );
+ };
+
+ // Function that will turn on/off socket depending on toggle Switch.
+ const handleToggleSwitch = () => {
+ setToggleSwitch(!toggleSwitch);
+ //checks the state before it's updated so need to check the opposite condition
+ if (toggleSwitch) {
+ socket.off('remote cursor data from server');
+ //make remote cursor invisible
+ setRemoteCursors((prevState) => {
+ const newState = prevState.map((cursor) => ({
+ ...cursor,
+ isVisible: false
+ }));
+ return newState;
+ });
+ } else {
+ socket.on('remote cursor data from server', (remoteData) =>
+ handleCursorDataFromServer(remoteData)
+ );
+ //make remote cursor visible
+ setRemoteCursors((prevState) =>
+ prevState.map((cursor) => ({
+ ...cursor,
+ isVisible: true
+ }))
+ );
+ }
+ };
+
+ //Function to handle the multiple click events of the toggle button.
+ const multipleClicks = () => {
+ handleToggleSwitch();
+ toggleButton();
+ };
+
+ const socket = getSocket();
+ //wrap the socket event listener in useEffect with dependency array as [socket], so the the effect will run only when: 1. After the initial rendering of the component 2. Every time the socket instance changes(connect, disconnect)
+ useEffect(() => {
+ if (socket) {
+ socket.on('remote cursor data from server', (remoteData) =>
+ handleCursorDataFromServer(remoteData)
+ );
+ }
+
+ return () => {
+ if (socket) socket.off('remote cursor data from server');
+ };
+ }, [socket]);
+
+ useEffect(() => {
+ handleCursorDeleteFromServer();
+ }, [userList]);
+
+ // find the current component based on the canvasFocus component ID in the state
+ const currentComponent: Component = state.components.find(
+ (elem: Component) => elem.id === state.canvasFocus.componentId
+ );
+
+ Arrow.deleteLines();
+
+ const dispatch = useDispatch();
+ // changes focus of the canvas to a new component / child
+ const changeFocusFunction = (
+ componentId?: number,
+ childId?: number | null
+ ) => {
+ dispatch(changeFocus({ componentId, childId }));
+ //if room exists, send focus dispatch to all users
+ if (roomCode) {
+ emitEvent('changeFocusAction', roomCode, {
+ componentId: componentId,
+ childId: childId
+ });
+ }
+ };
+
+ // onClickHandler is responsible for changing the focused component and child component
+ function onClickHandler(event: React.MouseEvent) {
+ event.stopPropagation();
+ changeFocusFunction(state.canvasFocus.componentId, null);
+ }
+
+ // stores a snapshot of state into the past array for UNDO. snapShotFunc is also invoked for nestable elements in DirectChildHTMLNestable.tsx
+ const snapShotFunc = () => {
+ // make a deep clone of state
+ const deepCopiedState = JSON.parse(JSON.stringify(state));
+ const focusIndex = state.canvasFocus.componentId - 1;
+ dispatch(
+ snapShotAction({
+ focusIndex: focusIndex,
+ deepCopiedState: deepCopiedState
+ })
+ );
+ };
+
+ // This hook will allow the user to drag items from the left panel on to the canvas
+ const [{ isOver }, drop] = useDrop({
+ accept: ItemTypes.INSTANCE,
+ drop: (item: DragItem, monitor: DropTargetMonitor) => {
+ const didDrop = monitor.didDrop();
+ // takes a snapshot of state to be used in UNDO and REDO cases
+ snapShotFunc();
+ // returns false for direct drop target
+ if (didDrop) {
+ return;
+ }
+ // 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({
+ type: item.instanceType,
+ typeId: item.instanceTypeId,
+ childId: null,
+ contextParam: contextParam
+ })
+ );
+
+ //emit the socket event
+ if (roomCode) {
+ emitEvent('addChildAction', roomCode, {
+ type: item.instanceType,
+ typeId: item.instanceTypeId,
+ childId: null,
+ contextParam: contextParam
+ });
+ }
+ } else if (item.newInstance && item.instanceType === 'Component') {
+ let hasDiffParent = false;
+ const components = state.components;
+ let newChildName = '';
+ // loop over components array
+ for (let i = 0; i < components.length; i++) {
+ const comp = components[i];
+ //loop over each componenets child
+ for (let j = 0; j < comp.children.length; j++) {
+ const child = comp.children[j];
+ if (child.name === 'separator') continue;
+ // check if the item.instanceTypeId matches and child ID
+ if (item.instanceTypeId === child.typeId) {
+ // check if the name of the parent matches the canvas focus name
+ // comp is the parent component
+ // currentComponent is the canvas.focus component
+ if (comp.name === currentComponent.name) {
+ i = components.length;
+ break;
+ } else {
+ // if false
+ // setCopiedComp(child);
+ hasDiffParent = true;
+ newChildName = child.name;
+ i = components.length;
+ break;
+ }
+ }
+ }
+ }
+ // if (!hasDiffParent) {
+ dispatch(
+ addChild({
+ type: item.instanceType,
+ typeId: item.instanceTypeId,
+ childId: null,
+ contextParam: contextParam
+ })
+ );
+ if (roomCode) {
+ emitEvent('addChildAction', roomCode, {
+ type: item.instanceType,
+ typeId: item.instanceTypeId,
+ childId: null,
+ contextParam: contextParam
+ });
+ }
+ }
+ },
+ collect: (monitor) => ({
+ isOver: !!monitor.isOver()
+ })
+ });
+
+ // Styling for Canvas
+ const defaultCanvasStyle: React.CSSProperties = {
+ width: '100%',
+ minHeight: '100%',
+ aspectRatio: 'auto 774 / 1200',
+ boxSizing: 'border-box',
+ transform: `scale(${zoom})`,
+ transformOrigin: 'top center'
+ };
+
+ // Combine the default styles of the canvas with the custom styles set by the user for that component
+ // The renderChildren function renders all direct children of a given component
+ // Direct children are draggable/clickable
+
+ const canvasStyle: React.CSSProperties = combineStyles(
+ defaultCanvasStyle,
+ currentComponent.style
+ );
+
+ // Array of colors that color code users as they join the room (In a set order)
+ const userColors = [
+ '#0671e3',
+ '#2fd64d',
+ '#f0c000',
+ '#fb4c64',
+ '#be5be8',
+ '#fe9c06',
+ '#f6352b',
+ '#1667d1',
+ '#1667d1',
+ '#50ed6a'
+ ];
+
+ const buttonStyle: React.CSSProperties = {
+ textAlign: 'center',
+ color: '#ffffff',
+ backgroundColor: '#151515',
+ zIndex: 0,
+ border: '2px solid #0671e3',
+ margin: '8px 0 0 8px'
+ };
+
+ return (
+
+ {renderChildren(currentComponent.children)}
+ {remoteCursors.map(
+ (cursor, idx) =>
+ cursor.isVisible && (
+
+ { }
+ {cursor.remoteUserName}
+
+ )
+ )}
+
+ {userList.length > 1 && (
+
+ {toggleText === 'on' ? 'View Cursors' : 'Hide Cursors'}
+
+ )}
+
+
+ );
+});
+
+export default Canvas;
diff --git a/app/src/components/main/DeleteButton.tsx b/app/src/components/main/DeleteButton.tsx
index 5e3ae0e2..f9e5027a 100644
--- a/app/src/components/main/DeleteButton.tsx
+++ b/app/src/components/main/DeleteButton.tsx
@@ -1,47 +1,49 @@
-import React from 'react';
-import { DeleteButtons } from '../../interfaces/Interfaces';
-import { useDispatch, useSelector } from 'react-redux';
-import { deleteChild } from '../../redux/reducers/slice/appStateSlice';
-import { RootState } from '../../redux/store';
-import { emitEvent } from '../../helperFunctions/socket';
-import { Clear } from '@mui/icons-material';
-
-function DeleteButton({ id, name, onClickHandler }: DeleteButtons) {
- const contextParam = useSelector((store: RootState) => store.contextSlice);
-
- const roomCode = useSelector((store: RootState) => store.roomSlice.roomCode);
-
- const dispatch = useDispatch();
-
- const deleteHTMLtype = (id: number) => {
- dispatch(deleteChild({ id: id, contextParam: contextParam }));
- if (roomCode) {
- emitEvent('deleteChildAction', roomCode, {
- id,
- contextParam
- });
-
- // console.log(
- // 'emit deleteChildAction event is triggered in DeleteButton.tsx'
- // );
- }
- };
-
- return (
-
- {
- event.stopPropagation();
- onClickHandler(event);
- deleteHTMLtype(id);
- }}
- >
-
-
-
- );
-}
-
-export default DeleteButton;
+import React from 'react';
+import { DeleteButtons } from '../../interfaces/Interfaces';
+import { useDispatch, useSelector } from 'react-redux';
+import { deleteChild } from '../../redux/reducers/slice/appStateSlice';
+import { RootState } from '../../redux/store';
+import { emitEvent } from '../../helperFunctions/socket';
+import { Clear } from '@mui/icons-material';
+
+function DeleteButton({ id, name, onClickHandler }: DeleteButtons) {
+ const contextParam = useSelector((store: RootState) => store.contextSlice);
+
+ const roomCode = useSelector((store: RootState) => store.roomSlice.roomCode);
+
+ const dispatch = useDispatch();
+
+ const deleteHTMLtype = (id: number) => {
+ dispatch(deleteChild({ id: id, contextParam: contextParam }));
+ if (roomCode) {
+ emitEvent('deleteChildAction', roomCode, {
+ id,
+ contextParam
+ });
+
+ // console.log(
+ // 'emit deleteChildAction event is triggered in DeleteButton.tsx'
+ // );
+ }
+ };
+
+ return (
+
+ {
+ event.stopPropagation();
+ if (typeof onClickHandler === 'function') {
+ onClickHandler(event);
+ }
+ deleteHTMLtype(id);
+ }}
+ >
+
+
+
+ );
+}
+
+export default DeleteButton;
diff --git a/app/src/components/main/DirectChildMUI.tsx b/app/src/components/main/DirectChildMUI.tsx
new file mode 100644
index 00000000..934ef48a
--- /dev/null
+++ b/app/src/components/main/DirectChildMUI.tsx
@@ -0,0 +1,90 @@
+import React from 'react';
+import { ChildElement, MUIType } from '../../interfaces/Interfaces';
+import { useDrag } from 'react-dnd';
+import { ItemTypes } from '../../constants/ItemTypes';
+import { combineStyles } from '../../helperFunctions/combineStyles';
+import globalDefaultStyle from '../../public/styles/globalDefaultStyles';
+import DeleteButton from './DeleteButton';
+import { useDispatch, useSelector } from 'react-redux';
+import { changeFocus } from '../../redux/reducers/slice/appStateSlice';
+import { RootState } from '../../redux/store';
+import { emitEvent } from '../../helperFunctions/socket';
+
+function DirectChildMUI({ childId, name, type, typeId, style }: ChildElement) {
+ const state = useSelector((store: RootState) => store.appState);
+
+ const roomCode = useSelector((store: RootState) => store.roomSlice.roomCode);
+
+ const dispatch = useDispatch();
+
+ // find the MUI element corresponding with this instance of an MUI element
+ // find the current component to render on the canvas
+ const MUIType: MUIType = state.MUITypes.find(
+ (type: MUIType) => type.id === typeId
+ );
+ // hook that allows component to be draggable
+ const [{ isDragging }, drag] = useDrag({
+ // setting item attributes to be referenced when updating state with new instance of dragged item
+ item: {
+ type: ItemTypes.INSTANCE,
+ newInstance: false,
+ childId: childId,
+ instanceType: type,
+ instanceTypeId: typeId
+ },
+ collect: (monitor: any) => ({
+ isDragging: !!monitor.isDragging()
+ })
+ });
+
+ const changeFocusFunction = (componentId: number, childId: number | null) => {
+ dispatch(changeFocus({ componentId, childId }));
+ if (roomCode) {
+ emitEvent('changeFocusAction', roomCode, {
+ componentId: componentId,
+ childId: childId
+ });
+ // console.log('emit focus event from DirectChildMUI');
+ }
+ };
+
+ // onClickHandler is responsible for changing the focused component and child component
+ function onClickHandler(event) {
+ event.stopPropagation();
+ changeFocusFunction(state.canvasFocus.componentId, childId);
+ }
+
+ // combine all styles so that higher priority style specifications overrule lower priority style specifications
+ // priority order is 1) style directly set for this child (style), 2) style of the referenced HTML element, and 3) default styling
+ const interactiveStyle = {
+ border:
+ state.canvasFocus.childId === childId
+ ? '2px solid #0671e3'
+ : '1px solid #31343A'
+ };
+
+ const combinedStyle = combineStyles(
+ combineStyles(combineStyles(globalDefaultStyle, MUIType.style), style),
+ interactiveStyle
+ );
+
+ return (
+
+
+ {MUIType.placeHolderShort}
+
+
+
+ );
+}
+
+export default DirectChildMUI;
diff --git a/app/src/components/main/DirectChildMUINestable.tsx b/app/src/components/main/DirectChildMUINestable.tsx
new file mode 100644
index 00000000..f30848cb
--- /dev/null
+++ b/app/src/components/main/DirectChildMUINestable.tsx
@@ -0,0 +1,240 @@
+import React, { useRef } from 'react';
+import { ChildElement, MUIType } from '../../interfaces/Interfaces';
+import { useDrag, useDrop, DropTargetMonitor } from 'react-dnd';
+import { ItemTypes } from '../../constants/ItemTypes';
+import { combineStyles } from '../../helperFunctions/combineStyles';
+import globalDefaultStyle from '../../public/styles/globalDefaultStyles';
+import renderChildren from '../../helperFunctions/renderChildren';
+import DeleteButton from './DeleteButton';
+import validateNewParent from '../../helperFunctions/changePositionValidation';
+import componentNest from '../../helperFunctions/componentNestValidation';
+import AddRoute from './AddRoute';
+import AddLink from './AddLink';
+import { useDispatch, useSelector } from 'react-redux';
+import { RootState } from '../../redux/store';
+import { emitEvent } from '../../helperFunctions/socket';
+
+import {
+ changeFocus,
+ changePosition,
+ addChild,
+ snapShotAction
+} from '../../redux/reducers/slice/appStateSlice';
+
+function DirectChildMUINestable({
+ childId,
+ type,
+ typeId,
+ style,
+ children,
+ name,
+ attributes
+}: ChildElement): React.JSX.Element {
+ const state = useSelector((store: RootState) => store.appState);
+ const contextParam = useSelector((store: RootState) => store.contextSlice);
+
+ const dispatch = useDispatch();
+ const roomCode = useSelector((store: RootState) => store.roomSlice.roomCode);
+
+ const ref = useRef(null);
+
+ // takes a snapshot of state to be used in UNDO and REDO cases. snapShotFunc is also invoked in Canvas.tsx
+ const snapShotFunc = () => {
+ //makes a deep clone of state
+ const deepCopiedState = JSON.parse(JSON.stringify(state));
+ const focusIndex = state.canvasFocus.componentId - 1;
+ //pushes the last user action on the canvas into the past array of Component
+ dispatch(
+ snapShotAction({
+ focusIndex: focusIndex,
+ deepCopiedState: deepCopiedState
+ })
+ );
+ };
+
+ // find the MUI element corresponding with this instance of an MUI element
+ // find the current component to render on the canvas
+ const MUIType: MUIType = state.MUITypes.find(
+ (type: MUIType) => type.id === typeId
+ );
+
+ // hook that allows component to be draggable
+ const [{ isDragging }, drag] = useDrag({
+ // setting item attributes to be referenced when updating state with new instance of dragged item
+ item: {
+ type: ItemTypes.INSTANCE,
+ newInstance: false,
+ childId: childId,
+ instanceType: type,
+ instanceTypeId: typeId,
+ name: name
+ },
+ canDrag: MUIType.id !== 1000, // dragging not permitted if element is separator
+ collect: (monitor: any) => {
+ return {
+ isDragging: !!monitor.isDragging()
+ };
+ }
+ });
+
+ // both useDrop and useDrag used here to allow canvas components to be both a drop target and drag source
+ const [{ isOver }, drop] = useDrop({
+ accept: ItemTypes.INSTANCE,
+ // triggered on drop
+ drop: (item: any, monitor: DropTargetMonitor) => {
+ const didDrop = monitor.didDrop();
+ // takes a snapshot of state to be used in UNDO and REDO cases
+ snapShotFunc();
+ if (didDrop) {
+ return;
+ }
+ // updates state with new instance
+ // 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) {
+ if (
+ (item.instanceType === 'Component' &&
+ componentNest(
+ state.components[item.instanceTypeId - 1].children,
+ childId
+ )) ||
+ item.instanceType !== 'Component'
+ ) {
+ dispatch(
+ addChild({
+ type: item.instanceType,
+ typeId: item.instanceTypeId,
+ childId: childId,
+ contextParam: contextParam
+ })
+ );
+ if (roomCode) {
+ emitEvent('addChildAction', roomCode, {
+ type: item.instanceType,
+ typeId: item.instanceTypeId,
+ childId: childId,
+ contextParam: contextParam
+ });
+
+ // console.log(
+ // 'emit addChildAction event is triggered in DirectChildMUINestable'
+ // );
+ }
+ }
+ }
+ // if item is not a new instance, change position of element dragged inside div so that the div is the new parent
+ else {
+ // check to see if the selected child is trying to nest within itself
+ if (validateNewParent(state, item.childId, childId) === true) {
+ dispatch(
+ changePosition({
+ currentChildId: item.childId,
+ newParentChildId: childId,
+ contextParam: contextParam
+ })
+ );
+ if (roomCode) {
+ emitEvent('changePositionAction', roomCode, {
+ currentChildId: item.childId,
+ newParentChildId: childId,
+ contextParam: contextParam
+ });
+
+ // console.log(
+ // 'emit changePosition event is triggered in DirectChildMUINestable'
+ // );
+ }
+ }
+ }
+ },
+
+ collect: (monitor: any) => {
+ return {
+ isOver: !!monitor.isOver({ shallow: true })
+ };
+ }
+ });
+
+ const changeFocusFunction = (componentId: number, childId: number | null) => {
+ dispatch(changeFocus({ componentId, childId }));
+ if (roomCode) {
+ emitEvent('changeFocusAction', roomCode, {
+ componentId: componentId,
+ childId: childId
+ });
+ // console.log('emit focus event from DirectChildMUINestable');
+ }
+ };
+
+ // onClickHandler is responsible for changing the focused component and child component
+ function onClickHandler(event) {
+ event.stopPropagation();
+ changeFocusFunction(state.canvasFocus.componentId, childId);
+ }
+
+ // combine all styles so that higher priority style specifications overrule lower priority style specifications
+ // priority order is 1) style directly set for this child (style), 2) style of the referenced MUI element, and 3) default styling
+ const defaultNestableStyle = { ...globalDefaultStyle };
+ const interactiveStyle = {
+ border:
+ state.canvasFocus.childId === childId
+ ? '2px solid #0671e3'
+ : '1px solid #31343A'
+ };
+
+ // interactive style to change color when nested element is hovered over
+ if (isOver) defaultNestableStyle['#3c59ba'];
+ defaultNestableStyle['backgroundColor'] = isOver
+ ? '#3c59ba'
+ : defaultNestableStyle['backgroundColor'];
+
+ const combinedStyle = combineStyles(
+ combineStyles(combineStyles(defaultNestableStyle, MUIType.style), style),
+ interactiveStyle
+ );
+
+ drag(drop(ref));
+
+ const routeButton = [];
+ if (typeId === 17) {
+ routeButton.push( );
+ }
+ if (typeId === 19) {
+ routeButton.push(
+
+ );
+ }
+
+ return (
+
+
+ {MUIType.placeHolderShort}
+
+ {attributes && attributes.compLink ? ` ${attributes.compLink}` : ''}
+
+ {routeButton}
+
+
+ {renderChildren(children)}
+
+ );
+}
+
+export default DirectChildMUINestable;
diff --git a/app/src/components/main/SeparatorChild.tsx b/app/src/components/main/SeparatorChild.tsx
index 7caef1bd..f2910ebb 100644
--- a/app/src/components/main/SeparatorChild.tsx
+++ b/app/src/components/main/SeparatorChild.tsx
@@ -1,169 +1,177 @@
-import React, { useRef } from 'react';
-import { ChildElement, HTMLType, DragItem } from '../../interfaces/Interfaces';
-import { useDrag, useDrop, DropTargetMonitor } from 'react-dnd';
-import { ItemTypes } from '../../constants/ItemTypes';
-import { combineStyles } from '../../helperFunctions/combineStyles';
-import globalDefaultStyle from '../../public/styles/globalDefaultStyles';
-import renderChildren from '../../helperFunctions/renderChildren';
-import validateNewParent from '../../helperFunctions/changePositionValidation';
-import componentNest from '../../helperFunctions/componentNestValidation';
-import { useDispatch, useSelector } from 'react-redux';
-import { RootState } from '../../redux/store';
-import {
- changeFocus,
- changePosition,
- addChild
-} from '../../redux/reducers/slice/appStateSlice';
-import { emitEvent } from '../../helperFunctions/socket';
-
-function SeparatorChild({
- childId,
- type,
- typeId,
- style,
- children
-}: ChildElement): JSX.Element {
- const state = useSelector((store: RootState) => store.appState);
- const contextParam = useSelector((store: RootState) => store.contextSlice);
-
- const roomCode = useSelector((store: RootState) => store.roomSlice.roomCode);
-
- const dispatch = useDispatch();
- const ref = useRef(null);
-
- // find the HTML element corresponding with this instance of an HTML element
- // find the current component to render on the canvas
- const HTMLType: HTMLType = state.HTMLTypes.find(
- (type: HTMLType) => type.id === typeId
- );
-
- // hook that allows component to be draggable
- const [{ isDragging }, drag] = useDrag({
- // setting item attributes to be referenced when updating state with new instance of dragged item
- item: {
- type: ItemTypes.INSTANCE,
- newInstance: false,
- childId: childId,
- instanceType: type,
- instanceTypeId: typeId
- },
- canDrag: HTMLType.id !== 1000, // dragging not permitted if element is separator
- collect: (monitor: any) => {
- return {
- isDragging: !!monitor.isDragging()
- };
- }
- });
-
- // both useDrop and useDrag used here to allow canvas components to be both a drop target and drag source
- const [{ isOver }, drop] = useDrop({
- accept: ItemTypes.INSTANCE,
- // triggered on drop
- drop: (item: DragItem, monitor: DropTargetMonitor) => {
- const didDrop = monitor.didDrop();
- if (didDrop) {
- return;
- }
- // updates state with new instance
- // 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) {
- if (
- (item.instanceType === 'Component' &&
- componentNest(
- state.components[item.instanceTypeId - 1].children,
- childId
- )) ||
- item.instanceType !== 'Component'
- ) {
- dispatch(
- addChild({
- type: item.instanceType,
- typeId: item.instanceTypeId,
- childId: childId,
- contextParam: contextParam
- })
- );
- if (roomCode) {
- emitEvent('addChildAction', roomCode, {
- type: item.instanceType,
- typeId: item.instanceTypeId,
- childId: childId,
- contextParam: contextParam
- });
-
- // console.log(
- // 'emit addChildAction event is triggered in SeparatorChild'
- // );
- }
- }
- }
- // if item is not a new instance, change position of element dragged inside separator so that separator is new parent (until replacement)
- else {
- // check to see if the selected child is trying to nest within itself
- if (validateNewParent(state, item.childId, childId) === true) {
- dispatch(
- changePosition({
- currentChildId: item.childId,
- newParentChildId: childId,
- contextParam: contextParam
- })
- );
- if (roomCode) {
- emitEvent('changePositionAction', roomCode, {
- currentChildId: item.childId,
- newParentChildId: childId,
- contextParam: contextParam
- });
-
- // console.log(
- // 'emit changePosition event is triggered in SeparatorChild'
- // );
- }
- }
- }
- },
-
- collect: (monitor: any) => {
- return {
- isOver: !!monitor.isOver({ shallow: true })
- };
- }
- });
-
- const changeFocusFunction = (componentId: number, childId: number | null) => {
- dispatch(changeFocus({ componentId, childId }));
- };
-
- // onClickHandler is responsible for changing the focused component and child component
- function onClickHandler(event) {
- event.stopPropagation();
- changeFocusFunction(state.canvasFocus.componentId, childId);
- }
-
- // combine all styles so that higher priority style specifications overrule lower priority style specifications
- // priority order is 1) style directly set for this child (style), 2) style of the referenced HTML element, and 3) default styling
- const defaultNestableStyle = { ...globalDefaultStyle };
- const separatorStyle = {
- padding: isOver ? '40px 10px' : '2px 10px',
- margin: '1px 10px',
- transition: 'padding 1s ease-out'
- };
-
- defaultNestableStyle['backgroundColor'] = isOver
- ? 'rgb(53, 78, 156)'
- : 'rgba(0, 0, 255, 0.0)';
-
- const combinedStyle = combineStyles(
- combineStyles(combineStyles(defaultNestableStyle, HTMLType.style), style),
- separatorStyle
- );
-
- drag(drop(ref));
- return (
-
- {renderChildren(children)}
-
- );
-}
-
-export default SeparatorChild;
+import React, { useRef } from 'react';
+import {
+ ChildElement,
+ HTMLType,
+ MUIType,
+ DragItem
+} from '../../interfaces/Interfaces';
+import { useDrag, useDrop, DropTargetMonitor } from 'react-dnd';
+import { ItemTypes } from '../../constants/ItemTypes';
+import { combineStyles } from '../../helperFunctions/combineStyles';
+import globalDefaultStyle from '../../public/styles/globalDefaultStyles';
+import renderChildren from '../../helperFunctions/renderChildren';
+import validateNewParent from '../../helperFunctions/changePositionValidation';
+import componentNest from '../../helperFunctions/componentNestValidation';
+import { useDispatch, useSelector } from 'react-redux';
+import { RootState } from '../../redux/store';
+import {
+ changeFocus,
+ changePosition,
+ addChild
+} from '../../redux/reducers/slice/appStateSlice';
+import { emitEvent } from '../../helperFunctions/socket';
+
+function SeparatorChild({
+ childId,
+ type,
+ typeId,
+ style,
+ children
+}: ChildElement): JSX.Element {
+ const state = useSelector((store: RootState) => store.appState);
+ const contextParam = useSelector((store: RootState) => store.contextSlice);
+
+ const roomCode = useSelector((store: RootState) => store.roomSlice.roomCode);
+
+ const dispatch = useDispatch();
+ const ref = useRef(null);
+
+ // find the HTML element corresponding with this instance of an HTML element
+ // find the current component to render on the canvas
+ const HTMLType: HTMLType = state.HTMLTypes.find(
+ (type: HTMLType) => type.id === typeId
+ );
+
+ const MUIType: MUIType = state.MUITypes.find(
+ (type: MUIType) => type.id === typeId
+ );
+ // hook that allows component to be draggable
+ const [{ isDragging }, drag] = useDrag({
+ // setting item attributes to be referenced when updating state with new instance of dragged item
+ item: {
+ type: ItemTypes.INSTANCE,
+ newInstance: false,
+ childId: childId,
+ instanceType: type,
+ instanceTypeId: typeId
+ },
+ canDrag: HTMLType.id !== 1000 || MUIType.id !== 1000, // dragging not permitted if element is separator
+ collect: (monitor: any) => {
+ return {
+ isDragging: !!monitor.isDragging()
+ };
+ }
+ });
+
+ // both useDrop and useDrag used here to allow canvas components to be both a drop target and drag source
+ const [{ isOver }, drop] = useDrop({
+ accept: ItemTypes.INSTANCE,
+ // triggered on drop
+ drop: (item: DragItem, monitor: DropTargetMonitor) => {
+ const didDrop = monitor.didDrop();
+ if (didDrop) {
+ return;
+ }
+ // updates state with new instance
+ // 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) {
+ if (
+ (item.instanceType === 'Component' &&
+ componentNest(
+ state.components[item.instanceTypeId - 1].children,
+ childId
+ )) ||
+ item.instanceType !== 'Component'
+ ) {
+ dispatch(
+ addChild({
+ type: item.instanceType,
+ typeId: item.instanceTypeId,
+ childId: childId,
+ contextParam: contextParam
+ })
+ );
+ if (roomCode) {
+ emitEvent('addChildAction', roomCode, {
+ type: item.instanceType,
+ typeId: item.instanceTypeId,
+ childId: childId,
+ contextParam: contextParam
+ });
+
+ // console.log(
+ // 'emit addChildAction event is triggered in SeparatorChild'
+ // );
+ }
+ }
+ }
+ // if item is not a new instance, change position of element dragged inside separator so that separator is new parent (until replacement)
+ else {
+ // check to see if the selected child is trying to nest within itself
+ if (validateNewParent(state, item.childId, childId) === true) {
+ dispatch(
+ changePosition({
+ currentChildId: item.childId,
+ newParentChildId: childId,
+ contextParam: contextParam
+ })
+ );
+ if (roomCode) {
+ emitEvent('changePositionAction', roomCode, {
+ currentChildId: item.childId,
+ newParentChildId: childId,
+ contextParam: contextParam
+ });
+
+ // console.log(
+ // 'emit changePosition event is triggered in SeparatorChild'
+ // );
+ }
+ }
+ }
+ },
+
+ collect: (monitor: any) => {
+ return {
+ isOver: !!monitor.isOver({ shallow: true })
+ };
+ }
+ });
+
+ const changeFocusFunction = (componentId: number, childId: number | null) => {
+ dispatch(changeFocus({ componentId, childId }));
+ };
+
+ // onClickHandler is responsible for changing the focused component and child component
+ function onClickHandler(event) {
+ event.stopPropagation();
+ changeFocusFunction(state.canvasFocus.componentId, childId);
+ }
+
+ // combine all styles so that higher priority style specifications overrule lower priority style specifications
+ // priority order is 1) style directly set for this child (style), 2) style of the referenced HTML element, and 3) default styling
+ const defaultNestableStyle = { ...globalDefaultStyle };
+ const separatorStyle = {
+ padding: isOver ? '40px 10px' : '2px 10px',
+ margin: '1px 10px',
+ transition: 'padding 1s ease-out'
+ };
+
+ defaultNestableStyle['backgroundColor'] = isOver
+ ? 'rgb(53, 78, 156)'
+ : 'rgba(0, 0, 255, 0.0)';
+
+ const combinedStyle = combineStyles(
+ combineStyles(combineStyles(defaultNestableStyle, HTMLType.style), style),
+ separatorStyle
+ );
+
+ drag(drop(ref));
+ return (
+
+ {renderChildren(children)}
+
+ );
+}
+
+export default SeparatorChild;
diff --git a/app/src/components/right/ComponentPanelItem.tsx b/app/src/components/right/ComponentPanelItem.tsx
index 861fd1c6..985a6713 100644
--- a/app/src/components/right/ComponentPanelItem.tsx
+++ b/app/src/components/right/ComponentPanelItem.tsx
@@ -1,102 +1,117 @@
-import React from 'react';
-import Grid from '@mui/material/Grid';
-import makeStyles from '@mui/styles/makeStyles';
-import { useDrag } from 'react-dnd';
-import { ItemTypes } from '../../constants/ItemTypes';
-import { useDispatch, useSelector } from 'react-redux';
-import { changeFocus } from '../../redux/reducers/slice/appStateSlice';
-import { RootState } from '../../redux/store';
-import { emitEvent } from '../../helperFunctions/socket';
-/*
-DESCRIPTION: This component is each box beneath the 'HTML Elements' and
- 'reusable components' (in classic React mode) headings. Drag-and-drop
- functionality works only for reusable components.
-
- -root is a boolean reflecting whether a component is a root component (that is, a container)
- -isFocus is boolean reflecting whether a component is the one currently displayed on the canvas
-*/
-// ComponentPanelItem is a tile that represents a single component
-const ComponentPanelItem: React.FC<{
- name: string;
- id: number;
- root: boolean;
- isFocus: boolean;
- isThemeLight: boolean;
-}> = ({ name, id, root, isFocus, isThemeLight }) => {
- const classes = useStyles();
- const state = useSelector((store: RootState) => store.appState);
- const roomCode = useSelector((store: RootState) => store.roomSlice.roomCode);
- const dispatch = useDispatch();
-
- // useDrag hook allows components in left panel to be drag source
- const [{ isDragging }, drag] = useDrag({
- item: {
- type: ItemTypes.INSTANCE,
- newInstance: true,
- instanceType: 'Component',
- instanceTypeId: id
- },
- canDrag: !root && !isFocus, // dragging not permitted if component is root component or current component
- collect: (monitor: any) => ({
- isDragging: !!monitor.isDragging() // !! converts an object to a boolean (i.e., if falsy, becomes false => !!0 === false)
- })
- });
-
- // when a component is clicked in the left panel, change canvas focus to that component
- const handleClick = () => {
- //LEGACY PD
- dispatch(changeFocus({ componentId: id, childId: null }));
-
- if (roomCode) {
- emitEvent('changeFocusAction', roomCode, {
- componentId: id,
- childId: null
- });
- }
- };
-
- return (
-
- {isFocus &&
}
-
- {/* render element's name on the left panel*/}
-
{name}
-
-
- );
-};
-
-const useStyles = makeStyles({
- activeFocus: {
- backgroundColor: 'rgba (0, 0, 0, 0.54)' //this doesnt do anything....
- },
- focusMark: {
- backgroundColor: '#0671e3',
- justifySelf: 'left',
- width: '14px',
- height: '14px',
- borderRadius: '25px',
- position: 'absolute' //so it doesn't cause the containing box to jiggle when selected due to change in size
- },
- lightTheme: {
- color: 'rgba (0, 0, 0, 0.54)'
- },
- darkTheme: {
- color: '#ffffff'
- }
-});
-export default ComponentPanelItem;
+import React from 'react';
+import Grid from '@mui/material/Grid';
+import makeStyles from '@mui/styles/makeStyles';
+import { useDrag } from 'react-dnd';
+import { ItemTypes } from '../../constants/ItemTypes';
+import { useDispatch, useSelector } from 'react-redux';
+import { changeFocus } from '../../redux/reducers/slice/appStateSlice';
+import { RootState } from '../../redux/store';
+import { emitEvent } from '../../helperFunctions/socket';
+import * as Icons from '@mui/icons-material';
+/*
+DESCRIPTION: This component is each box beneath the 'HTML Elements' and
+ 'reusable components' (in classic React mode) headings. Drag-and-drop
+ functionality works only for reusable components.
+
+ -root is a boolean reflecting whether a component is a root component (that is, a container)
+ -isFocus is boolean reflecting whether a component is the one currently displayed on the canvas
+*/
+// ComponentPanelItem is a tile that represents a single component
+const ComponentPanelItem: React.FC<{
+ name: string;
+ id: number;
+ root: boolean;
+ isFocus: boolean;
+ isThemeLight: boolean;
+}> = ({ name, id, root, isFocus, isThemeLight }) => {
+ const classes = useStyles({});
+ const state = useSelector((store: RootState) => store.appState);
+ const roomCode = useSelector((store: RootState) => store.roomSlice.roomCode);
+ const dispatch = useDispatch();
+
+ const IconComponent = Icons[name]; // Use the correct icon component based on the name
+
+ // useDrag hook allows components in left panel to be drag source
+ const [{ isDragging }, drag] = useDrag({
+ item: {
+ type: ItemTypes.INSTANCE,
+ newInstance: true,
+ instanceType: 'Component',
+ instanceTypeId: id
+ },
+ canDrag: !root && !isFocus, // dragging not permitted if component is root component or current component
+ collect: (monitor: any) => ({
+ isDragging: !!monitor.isDragging() // !! converts an object to a boolean (i.e., if falsy, becomes false => !!0 === false)
+ })
+ });
+
+ // when a component is clicked in the left panel, change canvas focus to that component
+ const handleClick = () => {
+ //LEGACY PD
+ dispatch(changeFocus({ componentId: id, childId: null }));
+
+ if (roomCode) {
+ emitEvent('changeFocusAction', roomCode, {
+ componentId: id,
+ childId: null
+ });
+ }
+ };
+
+ return (
+
+ {isFocus &&
}
+
+
+ {IconComponent && }
+
+ {name}
+
+
+
+
+ );
+};
+
+const useStyles = makeStyles({
+ nameContainer: {
+ display: 'flex',
+ alignItems: 'center'
+ },
+ focusMark: {
+ border: '2px solid #0671e3',
+ borderRadius: '5%',
+ position: 'absolute',
+ top: '0',
+ left: '0',
+ right: '0',
+ bottom: '0'
+ },
+ lightTheme: {
+ color: 'rgba (0, 0, 0, 0.54)'
+ },
+ darkTheme: {
+ color: '#ffffff'
+ }
+});
+
+export default ComponentPanelItem;
diff --git a/app/src/containers/CustomizationPanel.tsx b/app/src/containers/CustomizationPanel.tsx
index db2883e1..49748638 100644
--- a/app/src/containers/CustomizationPanel.tsx
+++ b/app/src/containers/CustomizationPanel.tsx
@@ -1,1092 +1,1128 @@
-import {
- Button,
- Dialog,
- DialogActions,
- DialogContent,
- DialogContentText,
- DialogTitle,
- FormControl,
- List,
- ListItem,
- ListItemText,
- TextField
-} from '@mui/material';
-import { Redo, Undo } from '@mui/icons-material';
-import React, { useCallback, useEffect, useMemo, useState } from 'react';
-import {
- changeTailwind,
- deleteChild,
- deleteEventAction,
- deletePage,
- deleteReusableComponent,
- redo,
- undo,
- updateAttributes,
- updateCss,
- updateEvents,
- updateStateUsed,
- updateUseContext
-} from '../redux/reducers/slice/appStateSlice';
-import { useDispatch, useSelector } from 'react-redux';
-
-import ClearIcon from '@mui/icons-material/Clear';
-import { DataGrid } from '@mui/x-data-grid';
-import ErrorMessages from '../constants/ErrorMessages';
-import FormSelector from '../components/form/Selector';
-import ProjectManager from '../components/right/ProjectManager';
-import UseStateModal from '../components/bottom/UseStateModal';
-import createModal from '../components/right/createModal';
-import makeStyles from '@mui/styles/makeStyles';
-import { ColumnTab } from '../interfaces/Interfaces';
-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 => {
- const classes = useStyles(isThemeLight);
- const dispatch = useDispatch();
- const state = useSelector((store: RootState) => store.appState);
- const contextParam = useSelector((store: RootState) => store.contextSlice);
- const roomCode = useSelector((store: RootState) => store.roomSlice.roomCode);
- const style = useSelector((store: RootState) => store.styleSlice);
-
- const [displayMode, setDisplayMode] = useState('');
- const [flexDir, setFlexDir] = useState('');
- const [flexJustify, setFlexJustify] = useState('');
- const [flexAlign, setFlexAlign] = useState('');
- const [BGColor, setBGColor] = useState('');
- const [compText, setCompText] = useState('');
- const [compLink, setCompLink] = useState('');
- const [cssClasses, setCssClasses] = useState('');
- const [compWidth, setCompWidth] = useState('');
- const [compHeight, setCompHeight] = useState('');
- const [deleteLinkedPageError, setDeleteLinkedPageError] = useState(false);
- const [deleteIndexError, setDeleteIndexError] = useState(false);
- const [deleteComponentError, setDeleteComponentError] = useState(false);
- const [modal, setModal] = useState(null);
- const [useContextObj, setUseContextObj] = useState({});
- const [stateUsedObj, setStateUsedObj] = useState({});
- const [eventAll, setEventAll] = useState(['', '']);
- const [eventRow, setEventRow] = useState([]);
-
- const currFocus = getFocus().child;
-
- useEffect(() => {
- currFocus?.attributes?.compLink &&
- setCompLink(currFocus.attributes.compLink);
- setEventAll(['', '']);
- if (currFocus) {
- const addedEvent: { id: string; funcName: any }[] = [];
- for (const [event, funcName] of Object.entries(currFocus?.events)) {
- addedEvent.push({ id: event, funcName });
- }
- setEventRow(addedEvent);
- }
- }, [state]);
-
- //this function allows properties to persist and appear in nested divs
- function deepIterate(arr) {
- const output = [];
- for (let i = 0; i < arr.length; i++) {
- if (arr[i].typeId === 1000) continue;
- output.push(arr[i]);
- if (arr[i].children.length) {
- output.push(...deepIterate(arr[i].children));
- }
- }
- return output;
- }
-
- const resetFields = () => {
- const childrenArray = deepIterate(configTarget.children);
- for (const element of childrenArray) {
- if (configTarget.child && element.childId === configTarget.child.id) {
- const attributes = element.attributes;
- const style = element.style;
- setCompText(attributes.compText ? attributes.compText : '');
- setCompLink(attributes.compLink ? attributes.compLink : '');
- setCssClasses(attributes.cssClasses ? attributes.cssClasses : '');
- }
- }
- const style = configTarget.child
- ? configTarget.child.style
- : configTarget.style;
- setDisplayMode(style.display ? style.display : '');
- setFlexDir(style.flexDirection ? style.flexDirection : '');
- setFlexJustify(style.justifyContent ? style.justifyContent : '');
- setFlexAlign(style.alignItems ? style.alignItems : '');
- setCompWidth(style.width ? style.width : '');
- setCompHeight(style.height ? style.height : '');
- setBGColor(style.backgroundColor ? style.backgroundColor : '');
- setEventAll(['', '']);
- };
- let configTarget;
- // after component renders, reset the input fields with the current styles of the selected child
- useEffect(() => {
- resetFields();
- }, [state.canvasFocus.componentId, state.canvasFocus.childId]);
- // handles all input field changes, with specific updates called based on input's name
- const handleChange = (e: React.ChangeEvent<{ value: any }>) => {
- const inputVal = e.target.value;
- switch (e.target.name) {
- case 'display':
- setDisplayMode(inputVal);
- break;
- case 'flexdir':
- setFlexDir(inputVal);
- break;
- case 'flexjust':
- setFlexJustify(inputVal);
- break;
- case 'flexalign':
- setFlexAlign(inputVal);
- break;
- case 'width':
- setCompWidth(inputVal);
- break;
- case 'height':
- setCompHeight(inputVal);
- break;
- case 'bgcolor':
- setBGColor(inputVal);
- break;
- case 'compText':
- setCompText(inputVal);
- break;
- case 'compLink':
- setCompLink(inputVal);
- break;
- case 'cssClasses':
- setCssClasses(inputVal);
- break;
- case 'event':
- setEventAll(
- inputVal ? [inputVal, `handle${inputVal.slice(2)}`] : ['', '']
- );
- break;
- case 'funcName':
- setEventAll([eventAll[0], inputVal]);
- break;
- default:
- break;
- }
- };
- // returns the current component referenced in canvasFocus
- // along with its child instance, if it exists
- function getFocus() {
- // find and store component's name based on canvasFocus.componentId
- // note: deep clone here to make sure we don't end up altering state
- const focusTarget = JSON.parse(
- JSON.stringify(
- state.components.find(
- (comp) => comp.id === state.canvasFocus.componentId
- )
- )
- );
- delete focusTarget.child;
- // checks if canvasFocus references a childId
- const childInstanceId = state.canvasFocus.childId;
- let focusChild;
- // if so, breadth-first search through focusTarget's descendants to find matching child
- if (childInstanceId) {
- focusTarget.child = {};
- focusTarget.child.id = childInstanceId;
- focusChild = {}; // child instance being referenced in canvasFocus
- const searchArray = [...focusTarget.children];
- while (searchArray.length > 0) {
- const currentChild = searchArray.shift();
- // if a match is found, set focusChild to the matched child and break out of the loop
- if (currentChild.childId === childInstanceId) {
- focusChild = currentChild;
- focusTarget.child.style = focusChild.style;
- focusTarget.child.events = focusChild.events;
- focusTarget.child.attributes = focusChild.attributes;
- break;
- }
- if (currentChild.name !== 'input' && currentChild.name !== 'img')
- currentChild.children.forEach((child) => searchArray.push(child));
- }
-
- // if type is Component, use child's typeId to search through state components and find matching component's name
- if (focusChild.type === 'Component') {
- focusTarget.child.type = 'component';
- focusTarget.child.name = state.components.find(
- (comp) => comp.id === focusChild.typeId
- ).name;
- // if type is HTML Element, search through HTML types to find matching element's name
- } else if (focusChild.type === 'HTML Element') {
- focusTarget.child.type = 'HTML element';
- focusTarget.child.name = state.HTMLTypes.find(
- (elem) => elem.id === focusChild.typeId
- ).name;
- }
- }
- return focusTarget;
- }
- // since determining the details of the focused component/child is an expensive operation, only perform this operation if the child/component have changed
- configTarget = useMemo(
- () => getFocus(),
- [state.canvasFocus.childId, state.canvasFocus.componentId]
- );
- const isPage = (configTarget): boolean => {
- const { components, rootComponents } = state;
- return components
- .filter((component) => rootComponents.includes(component.id))
- .some((el) => el.id === configTarget.id);
- };
- const isIndex = (): boolean => configTarget.id === 1;
- const isLinkedTo = (): boolean => {
- const { id } = configTarget;
- const pageName = state.components[id - 1].name;
- let isLinked = false;
- const searchNestedChildren = (comps) => {
- if (comps.length === 0) return;
- comps.forEach((comp, i) => {
- if (comp.type === 'Route Link' && comp.name === pageName) {
- isLinked = true;
- }
- if (comp.children.length > 0) searchNestedChildren(comp.children);
- });
- };
- searchNestedChildren(state.components);
- return isLinked;
- };
-
- const updateAttributeWithState = (
- attributeName: string,
- componentProviderId,
- statePropsId,
- statePropsRow,
- stateKey = ''
- ) => {
- const newInput = statePropsRow.value;
- // get the stateProps of the componentProvider
- const currentComponent =
- state.components[state.canvasFocus.componentId - 1];
- let newContextObj = { ...currentComponent.useContext };
- if (!newContextObj) {
- newContextObj = {};
- }
- if (!newContextObj[componentProviderId]) {
- newContextObj[componentProviderId] = { statesFromProvider: new Set() };
- }
- newContextObj[componentProviderId].statesFromProvider.add(statePropsId);
- if (attributeName === 'compText') {
- newContextObj[componentProviderId].compText = statePropsId;
- setStateUsedObj({
- ...stateUsedObj,
- compText: stateKey,
- compTextProviderId: componentProviderId,
- compTextPropsId: statePropsId
- });
- setCompText(newInput);
- setUseContextObj(newContextObj);
- }
-
- if (attributeName === 'compLink') {
- newContextObj[componentProviderId].compLink = statePropsId;
- setStateUsedObj({
- ...stateUsedObj,
- compLink: stateKey,
- compLinkProviderId: componentProviderId,
- compLinkPropsId: statePropsId
- });
- setCompLink(newInput);
- setUseContextObj(newContextObj);
- }
- };
-
- const eventColumnTabs: ColumnTab[] = [
- {
- field: 'id',
- headerName: 'Event',
- width: '40%',
- editable: false,
- flex: 1,
- disableColumnMenu: true
- },
- {
- field: 'funcName',
- headerName: 'Function Name',
- width: '50%',
- editable: false,
- flex: 1,
- disableColumnMenu: true
- },
- {
- field: 'delete',
- headerName: 'Delete',
- width: '10%',
- editable: false,
- flex: 1,
- sortable: false,
- disableColumnMenu: true,
- renderCell: function renderCell(params: any) {
- return (
- {
- deleteEvent(params.id);
- }}
- >
-
-
- );
- }
- }
- ];
-
- const deleteEvent = (selectedEvent) => {
- dispatch(
- deleteEventAction({ event: selectedEvent, contextParam: contextParam })
- );
- };
-
- const handleSave = (): Object => {
- dispatch(changeTailwind(false));
- dispatch(
- updateStateUsed({
- stateUsedObj: stateUsedObj,
- contextParam: contextParam
- })
- );
-
- dispatch(
- updateUseContext({
- useContextObj: useContextObj,
- contextParam: contextParam
- })
- );
-
- const styleObj: any = {};
- if (displayMode !== '') styleObj.display = displayMode;
- if (flexDir !== '') styleObj.flexDirection = flexDir;
- if (flexJustify !== '') styleObj.justifyContent = flexJustify;
- if (flexAlign !== '') styleObj.alignItems = flexAlign;
- if (compWidth !== '') styleObj.width = compWidth;
- if (compHeight !== '') styleObj.height = compHeight;
- if (BGColor !== '') styleObj.backgroundColor = BGColor;
- dispatch(updateCss({ style: styleObj, contextParam: contextParam }));
-
- const attributesObj: any = {};
- if (compText !== '') attributesObj.compText = compText;
- if (compLink !== '') attributesObj.compLink = compLink;
- if (cssClasses !== '') attributesObj.cssClasses = cssClasses;
- dispatch(
- updateAttributes({
- attributes: attributesObj,
- contextParam: contextParam
- })
- );
-
- const eventsObj: any = {};
- if (eventAll[0] !== '') eventsObj[eventAll[0]] = eventAll[1];
- dispatch(updateEvents({ events: eventsObj, contextParam: contextParam }));
-
- if (roomCode) {
- emitEvent('updateChildAction', roomCode, {
- stateUsedObj: stateUsedObj,
- contextParam: contextParam,
- useContextObj: useContextObj,
- attributes: attributesObj,
- style: styleObj,
- events: eventsObj
- });
- // console.log(
- // 'emit updateChildAction event is triggered in CustomizationPanel.tsx'
- // );
- }
-
- return styleObj;
- };
- const handleTailwind = (): void => {
- dispatch(changeTailwind(true));
- handleSave();
- };
-
- // const clickToUpdate = () => {
- // handleSave();
- // saveEmitter();
- // }
-
- // UNDO/REDO functionality--onClick these functions will be invoked.
- const handleUndo = () => {
- dispatch(undo({ contextParam }));
- };
- const handleRedo = () => {
- dispatch(redo({ contextParam }));
- };
- // placeholder for handling deleting instance
- const handleDelete = () => {
- dispatch(deleteChild({ id: {}, contextParam: contextParam }));
- if (roomCode) {
- emitEvent('deleteChildAction', roomCode, {
- id: {},
- contextParam: contextParam
- });
- // console.log(
- // 'emit deleteChildAction event is triggered in CustomizationPanel.tsx'
- // );
- }
- };
-
- const handlePageDelete = (id) => () => {
- // TODO: return modal
- if (isLinkedTo()) return setDeleteLinkedPageError(true);
- isIndex() ? handleDialogError('index') : dispatch(deletePage({ id }));
- };
- const handleDialogError = (err) => {
- if (err === 'index') setDeleteIndexError(true);
- else setDeleteComponentError(true);
- };
- const handleCloseDialogError = () => {
- setDeleteIndexError(false);
- setDeleteComponentError(false);
- setDeleteLinkedPageError(false);
- };
- // closes out the open modal
- const closeModal = (): void => setModal('');
- // creates modal that asks if user wants to clear all components
- // if user clears their components, then their components are removed from state and the modal is closed
- const clearComps = (): void => {
- // Reset state for project to initial state
- const handleDeleteReusableComponent = (): void => {
- closeModal();
- dispatch(deleteReusableComponent({ contextParam: contextParam }));
- };
- // set modal options
- const children = (
-
-
-
-
-
-
-
-
- );
-
- // create modal
- setModal(
- createModal({
- closeModal,
- children,
- message:
- 'Deleting this component will delete all instances of this component within the application. Do you still wish to proceed?',
- primBtnLabel: null,
- primBtnAction: null,
- secBtnAction: null,
- secBtnLabel: null,
- open: true
- })
- );
- };
- const keyBindedFunc = useCallback((e) => {
- // the || is for either Mac or Windows OS
- // Undo
- (e.key === 'z' && e.metaKey && !e.shiftKey) ||
- (e.key === 'z' && e.ctrlKey && !e.shiftKey)
- ? handleUndo()
- : // Redo
- (e.shiftKey && e.metaKey && e.key === 'z') ||
- (e.shiftKey && e.ctrlKey && e.key === 'z')
- ? handleRedo()
- : // Delete HTML tag off canvas
- e.key === 'Backspace' &&
- e.target.tagName !== 'TEXTAREA' &&
- e.target.tagName !== 'INPUT'
- ? handleDelete()
- : // Save
- (e.key === 's' && e.ctrlKey && e.shiftKey) ||
- (e.key === 's' && e.metaKey && e.shiftKey)
- ? handleSave()
- : '';
- }, []);
-
- useEffect(() => {
- document.addEventListener('keydown', keyBindedFunc);
- return () => {
- document.removeEventListener('keydown', keyBindedFunc);
- };
- }, []);
-
- if (state.canvasFocus.childId === null) {
- return (
-
-
-
-
-
-
- Parent Component:
-
-
-
- {configTarget.name}
-
-
- Drag or click an html element to the canvas to see what
- happens!
-
-
-
-
-
-
-
- );
- }
- return (
-
-
- {/* -----------------------------MOVED PROJECT MANAGER-------------------------------------- */}
-
-
-
-
- Instance of
- {configTarget.child.type === 'component'
- ? ' component:'
- : ' element:'}{' '}
- {configTarget.child.name}
-
-
-
-
-
- {displayMode === 'flex' && (
-
-
-
-
-
- )}
-
-
-
-
-
Background Color:
-
-
-
-
-
-
-
-
-
-
-
-
Text:
-
-
-
-
-
-
-
-
-
-
-
-
-
Link:
-
-
-
-
-
-
-
-
-
-
-
-
-
CSS Classes:
-
-
-
-
-
-
-
-
-
-
- {eventAll[0] && (
-
-
-
Function Name:
-
-
-
-
-
- )}
- {currFocus && Object.keys(currFocus.events).length !== 0 && (
-
-
-
- )}
-
-
-
-
-
- Save
-
-
- {/*
-
- CSS
-
-
-
-
-
- Tailwind
-
-
*/}
- {configTarget.child ? (
-
-
- Delete Instance
-
-
- ) : isPage(configTarget) ? (
-
-
- Delete Page
-
-
- ) : (
-
-
- Delete Reuseable Component
-
-
- )}
-
-
-
-
-
-
-
-
-
-
-
-
-
- {deleteIndexError ? ErrorMessages.deleteIndexTitle : ''}
- {deleteComponentError ? ErrorMessages.deleteComponentTitle : ''}
- {deleteLinkedPageError ? ErrorMessages.deleteLinkedPageTitle : ''}
-
-
-
- {deleteIndexError ? ErrorMessages.deleteIndexMessage : ''}
- {deleteComponentError ? ErrorMessages.deleteComponentMessage : ''}
- {deleteLinkedPageError ? ErrorMessages.deleteLinkedPageMessage : ''}
-
-
-
-
- OK
-
-
-
- {modal}
-
- );
-};
-const useStyles = makeStyles({
- select: {
- fontSize: '1em',
- borderRadius: '10px',
- '> .MuiSelect-icon': {
- color: '#C6C6C6'
- }
- },
- selectInput: {
- paddingTop: '15px',
- paddingLeft: '15px'
- },
- formControl: {
- minWidth: '125px'
- },
- configRow: {
- display: 'flex',
- paddingLeft: '25px',
- paddingRight: '25px',
- marginTop: '20px'
- },
- configType: {
- minWidth: '185px',
- fontSize: '85%'
- },
- configValue: {
- marginLeft: '20px'
- },
- buttonRow: (isThemeLight) => ({
- display: 'flex',
- justifyContent: 'center',
- alignItems: 'center',
- textAlign: 'center',
- '& > .MuiButton-textSecondary': {
- color: isThemeLight ? '#808080' : '#ECECEA', // color for delete page
- border: isThemeLight ? '1px solid #808080' : '1px solid #ECECEA'
- }
- }),
- button: {
- fontSize: '1rem',
- padding: '9px 35px',
- margin: '10px 15px 0 0',
- borderRadius: '8px'
- },
- saveButtonLight: {
- border: '1px solid #0671e3',
- backgroundColor: 'rgba(0, 0, 0, 0.2)'
- },
- saveButtonDark: {
- border: '1px solid #3c59ba'
- },
- compName: {
- fontSize: '1rem'
- },
- rootCompName: {
- fontSize: '1.75rem'
- },
- // 'Parent Component' font size
- configHeader: {
- '& > h4': {
- fontSize: '1rem',
- letterSpacing: '0.5px',
- marginBottom: '10px',
- marginTop: '10px'
- }
- },
- rootConfigHeader: {
- height: '70px',
- '& > h4': {
- fontSize: '1.75rem',
- letterSpacing: '0.5px',
- marginBottom: '0',
- marginTop: '30px'
- }
- },
- lightThemeFontColor: {
- color: 'white'
- },
- darkThemeFontColor: {
- color: '#fff'
- }
-});
-
-export default CustomizationPanel;
+import {
+ Button,
+ Dialog,
+ DialogActions,
+ DialogContent,
+ DialogContentText,
+ DialogTitle,
+ FormControl,
+ List,
+ ListItem,
+ ListItemText,
+ TextField
+} from '@mui/material';
+import { Redo, Undo } from '@mui/icons-material';
+import React, { useCallback, useEffect, useMemo, useState } from 'react';
+import {
+ changeTailwind,
+ deleteChild,
+ deleteEventAction,
+ deletePage,
+ deleteReusableComponent,
+ redo,
+ undo,
+ updateAttributes,
+ updateCss,
+ updateEvents,
+ updateStateUsed,
+ updateUseContext
+} from '../redux/reducers/slice/appStateSlice';
+import { useDispatch, useSelector } from 'react-redux';
+
+import ClearIcon from '@mui/icons-material/Clear';
+import { DataGrid } from '@mui/x-data-grid';
+import ErrorMessages from '../constants/ErrorMessages';
+import FormSelector from '../components/form/Selector';
+import ProjectManager from '../components/right/ProjectManager';
+import UseStateModal from '../components/bottom/UseStateModal';
+import createModal from '../components/right/createModal';
+import makeStyles from '@mui/styles/makeStyles';
+import { ColumnTab } from '../interfaces/Interfaces';
+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 => {
+ const classes = useStyles(isThemeLight);
+ const dispatch = useDispatch();
+ const state = useSelector((store: RootState) => store.appState);
+ const contextParam = useSelector((store: RootState) => store.contextSlice);
+ const roomCode = useSelector((store: RootState) => store.roomSlice.roomCode);
+ const style = useSelector((store: RootState) => store.styleSlice);
+
+ const [displayMode, setDisplayMode] = useState('');
+ const [flexDir, setFlexDir] = useState('');
+ const [flexJustify, setFlexJustify] = useState('');
+ const [flexAlign, setFlexAlign] = useState('');
+ const [flexOptionsVisible, setFlexOptionsVisible] = useState(false);
+ const [BGColor, setBGColor] = useState('');
+ const [compText, setCompText] = useState('');
+ const [compLink, setCompLink] = useState('');
+ const [cssClasses, setCssClasses] = useState('');
+ const [compWidth, setCompWidth] = useState('');
+ const [compHeight, setCompHeight] = useState('');
+ const [deleteLinkedPageError, setDeleteLinkedPageError] = useState(false);
+ const [deleteIndexError, setDeleteIndexError] = useState(false);
+ const [deleteComponentError, setDeleteComponentError] = useState(false);
+ const [modal, setModal] = useState(null);
+ const [useContextObj, setUseContextObj] = useState({});
+ const [stateUsedObj, setStateUsedObj] = useState({});
+ const [eventAll, setEventAll] = useState(['', '']);
+ const [eventOptionsVisible, setEventOptionsVisible] = useState(false);
+ const [eventRow, setEventRow] = useState([]);
+ const [eventRowsVisible, setEventRowsVisible] = useState(false);
+
+ const currFocus = getFocus().child;
+
+ useEffect(() => {
+ currFocus?.attributes?.compLink &&
+ setCompLink(currFocus.attributes.compLink);
+ setEventAll(['', '']);
+ if (currFocus) {
+ const addedEvent: { id: string; funcName: any }[] = [];
+ for (const [event, funcName] of Object.entries(currFocus?.events)) {
+ addedEvent.push({ id: event, funcName });
+ }
+ setEventRow(addedEvent);
+ }
+ }, [state]);
+
+ useEffect(() => {
+ if (displayMode === 'flex') {
+ return setFlexOptionsVisible(true);
+ }
+ return setFlexOptionsVisible(false);
+ }, [displayMode]);
+
+ useEffect(() => {
+ if (eventAll[0] !== '') {
+ return setEventOptionsVisible(true);
+ }
+ return setEventOptionsVisible(false);
+ }, [eventAll]);
+
+ useEffect(() => {
+ if (eventRow.length) {
+ return setEventRowsVisible(true);
+ }
+ return setEventRowsVisible(false);
+ }, [eventRow]);
+
+ const marginTopAmount = () => {
+ let totalMargin = 0;
+ if (eventOptionsVisible) totalMargin += 90;
+ if (flexOptionsVisible) totalMargin = Math.max(totalMargin, 210);
+ if (eventRowsVisible) totalMargin = Math.max(totalMargin, 335);
+ return `${totalMargin}px`;
+ };
+
+ //this function allows properties to persist and appear in nested divs
+ function deepIterate(arr) {
+ const output = [];
+ for (let i = 0; i < arr.length; i++) {
+ if (arr[i].typeId === 1000) continue;
+ output.push(arr[i]);
+ if (arr[i].children.length) {
+ output.push(...deepIterate(arr[i].children));
+ }
+ }
+ return output;
+ }
+
+ const resetFields = () => {
+ const childrenArray = deepIterate(configTarget.children);
+ for (const element of childrenArray) {
+ if (configTarget.child && element.childId === configTarget.child.id) {
+ const attributes = element.attributes;
+ const style = element.style;
+ setCompText(attributes.compText ? attributes.compText : '');
+ setCompLink(attributes.compLink ? attributes.compLink : '');
+ setCssClasses(attributes.cssClasses ? attributes.cssClasses : '');
+ }
+ }
+ const style = configTarget.child
+ ? configTarget.child.style
+ : configTarget.style;
+ setDisplayMode(style.display ? style.display : '');
+ setFlexDir(style.flexDirection ? style.flexDirection : '');
+ setFlexJustify(style.justifyContent ? style.justifyContent : '');
+ setFlexAlign(style.alignItems ? style.alignItems : '');
+ setCompWidth(style.width ? style.width : '');
+ setCompHeight(style.height ? style.height : '');
+ setBGColor(style.backgroundColor ? style.backgroundColor : '');
+ setEventAll(['', '']);
+ };
+ let configTarget;
+ // after component renders, reset the input fields with the current styles of the selected child
+ useEffect(() => {
+ resetFields();
+ }, [state.canvasFocus.componentId, state.canvasFocus.childId]);
+ // handles all input field changes, with specific updates called based on input's name
+ const handleChange = (e: React.ChangeEvent) => {
+ const inputVal = e.target.value;
+ switch (e.target.name) {
+ case 'display':
+ setDisplayMode(inputVal);
+ break;
+ case 'flexdir':
+ setFlexDir(inputVal);
+ break;
+ case 'flexjust':
+ setFlexJustify(inputVal);
+ break;
+ case 'flexalign':
+ setFlexAlign(inputVal);
+ break;
+ case 'width':
+ setCompWidth(inputVal);
+ break;
+ case 'height':
+ setCompHeight(inputVal);
+ break;
+ case 'bgcolor':
+ setBGColor(inputVal);
+ break;
+ case 'compText':
+ setCompText(inputVal);
+ break;
+ case 'compLink':
+ setCompLink(inputVal);
+ break;
+ case 'cssClasses':
+ setCssClasses(inputVal);
+ break;
+ case 'event':
+ setEventAll(
+ inputVal ? [inputVal, `handle${inputVal.slice(2)}`] : ['', '']
+ );
+ break;
+ case 'funcName':
+ setEventAll([eventAll[0], inputVal]);
+ break;
+ default:
+ break;
+ }
+ };
+ // returns the current component referenced in canvasFocus
+ // along with its child instance, if it exists
+ function getFocus() {
+ // find and store component's name based on canvasFocus.componentId
+ // note: deep clone here to make sure we don't end up altering state
+ const focusTarget = JSON.parse(
+ JSON.stringify(
+ state.components.find(
+ (comp) => comp.id === state.canvasFocus.componentId
+ )
+ )
+ );
+ delete focusTarget.child;
+ // checks if canvasFocus references a childId
+ const childInstanceId = state.canvasFocus.childId;
+ let focusChild;
+ // if so, breadth-first search through focusTarget's descendants to find matching child
+ if (childInstanceId) {
+ focusTarget.child = {};
+ focusTarget.child.id = childInstanceId;
+ focusChild = {}; // child instance being referenced in canvasFocus
+ const searchArray = [...focusTarget.children];
+ while (searchArray.length > 0) {
+ const currentChild = searchArray.shift();
+ // if a match is found, set focusChild to the matched child and break out of the loop
+ if (currentChild.childId === childInstanceId) {
+ focusChild = currentChild;
+ focusTarget.child.style = focusChild.style;
+ focusTarget.child.events = focusChild.events;
+ focusTarget.child.attributes = focusChild.attributes;
+ break;
+ }
+ if (currentChild.name !== 'input' && currentChild.name !== 'img')
+ currentChild.children.forEach((child) => searchArray.push(child));
+ }
+
+ // if type is Component, use child's typeId to search through state components and find matching component's name
+ if (focusChild.type === 'Component') {
+ focusTarget.child.type = 'component';
+ focusTarget.child.name = state.components.find(
+ (comp) => comp.id === focusChild.typeId
+ ).name;
+ // if type is HTML Element, search through HTML types to find matching element's name
+ } else if (focusChild.type === 'HTML Element') {
+ focusTarget.child.type = 'HTML element';
+ focusTarget.child.name = state.HTMLTypes.find(
+ (elem) => elem.id === focusChild.typeId
+ ).name;
+ }
+ }
+ return focusTarget;
+ }
+ // since determining the details of the focused component/child is an expensive operation, only perform this operation if the child/component have changed
+ configTarget = useMemo(
+ () => getFocus(),
+ [state.canvasFocus.childId, state.canvasFocus.componentId]
+ );
+ const isPage = (configTarget): boolean => {
+ const { components, rootComponents } = state;
+ return components
+ .filter((component) => rootComponents.includes(component.id))
+ .some((el) => el.id === configTarget.id);
+ };
+ const isIndex = (): boolean => configTarget.id === 1;
+ const isLinkedTo = (): boolean => {
+ const { id } = configTarget;
+ const pageName = state.components[id - 1].name;
+ let isLinked = false;
+ const searchNestedChildren = (comps) => {
+ if (comps.length === 0) return;
+ comps.forEach((comp, i) => {
+ if (comp.type === 'Route Link' && comp.name === pageName) {
+ isLinked = true;
+ }
+ if (comp.children.length > 0) searchNestedChildren(comp.children);
+ });
+ };
+ searchNestedChildren(state.components);
+ return isLinked;
+ };
+
+ const updateAttributeWithState = (
+ attributeName: string,
+ componentProviderId,
+ statePropsId,
+ statePropsRow,
+ stateKey = ''
+ ) => {
+ const newInput = statePropsRow.value;
+ // get the stateProps of the componentProvider
+ const currentComponent =
+ state.components[state.canvasFocus.componentId - 1];
+ let newContextObj = { ...currentComponent.useContext };
+ if (!newContextObj) {
+ newContextObj = {};
+ }
+ if (!newContextObj[componentProviderId]) {
+ newContextObj[componentProviderId] = { statesFromProvider: new Set() };
+ }
+ newContextObj[componentProviderId].statesFromProvider.add(statePropsId);
+ if (attributeName === 'compText') {
+ newContextObj[componentProviderId].compText = statePropsId;
+ setStateUsedObj({
+ ...stateUsedObj,
+ compText: stateKey,
+ compTextProviderId: componentProviderId,
+ compTextPropsId: statePropsId
+ });
+ setCompText(newInput);
+ setUseContextObj(newContextObj);
+ }
+
+ if (attributeName === 'compLink') {
+ newContextObj[componentProviderId].compLink = statePropsId;
+ setStateUsedObj({
+ ...stateUsedObj,
+ compLink: stateKey,
+ compLinkProviderId: componentProviderId,
+ compLinkPropsId: statePropsId
+ });
+ setCompLink(newInput);
+ setUseContextObj(newContextObj);
+ }
+ };
+
+ const eventColumnTabs: ColumnTab[] = [
+ {
+ field: 'id',
+ headerName: 'Event',
+ width: '40%',
+ editable: false,
+ flex: 1,
+ disableColumnMenu: true
+ },
+ {
+ field: 'funcName',
+ headerName: 'Function Name',
+ width: '50%',
+ editable: false,
+ flex: 1,
+ disableColumnMenu: true
+ },
+ {
+ field: 'delete',
+ headerName: 'Delete',
+ width: '10%',
+ editable: false,
+ flex: 1,
+ sortable: false,
+ disableColumnMenu: true,
+ renderCell: function renderCell(params: any) {
+ return (
+ {
+ deleteEvent(params.id);
+ }}
+ >
+
+
+ );
+ }
+ }
+ ];
+
+ const deleteEvent = (selectedEvent) => {
+ dispatch(
+ deleteEventAction({ event: selectedEvent, contextParam: contextParam })
+ );
+ };
+
+ const handleSave = (): Object => {
+ dispatch(changeTailwind(false));
+ dispatch(
+ updateStateUsed({
+ stateUsedObj: stateUsedObj,
+ contextParam: contextParam
+ })
+ );
+
+ dispatch(
+ updateUseContext({
+ useContextObj: useContextObj,
+ contextParam: contextParam
+ })
+ );
+
+ const styleObj: any = {};
+ if (displayMode !== '') styleObj.display = displayMode;
+ if (flexDir !== '') styleObj.flexDirection = flexDir;
+ if (flexJustify !== '') styleObj.justifyContent = flexJustify;
+ if (flexAlign !== '') styleObj.alignItems = flexAlign;
+ if (compWidth !== '') styleObj.width = compWidth;
+ if (compHeight !== '') styleObj.height = compHeight;
+ if (BGColor !== '') styleObj.backgroundColor = BGColor;
+ dispatch(updateCss({ style: styleObj, contextParam: contextParam }));
+
+ const attributesObj: any = {};
+ if (compText !== '') attributesObj.compText = compText;
+ if (compLink !== '') attributesObj.compLink = compLink;
+ if (cssClasses !== '') attributesObj.cssClasses = cssClasses;
+ dispatch(
+ updateAttributes({
+ attributes: attributesObj,
+ contextParam: contextParam
+ })
+ );
+
+ const eventsObj: any = {};
+ if (eventAll[0] !== '') eventsObj[eventAll[0]] = eventAll[1];
+ dispatch(updateEvents({ events: eventsObj, contextParam: contextParam }));
+
+ if (roomCode) {
+ emitEvent('updateChildAction', roomCode, {
+ stateUsedObj: stateUsedObj,
+ contextParam: contextParam,
+ useContextObj: useContextObj,
+ attributes: attributesObj,
+ style: styleObj,
+ events: eventsObj
+ });
+ // console.log(
+ // 'emit updateChildAction event is triggered in CustomizationPanel.tsx'
+ // );
+ }
+
+ return styleObj;
+ };
+ const handleTailwind = (): void => {
+ dispatch(changeTailwind(true));
+ handleSave();
+ };
+
+ // const clickToUpdate = () => {
+ // handleSave();
+ // saveEmitter();
+ // }
+
+ // UNDO/REDO functionality--onClick these functions will be invoked.
+ const handleUndo = () => {
+ dispatch(undo({ contextParam }));
+ };
+ const handleRedo = () => {
+ dispatch(redo({ contextParam }));
+ };
+ // placeholder for handling deleting instance
+ const handleDelete = () => {
+ dispatch(deleteChild({ id: {}, contextParam: contextParam }));
+ if (roomCode) {
+ emitEvent('deleteChildAction', roomCode, {
+ id: {},
+ contextParam: contextParam
+ });
+ // console.log(
+ // 'emit deleteChildAction event is triggered in CustomizationPanel.tsx'
+ // );
+ }
+ };
+
+ const handlePageDelete = (id) => () => {
+ // TODO: return modal
+ if (isLinkedTo()) return setDeleteLinkedPageError(true);
+ isIndex() ? handleDialogError('index') : dispatch(deletePage({ id }));
+ };
+ const handleDialogError = (err) => {
+ if (err === 'index') setDeleteIndexError(true);
+ else setDeleteComponentError(true);
+ };
+ const handleCloseDialogError = () => {
+ setDeleteIndexError(false);
+ setDeleteComponentError(false);
+ setDeleteLinkedPageError(false);
+ };
+ // closes out the open modal
+ const closeModal = (): void => setModal('');
+ // creates modal that asks if user wants to clear all components
+ // if user clears their components, then their components are removed from state and the modal is closed
+ const clearComps = (): void => {
+ // Reset state for project to initial state
+ const handleDeleteReusableComponent = (): void => {
+ closeModal();
+ dispatch(deleteReusableComponent({ contextParam: contextParam }));
+ };
+ // set modal options
+ const children = (
+
+
+
+
+
+
+
+
+ );
+
+ // create modal
+ setModal(
+ createModal({
+ closeModal,
+ children,
+ message:
+ 'Deleting this component will delete all instances of this component within the application. Do you still wish to proceed?',
+ primBtnLabel: null,
+ primBtnAction: null,
+ secBtnAction: null,
+ secBtnLabel: null,
+ open: true
+ })
+ );
+ };
+ const keyBindedFunc = useCallback((e) => {
+ // the || is for either Mac or Windows OS
+ // Undo
+ (e.key === 'z' && e.metaKey && !e.shiftKey) ||
+ (e.key === 'z' && e.ctrlKey && !e.shiftKey)
+ ? handleUndo()
+ : // Redo
+ (e.shiftKey && e.metaKey && e.key === 'z') ||
+ (e.shiftKey && e.ctrlKey && e.key === 'z')
+ ? handleRedo()
+ : // Delete HTML tag off canvas
+ e.key === 'Backspace' &&
+ e.target.tagName !== 'TEXTAREA' &&
+ e.target.tagName !== 'INPUT'
+ ? handleDelete()
+ : // Save
+ (e.key === 's' && e.ctrlKey && e.shiftKey) ||
+ (e.key === 's' && e.metaKey && e.shiftKey)
+ ? handleSave()
+ : '';
+ }, []);
+
+ useEffect(() => {
+ document.addEventListener('keydown', keyBindedFunc);
+ return () => {
+ document.removeEventListener('keydown', keyBindedFunc);
+ };
+ }, []);
+
+ if (state.canvasFocus.childId === null) {
+ return (
+
+
+
+
+
+
+ Parent Component:
+
+
+
+ {configTarget.name}
+
+
+ Drag or click an html element to the canvas to see what
+ happens!
+
+
+
+
+
+
+
+ );
+ }
+ return (
+
+
+ {/* -----------------------------MOVED PROJECT MANAGER-------------------------------------- */}
+
+
+
+
+ Instance of
+ {configTarget.child.type === 'component'
+ ? ' component:'
+ : ' element:'}{' '}
+ {configTarget.child.name}
+
+
+
+
+
+ {displayMode === 'flex' && (
+
+
+
+
+
+ )}
+
+
+
+
+
Background Color:
+
+
+
+
+
+
+
+
+
+
+
+
Text:
+
+
+
+
+
+
+
+
+
+
+
+
+
Link:
+
+
+
+
+
+
+
+
+
+
+
+
+
CSS Classes:
+
+
+
+
+
+
+
+
+
+
+ {eventAll[0] && (
+
+
+
Function Name:
+
+
+
+
+
+ )}
+ {currFocus && Object.keys(currFocus.events).length !== 0 && (
+
+
+
+ )}
+
+
+
+
+
+ Save
+
+
+ {/*
+
+ CSS
+
+
+
+
+
+ Tailwind
+
+
*/}
+ {configTarget.child ? (
+
+
+ Delete Instance
+
+
+ ) : isPage(configTarget) ? (
+
+
+ Delete Page
+
+
+ ) : (
+
+
+ Delete Reuseable Component
+
+
+ )}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {deleteIndexError ? ErrorMessages.deleteIndexTitle : ''}
+ {deleteComponentError ? ErrorMessages.deleteComponentTitle : ''}
+ {deleteLinkedPageError ? ErrorMessages.deleteLinkedPageTitle : ''}
+
+
+
+ {deleteIndexError ? ErrorMessages.deleteIndexMessage : ''}
+ {deleteComponentError ? ErrorMessages.deleteComponentMessage : ''}
+ {deleteLinkedPageError ? ErrorMessages.deleteLinkedPageMessage : ''}
+
+
+
+
+ OK
+
+
+
+ {modal}
+
+ );
+};
+const useStyles = makeStyles({
+ select: {
+ fontSize: '1em',
+ borderRadius: '10px',
+ '> .MuiSelect-icon': {
+ color: '#C6C6C6'
+ }
+ },
+ selectInput: {
+ paddingTop: '15px',
+ paddingLeft: '15px'
+ },
+ formControl: {
+ minWidth: '125px'
+ },
+ configRow: {
+ display: 'flex',
+ paddingLeft: '25px',
+ paddingRight: '25px',
+ marginTop: '20px'
+ },
+ configType: {
+ minWidth: '185px',
+ fontSize: '85%'
+ },
+ configValue: {
+ marginLeft: '20px'
+ },
+ buttonRow: (isThemeLight) => ({
+ display: 'flex',
+ justifyContent: 'center',
+ alignItems: 'center',
+ textAlign: 'center',
+ marginLeft: '15px',
+ '& > .MuiButton-textSecondary': {
+ color: isThemeLight ? '#808080' : '#ECECEA', // color for delete page
+ border: isThemeLight ? '1px solid #808080' : '1px solid #ECECEA'
+ }
+ }),
+ button: {
+ fontSize: '1rem',
+ padding: '9px 35px',
+ margin: '10px 15px 0 0',
+ borderRadius: '8px'
+ },
+ saveButtonLight: {
+ border: '1px solid #0671e3',
+ backgroundColor: 'rgba(0, 0, 0, 0.2)'
+ },
+ saveButtonDark: {
+ border: '1px solid #3c59ba'
+ },
+ compName: {
+ fontSize: '1rem'
+ },
+ rootCompName: {
+ fontSize: '1.75rem'
+ },
+ // 'Parent Component' font size
+ configHeader: {
+ '& > h4': {
+ fontSize: '1rem',
+ letterSpacing: '0.5px',
+ marginBottom: '10px',
+ marginTop: '10px'
+ }
+ },
+ rootConfigHeader: {
+ height: '70px',
+ '& > h4': {
+ fontSize: '1.75rem',
+ letterSpacing: '0.5px',
+ marginBottom: '0',
+ marginTop: '30px'
+ }
+ },
+ lightThemeFontColor: {
+ color: 'white'
+ },
+ darkThemeFontColor: {
+ color: '#fff'
+ }
+});
+
+export default CustomizationPanel;
diff --git a/app/src/helperFunctions/generateCode.ts b/app/src/helperFunctions/generateCode.ts
index 4b4a2150..35bbf93e 100644
--- a/app/src/helperFunctions/generateCode.ts
+++ b/app/src/helperFunctions/generateCode.ts
@@ -1,602 +1,639 @@
-import {
- Component,
- ChildElement,
- HTMLType,
- ChildStyle,
- StateProp
-} from '../interfaces/Interfaces';
-declare global {
- interface Window {
- api: any;
- }
-}
-// generate code based on component hierarchy and then return the rendered code
-const generateCode = (
- components: Component[],
- componentId: number,
- rootComponents: number[],
- projectType: string,
- HTMLTypes: HTMLType[],
- tailwind: boolean,
- contextParam: any
-) => {
- const code = generateUnformattedCode(
- components,
- componentId,
- rootComponents,
- projectType,
- HTMLTypes,
- tailwind,
- contextParam
- );
- return formatCode(code);
-};
-
-// generate code based on the component hierarchy
-const generateUnformattedCode = (
- comps: Component[],
- componentId: number,
- rootComponents: number[],
- projectType: string,
- HTMLTypes: HTMLType[],
- tailwind: boolean,
- contextParam: any
-) => {
- const components = [...comps];
- // 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
- let imports: any = [];
- 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);
- }
-
- // 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 === 'Route Link') {
- links = true;
- child.name = components.find(
- (comp: Component) => comp.id === child.typeId
- ).name;
- return child;
- }
- });
- 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;
- };
- // 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}} `;
- });
- }
-
- 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.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} `;
- }
- 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}()}`;
- }
- }
-
- return customizationDetails;
- };
- // 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;
- };
- // 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 '';
- };
- // function to dynamically generate a complete html (& also other library type) elements
- const elementGenerator = (childElement: ChildElement, level: number = 2) => {
- let innerText = '';
- let activeLink = '""';
- if (childElement.attributes && childElement.attributes.compText) {
- if (childElement.stateUsed && childElement.stateUsed.compText) {
- innerText = '{' + childElement.stateUsed.compText + '}';
- } else {
- innerText = childElement.attributes.compText;
- }
- }
- if (childElement.attributes && childElement.attributes.compLink) {
- if (childElement.stateUsed && childElement.stateUsed.compLink) {
- activeLink = '{' + childElement.stateUsed.compLink + '}';
- } else {
- activeLink = '"' + childElement.attributes.compLink + '"';
- }
- }
-
- 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}${
- childElement.tag
- }>${levelSpacer(2, 3 + level)}`;
- } else if (childElement.tag === 'input') {
- return `${levelSpacer(level, 5)}<${childElement.tag}${elementTagDetails(
- childElement
- )}>${childElement.tag}>${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,
- 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) {
- if (
- (childElement.tag === 'Route' || childElement.tag === 'Switch') &&
- projectType === 'Next.js'
- ) {
- return `${writeNestedElements(childElement.children, level)}`;
- }
- 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)}${childElement.tag}>${levelSpacer(
- 2,
- 3 + level
- )}`;
- } else if (childElement.tag !== 'separator') {
- return `${levelSpacer(level, 5)}<${childElement.tag}${elementTagDetails(
- childElement
- )}>${innerText}${childElement.tag}>${levelSpacer(2, 3 + level)}`;
- }
- };
- // 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 + ';';
- }
- 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'
- ? imports
- .map((comp: string) => {
- return isRoot
- ? `import ${comp} from '../components/${comp}'`
- : `import ${comp} from './${comp}'`;
- })
- .join('\n')
- : imports
- .map((comp: string) => {
- 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
- if (projectType === 'Classic React') {
- //string to store all imports string for context
- let contextImports = '';
- const { allContext } = contextParam;
-
- for (const context of allContext) {
- contextImports += `import ${context.name}Provider from '../contexts/${context.name}.js'\n`;
- }
-
- //build an object with keys representing all components, their values are arrays storing all contexts that those components are consuming
- const componentContext = allContext.reduce((acc, curr) => {
- for (const component of curr.components) {
- if (acc[component] === undefined) acc[component] = [];
- acc[component].push(curr.name);
- }
- 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)}${
- el.name
- }Provider>`;
- });
- }
- return result;
- };
-
- //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 += `import { ${context} } from '../contexts/${context}.js'\n`;
- });
-
- return importStr;
- };
-
- //call use context hooks for components that are consuming contexts
- //LEGACY PD:
- const createUseContextHook = () => {
- if (!(currComponent.name in componentContext)) return '';
-
- let importStr = '';
- componentContext[currComponent.name].forEach((context) => {
- importStr += ` const [${context}Val] = useContext(${context})\n`;
- });
-
- return importStr;
- };
-
- const createEventHandler = (children: ChildElement[]) => {
- let importStr = '';
- children.map((child) => {
- if (child.type === 'HTML Element') {
- if (child.events) {
- for (const [event, funcName] of Object.entries(child.events)) {
- importStr += `\tconst ${funcName} = () => {};\n`;
- }
- }
- if (child.children.length !== 0)
- importStr += createEventHandler(child.children);
- }
- });
-
- return importStr;
- };
-
- let generatedCode =
- "import React, { useState, useEffect, useContext} from 'react';\n\n";
- generatedCode += currComponent.name === 'App' ? contextImports : '';
- generatedCode += importReactRouter
- ? `import { BrowserRouter as Router, Route, Switch, Link } from 'react-router-dom';\n`
- : ``;
- generatedCode += createContextImport() ? `${createContextImport()}\n` : '';
- generatedCode += importsMapped ? `${importsMapped}\n` : '';
- // below is the return statement of the codepreview
- generatedCode += `const ${currComponent.name} = (props) => {\n`;
- generatedCode += writeStateProps(currComponent.useStateCodes)
- ? `\t${writeStateProps(currComponent.useStateCodes)}\n`
- : '';
- generatedCode += createEventHandler(enrichedChildren)
- ? `${createEventHandler(enrichedChildren)}\n`
- : '';
- generatedCode += `
- return(
- <>
- ${createRender()}
- >
- );`;
- generatedCode += `\n}`;
- return generatedCode;
- } else if (projectType === 'Next.js') {
- return `
- import React, { useState } from 'react';
- ${importsMapped}
- import Head from 'next/head'
- ${links ? `import Link from 'next/link'` : ``}
- ${images ? `import Image from 'next/image'` : ``}
-
- const ${
- currComponent.name[0].toUpperCase() + currComponent.name.slice(1)
- } = (props): JSX.Element => {
- return (
- <>
- ${
- isRoot
- ? `
-
- ${currComponent.name}
- `
- : ``
- }
- ${writeNestedElements(enrichedChildren)}
- >
- );
- }
- export default ${
- currComponent.name[0].toUpperCase() + currComponent.name.slice(1)
- };
- `;
- } else {
- // gatsby component code
- return `
- import React, { useState } from 'react';
- ${importsMapped}
- import { StaticQuery, graphql } from 'gatsby';
- ${links ? `import { Link } from 'gatsby'` : ``}
- const ${currComponent.name} = (props: any): JSX.Element => {
- return (
- <>
- ${
- isRoot
- ? `
- ${currComponent.name}
- `
- : ``
- }
-
- ${writeNestedElements(enrichedChildren)}
-
- >
- );
- }
- export default ${currComponent.name};
- `;
- }
-};
-// formats code with prettier linter
-const formatCode = (code: string) => {
- if (import.meta.env.NODE_ENV === 'test') {
- const { format } = require('prettier');
- return format(code, {
- singleQuote: true,
- trailingComma: 'es5',
- bracketSpacing: true,
- jsxBracketSameLine: true,
- parser: 'babel'
- });
- } else {
- return code;
- }
-};
-export default generateCode;
+import {
+ Component,
+ ChildElement,
+ HTMLType,
+ MUIType,
+ ChildStyle,
+ StateProp
+} from '../interfaces/Interfaces';
+declare global {
+ interface Window {
+ api: any;
+ }
+}
+// generate code based on component hierarchy and then return the rendered code
+const generateCode = (
+ components: Component[],
+ componentId: number,
+ rootComponents: number[],
+ projectType: string,
+ HTMLTypes: HTMLType[],
+ MUITypes: MUIType[],
+ tailwind: boolean,
+ contextParam: any
+) => {
+ const code = generateUnformattedCode(
+ components,
+ componentId,
+ rootComponents,
+ projectType,
+ HTMLTypes,
+ MUITypes,
+ tailwind,
+ contextParam
+ );
+ return formatCode(code);
+};
+
+// generate code based on the component hierarchy
+const generateUnformattedCode = (
+ comps: Component[],
+ componentId: number,
+ rootComponents: number[],
+ projectType: string,
+ HTMLTypes: HTMLType[],
+ MUITypes: MUIType[],
+ tailwind: boolean,
+ contextParam: any
+) => {
+ const components = [...comps];
+ // 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
+ let imports: any = [];
+ 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);
+ }
+
+ // 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);
+ }
+
+ // 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;
+ }
+ });
+ 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;
+ };
+ // 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}} `;
+ });
+ }
+
+ 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.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} `;
+ }
+ 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}()}`;
+ }
+ }
+
+ return customizationDetails;
+ };
+ // 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;
+ };
+ // 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 '';
+ };
+ // function to dynamically generate a complete html (& also other library type) elements
+ const elementGenerator = (childElement: ChildElement, level: number = 2) => {
+ let innerText = '';
+ let activeLink = '""';
+ if (childElement.attributes && childElement.attributes.compText) {
+ if (childElement.stateUsed && childElement.stateUsed.compText) {
+ innerText = '{' + childElement.stateUsed.compText + '}';
+ } else {
+ innerText = childElement.attributes.compText;
+ }
+ }
+ if (childElement.attributes && childElement.attributes.compLink) {
+ if (childElement.stateUsed && childElement.stateUsed.compLink) {
+ activeLink = '{' + childElement.stateUsed.compLink + '}';
+ } else {
+ activeLink = '"' + childElement.attributes.compLink + '"';
+ }
+ }
+
+ 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}${
+ childElement.tag
+ }>${levelSpacer(2, 3 + level)}`;
+ } else if (childElement.tag === 'input') {
+ return `${levelSpacer(level, 5)}<${childElement.tag}${elementTagDetails(
+ childElement
+ )}>${childElement.tag}>${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,
+ 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) {
+ if (
+ (childElement.tag === 'Route' || childElement.tag === 'Switch') &&
+ projectType === 'Next.js'
+ ) {
+ return `${writeNestedElements(childElement.children, level)}`;
+ }
+ 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)}${childElement.tag}>${levelSpacer(
+ 2,
+ 3 + level
+ )}`;
+ } else if (childElement.tag !== 'separator') {
+ return `${levelSpacer(level, 5)}<${childElement.tag}${elementTagDetails(
+ childElement
+ )}>${innerText}${childElement.tag}>${levelSpacer(2, 3 + level)}`;
+ }
+ };
+ // 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 + ';';
+ }
+ 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'
+ ? imports
+ .map((comp: string) => {
+ return isRoot
+ ? `import ${comp} from '../components/${comp}'`
+ : `import ${comp} from './${comp}'`;
+ })
+ .join('\n')
+ : imports
+ .map((comp: string) => {
+ 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
+ if (projectType === 'Classic React') {
+ //string to store all imports string for context
+ let contextImports = '';
+ const { allContext } = contextParam;
+
+ for (const context of allContext) {
+ contextImports += `import ${context.name}Provider from '../contexts/${context.name}.js'\n`;
+ }
+
+ //build an object with keys representing all components, their values are arrays storing all contexts that those components are consuming
+ const componentContext = allContext.reduce((acc, curr) => {
+ for (const component of curr.components) {
+ if (acc[component] === undefined) acc[component] = [];
+ acc[component].push(curr.name);
+ }
+ 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)}${
+ el.name
+ }Provider>`;
+ });
+ }
+ return result;
+ };
+
+ //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 += `import { ${context} } from '../contexts/${context}.js'\n`;
+ });
+
+ return importStr;
+ };
+
+ //call use context hooks for components that are consuming contexts
+ //LEGACY PD:
+ const createUseContextHook = () => {
+ if (!(currComponent.name in componentContext)) return '';
+
+ let importStr = '';
+ componentContext[currComponent.name].forEach((context) => {
+ importStr += ` const [${context}Val] = useContext(${context})\n`;
+ });
+
+ return importStr;
+ };
+
+ const createEventHandler = (children: ChildElement[]) => {
+ let importStr = '';
+ children.map((child) => {
+ if (child.type === 'HTML Element') {
+ if (child.events) {
+ for (const [event, funcName] of Object.entries(child.events)) {
+ importStr += `\tconst ${funcName} = () => {};\n`;
+ }
+ }
+ if (child.children.length !== 0)
+ importStr += createEventHandler(child.children);
+ }
+ if (child.type === 'MUI Component') {
+ if (child.events) {
+ for (const [event, funcName] of Object.entries(child.events)) {
+ importStr += `\tconst ${funcName} = () => {};\n`;
+ }
+ }
+ if (child.children.length !== 0)
+ importStr += createEventHandler(child.children);
+ }
+ });
+
+ return importStr;
+ };
+
+ let generatedCode =
+ "import React, { useState, useEffect, useContext} from 'react';\n\n";
+ generatedCode += currComponent.name === 'App' ? contextImports : '';
+ generatedCode += importReactRouter
+ ? `import { BrowserRouter as Router, Route, Switch, Link } from 'react-router-dom';\n`
+ : ``;
+ generatedCode += createContextImport() ? `${createContextImport()}\n` : '';
+ generatedCode += importsMapped ? `${importsMapped}\n` : '';
+ // below is the return statement of the codepreview
+ generatedCode += `const ${currComponent.name} = (props) => {\n`;
+ generatedCode += writeStateProps(currComponent.useStateCodes)
+ ? `\t${writeStateProps(currComponent.useStateCodes)}\n`
+ : '';
+ generatedCode += createEventHandler(enrichedChildren)
+ ? `${createEventHandler(enrichedChildren)}\n`
+ : '';
+ generatedCode += `
+ return(
+ <>
+ ${createRender()}
+ >
+ );`;
+ generatedCode += `\n}`;
+ return generatedCode;
+ } else if (projectType === 'Next.js') {
+ return `
+ import React, { useState } from 'react';
+ ${importsMapped}
+ import Head from 'next/head'
+ ${links ? `import Link from 'next/link'` : ``}
+ ${images ? `import Image from 'next/image'` : ``}
+
+ const ${
+ currComponent.name[0].toUpperCase() + currComponent.name.slice(1)
+ } = (props): JSX.Element => {
+ return (
+ <>
+ ${
+ isRoot
+ ? `
+
+ ${currComponent.name}
+ `
+ : ``
+ }
+ ${writeNestedElements(enrichedChildren)}
+ >
+ );
+ }
+ export default ${
+ currComponent.name[0].toUpperCase() + currComponent.name.slice(1)
+ };
+ `;
+ } else {
+ // gatsby component code
+ return `
+ import React, { useState } from 'react';
+ ${importsMapped}
+ import { StaticQuery, graphql } from 'gatsby';
+ ${links ? `import { Link } from 'gatsby'` : ``}
+ const ${currComponent.name} = (props: any): JSX.Element => {
+ return (
+ <>
+ ${
+ isRoot
+ ? `
+ ${currComponent.name}
+ `
+ : ``
+ }
+
+ ${writeNestedElements(enrichedChildren)}
+
+ >
+ );
+ }
+ export default ${currComponent.name};
+ `;
+ }
+};
+// formats code with prettier linter
+const formatCode = (code: string) => {
+ if (import.meta.env.NODE_ENV === 'test') {
+ const { format } = require('prettier');
+ return format(code, {
+ singleQuote: true,
+ trailingComma: 'es5',
+ bracketSpacing: true,
+ jsxBracketSameLine: true,
+ parser: 'babel'
+ });
+ } else {
+ return code;
+ }
+};
+export default generateCode;
diff --git a/app/src/helperFunctions/renderChildren.tsx b/app/src/helperFunctions/renderChildren.tsx
index 33844c64..9a19901a 100644
--- a/app/src/helperFunctions/renderChildren.tsx
+++ b/app/src/helperFunctions/renderChildren.tsx
@@ -1,166 +1,231 @@
-import React from 'react';
-import { ChildElement } from '../interfaces/Interfaces';
-import DirectChildComponent from '../components/main/DirectChildComponent';
-import DirectChildHTML from '../components/main/DirectChildHTML';
-import DirectChildHTMLNestable from '../components/main/DirectChildHTMLNestable';
-import SeparatorChild from '../components/main/SeparatorChild';
-import RouteLink from '../components/main/RouteLink';
-import { useSelector } from 'react-redux';
-import { RootState } from '../redux/store';
-
-// helper method to render all direct children of a component
-// direct children are clickable and draggable
-// direct children may also have their own indirect children (grandchildren, great-grandchildren, etc) which are not draggable and clickable
-// there are four types of direct children that can be rendered on the screen
-const renderChildren = (children: ChildElement[]) => {
- const state = useSelector((store: RootState) => store.appState);
-
- return children.map((child: ChildElement, i: number) => {
- const { type, style, childId, children, attributes, name } = child;
- let { typeId } = child;
- 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)
- // Removed style from prop drills so that styling isn't applied to canvas items.
- // Also added keys & removed an unnecessary div around DirChildNestables that was causing errors.
- if (type === 'Component') {
- return (
-
- );
- }
- // child is a non-nestable type of HTML element (aka NOT divs, forms, OrderedLists, UnorderedLists, menus)
- else if (
- type === 'HTML Element' &&
- typeId !== 11 &&
- typeId !== 1000 &&
- typeId !== 1 &&
- typeId !== 2 &&
- typeId !== 3 &&
- typeId !== 4 &&
- typeId !== 5 &&
- typeId !== 6 &&
- typeId !== 8 &&
- typeId !== 9 &&
- typeId !== 14 &&
- typeId !== 15 &&
- typeId !== 16 &&
- typeId !== 17 &&
- typeId !== 18 &&
- typeId !== -1 &&
- typeId !== 19
- ) {
- return (
-
- );
- }
- // child is a nestable type of HTML element (divs, forms, OrderedLists, UnorderedLists, menus)
- else if (
- type === 'HTML Element' &&
- (typeId === 11 ||
- typeId === 1 ||
- typeId === 2 ||
- typeId === 3 ||
- typeId === 4 ||
- typeId === 5 ||
- typeId === 6 ||
- typeId === 8 ||
- typeId === 9 ||
- typeId === 14 ||
- typeId === 15 ||
- typeId === 16 ||
- typeId === 17 ||
- typeId === 18 ||
- typeId === -1 ||
- typeId === 19)
- ) {
- if (
- (typeId === 18 || typeId === 19) &&
- state.projectType === 'Classic React'
- )
- typeId = 18;
- if ((typeId === 17 || typeId === -1) && state.projectType === 'Next.js')
- return renderChildren(children);
- if ((typeId === 18 || typeId === 19) && state.projectType === 'Next.js')
- typeId = 19;
- return (
-
- );
- } else if (type === 'HTML Element' && typeId === 1000) {
- return (
-
- );
- }
- // A route link is a next.js or gatsby.js navigation link
- // The route link component includes a clickable link that, when clicked, will change the user focus to the referenced component
- else if (type === 'Route Link') {
- return (
-
- );
- }
- });
-};
-// removed style from prop drilling
-export default renderChildren;
+import React from 'react';
+import { ChildElement } from '../interfaces/Interfaces';
+import DirectChildComponent from '../components/main/DirectChildComponent';
+import DirectChildHTML from '../components/main/DirectChildHTML';
+import DirectChildHTMLNestable from '../components/main/DirectChildHTMLNestable';
+import DirectChildMUI from '../components/main/DirectChildMUI';
+import DirectChildMUINestable from '../components/main/DirectChildMUINestable';
+import SeparatorChild from '../components/main/SeparatorChild';
+import RouteLink from '../components/main/RouteLink';
+import { useSelector } from 'react-redux';
+import { RootState } from '../redux/store';
+
+// helper method to render all direct children of a component
+// direct children are clickable and draggable
+// direct children may also have their own indirect children (grandchildren, great-grandchildren, etc) which are not draggable and clickable
+// there are four types of direct children that can be rendered on the screen
+const renderChildren = (children: ChildElement[]) => {
+ const state = useSelector((store: RootState) => store.appState);
+
+ 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)
+ // Removed style from prop drills so that styling isn't applied to canvas items.
+ // Also added keys & removed an unnecessary div around DirChildNestables that was causing errors.
+ if (type === 'Component') {
+ return (
+
+ );
+ }
+ // child is a non-nestable type of HTML element
+ // nestable = false -> input(10), img(12), image(20)
+ else if (
+ type === 'HTML Element' &&
+ typeId !== 11 &&
+ typeId !== 1000 &&
+ typeId !== 1 &&
+ typeId !== 2 &&
+ typeId !== 3 &&
+ typeId !== 4 &&
+ typeId !== 5 &&
+ typeId !== 6 &&
+ typeId !== 8 &&
+ typeId !== 9 &&
+ typeId !== 14 &&
+ typeId !== 15 &&
+ typeId !== 16 &&
+ typeId !== 17 &&
+ typeId !== 18 &&
+ typeId !== -1 &&
+ typeId !== 19
+ ) {
+ return (
+
+ );
+ }
+ // child is a nestable type of HTML element (divs, forms, OrderedLists, UnorderedLists, menus)
+ else if (
+ type === 'HTML Element' &&
+ (typeId === 11 ||
+ typeId === 1 ||
+ typeId === 2 ||
+ typeId === 3 ||
+ typeId === 4 ||
+ typeId === 5 ||
+ typeId === 6 ||
+ typeId === 8 ||
+ typeId === 9 ||
+ typeId === 14 ||
+ typeId === 15 ||
+ typeId === 16 ||
+ typeId === 17 ||
+ typeId === 18 ||
+ typeId === -1 ||
+ typeId === 19)
+ ) {
+ if (
+ (typeId === 18 || typeId === 19) &&
+ state.projectType === 'Classic React'
+ )
+ typeId = 18;
+ if ((typeId === 17 || typeId === -1) && state.projectType === 'Next.js')
+ return renderChildren(children);
+ if ((typeId === 18 || typeId === 19) && state.projectType === 'Next.js')
+ typeId = 19;
+ return (
+
+ );
+ } else if (type === 'MUI Component' && typeId === 31) {
+ return (
+
+ );
+ }
+ // child is a nestable type of HTML element (divs, forms, OrderedLists, UnorderedLists, menus)
+ else if (
+ type === 'MUI Component' &&
+ (typeId === 21 || typeId === 41 || typeId === 51)
+ ) {
+ return (
+
+ );
+ } else if (type === 'MUI Component' && typeId === 1000) {
+ return (
+
+ );
+ } else if (type === 'HTML Element' && typeId === 1000) {
+ return (
+
+ );
+ }
+ // A route link is a next.js or gatsby.js navigation link
+ // The route link component includes a clickable link that, when clicked, will change the user focus to the referenced component
+ else if (type === 'Route Link') {
+ return (
+
+ );
+ }
+ });
+};
+// removed style from prop drilling
+export default renderChildren;
diff --git a/app/src/interfaces/Interfaces.ts b/app/src/interfaces/Interfaces.ts
index 5e49e324..1c38b7f9 100644
--- a/app/src/interfaces/Interfaces.ts
+++ b/app/src/interfaces/Interfaces.ts
@@ -17,6 +17,7 @@ export interface State {
nextBottomSeparatorId?: number;
nextChildId: number;
HTMLTypes: HTMLType[];
+ MUITypes: MUIType[];
tailwind: boolean;
stylesheet: string;
codePreview: boolean;
@@ -114,6 +115,7 @@ export interface MUIType {
framework: string;
nestable: boolean;
imports: any[];
+ props: any[];
}
export interface DragItem extends DragObjectWithType {
newInstance: boolean;
diff --git a/app/src/public/styles/style.css b/app/src/public/styles/style.css
index 8ae9bfa2..b349080e 100644
--- a/app/src/public/styles/style.css
+++ b/app/src/public/styles/style.css
@@ -1,911 +1,937 @@
-@import url('https://fonts.googleapis.com/css?family=Open+Sans&display=swap');
-@import url('https://fonts.googleapis.com/css2?family=Raleway&display=swap');
-@import url('https://fonts.googleapis.com/css2?family=Roboto&display=swap');
-@import url('https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@48,400,0,0');
-/* @import url('https: //fonts.googleapis.com/css2?family=Fira+Code:wght@300;400;500;600;700&display=swap'); */
-
-*,
-*::before,
-*::after {
- box-sizing: inherit;
-}
-
-html {
- box-sizing: border-box;
- overflow: auto;
-}
-
-body {
- margin: 0;
- padding: 0;
- font-family: 'Roboto', 'Raleway', sans-serif;
- font-size: 0.9 em;
- font-weight: normal;
- overflow: hidden;
- height: 100vh;
- width: 100vw;
- background-color: #191919;
-}
-
-h4 {
- color: #155084;
- text-align: center;
-}
-
-.creation-panel {
- color: black;
- display: flex;
- flex-direction: row;
- height: 100%;
- width: 100%;
-}
-
-.customization-section {
- color: black;
- display: flex;
- flex-direction: row;
- height: 100%;
-}
-
-.state-panel {
- margin-top: -15px;
- flex-grow: 2;
- display: flex;
- flex-direction: row;
- justify-content: space-evenly;
-}
-
-.state-prop-grid {
- height: 300px;
- width: 410px;
- flex-grow: 1;
- padding-left: 10px;
- padding-right: 10px;
-}
-
-.HTMLItemCreate {
- flex-grow: 1;
- justify-content: center;
-}
-
-:disabled {
- cursor: not-allowed;
-}
-
-header {
- display: none;
-}
-
-.app-container {
- display: flex;
- width: 100%;
- height: calc(100vh - 64px);
-}
-
-.mode-change-button {
- display: flex;
- align-content: center;
- padding-top: 50px;
-}
-
-.progress-bar {
- flex-grow: 1;
-}
-
-.column {
- background-color: #1e2024;
- margin-right: 1.5px;
-}
-
-/*
-/////////////////////////////////////////////////
-LEFT COLUMN
-/////////////////////////////////////////////////
-*/
-
-.app-container {
- position: relative;
-}
-
-.left-container {
- display: flex;
- z-index: 1;
-}
-
-span.material-symbols-outlined {
- padding: 1px;
- font-size: 18px;
-}
-
-.left {
- padding: 0px;
- display: flex;
- height: 100%;
- width: 280px;
- min-width: 225px;
- flex-direction: column;
- align-content: center;
- position: relative;
- overflow-x: hidden;
-}
-
-.column.left::-webkit-scrollbar {
- width: 0.5rem;
- height: 0.5rem;
-}
-
-.column.left::-webkit-scrollbar-thumb {
- transition: 0.3s ease all;
- border-color: transparent;
- background-color: #1e2024;
- border-radius: 8px;
- z-index: 40;
-}
-
-.hide-show {
- position: absolute;
- height: 100%;
- left: -252px;
- transition: all 0.5s ease-in-out;
-}
-
-.hide-show:hover {
- left: 0px;
- z-index: 289;
-}
-
-.HTMLItems {
- display: flex;
- flex-direction: column;
- justify-content: space-around;
- flex-wrap: wrap;
- width: 100%;
- flex-grow: 1;
-}
-
-.HTMLItemsDark {
- display: flex;
- flex-direction: column;
- justify-content: space-around;
- flex-wrap: wrap;
- flex-grow: 1;
- border: 3px solid white;
-}
-
-#Half {
- display: flex;
- flex-direction: column;
- justify-content: top;
- height: 60vh;
- text-align: center;
- overflow-x: hidden;
- overflow-y: auto;
-}
-
-#HTMLItemsTopHalf::-webkit-scrollbar {
- width: 0.8rem;
- height: 0.5rem;
-}
-
-#HTMLItemsTopHalf::-webkit-scrollbar-thumb {
- transition: 0.3s ease all;
- border-color: transparent;
- background-color: #1e2024;
- border-radius: 8px;
- z-index: 40;
-}
-
-#CompBottomHalf {
- flex-grow: 1;
- text-align: center;
- overflow-x: hidden;
- overflow-y: auto;
-}
-
-.HTMLElements {
- display: flex;
- flex-direction: column;
- align-items: flex-start;
- flex-wrap: wrap;
-}
-
-#HTMLgrid {
- margin: 5px;
-}
-
-#HTMLItemsGrid {
- display: flex;
-}
-
-#HTMLItem {
- transition: 0.3s;
- border: 1px solid transparent;
- color: #f2fbf8;
- padding: 10%;
- border-radius: 10px;
- box-shadow: #191919;
-}
-
-#HTMLItem:hover {
- background-color: #474545a8;
- color: #8ca8ff;
-}
-
-#submitButton {
- margin-left: 5px;
-}
-
-#submitButton:hover {
- background-color: #0671e3;
- color: white;
- border: none;
- cursor: pointer;
-}
-
-.lineDiv {
- width: 150%;
- margin-left: 0px;
-}
-
-.customForm {
- display: flex;
- flex-direction: column;
- align-items: start;
- color: white;
- font-size: 95%;
-}
-
-.customForm h4 {
- align-self: center;
-}
-
-#newElement {
- border: none;
- background: none;
- display: flex;
- align-items: baseline;
- justify-content: flex-start;
- width: 0.5em;
-}
-
-/* span container in HTMLItem.tsx */
-#customHTMLElement {
- display: flex;
- flex-direction: row;
- justify-content: center;
- width: 125px;
-}
-
-.component-input {
- bottom: 2.8%;
-}
-
-.clear-workspace {
- top: 96%;
- position: sticky !important;
- z-index: 1;
-}
-
-.hidden {
- display: none;
-}
-
-/*
-/////////////////////////////////////////////////
-MAIN COLUMN
-////////////////////////////////////////////////
-*/
-
-h1 {
- color: rgb(75, 75, 75);
-}
-
-.main-container {
- display: flex;
- flex-direction: column;
- flex: 1;
- min-width: 700px;
-}
-
-.main-header {
- background-color: #212121;
- box-shadow: 0 5px 7px -2px rgba(0, 0, 0, 0.1);
- z-index: 10;
-}
-
-.main-header-buttons {
- display: flex;
- font-size: 12px;
-}
-
-.main {
- background: #fff;
- flex: 1;
- width: 100%;
- overflow: auto;
- display: flex;
- background-color: #e4e4e4;
-}
-
-.draggable {
- position: relative;
-}
-
-.image {
- top: 0;
- left: 0;
- width: 100%;
- position: relative;
-}
-
-.image-container {
- position: relative;
- display: flex;
- top: 0;
- left: 0;
-}
-
-.export-preference div span {
- transition: 0.5s;
-}
-
-#export-modal {
- transition: 0.5s;
-}
-
-#export-modal:hover {
- background-color: #66c4eb;
- cursor: pointer;
-}
-/* ///CHANGED COLOR COLOR */
-#export-modal:hover span {
- color: white;
-}
-
-.info {
- width: 100%;
- height: 100%;
- display: flex;
- justify-content: center;
- align-items: center;
- text-align: center;
-}
-
-.ace_scrollbar {
- display: none;
-}
-
-.ace_print-margin {
- display: none;
-}
-
-/*
-//////////////////////////////////////////////////
-RIGHT COLUMN
-/////////////////////////////////////////////////
-// RIGHT NOW, I'M NOT SURE WHAT RIGHT COL IS REFERING TO
-*/
-#rightContainer {
- display: flex;
- flex-direction: row;
- height: 100%;
- width: 99.9%;
-}
-
-.right {
- padding-top: 0px;
- width: 100%;
- height: 100%;
-}
-
-.rightContainer {
- margin-top: 65px;
- color: black;
-}
-
-.rightPanelWrapper {
- display: flex;
- flex-direction: row;
- justify-content: center;
- height: 100%;
- width: 100%;
-}
-
-#addComponentButton {
- height: 49px;
- width: 130px;
- background-color: #0671e3;
- border: 2px solid #0671e3;
- border-radius: 10px;
- font-size: 16.5px;
-}
-
-#addComponentButton:hover {
- background-color: #25366d;
- border: 2px solid #354e9c;
- color: white;
- cursor: pointer;
-}
-
-.export {
- border-top: 1px solid #ccc;
- display: flex;
- justify-content: center;
- align-items: center;
- flex-direction: row;
-}
-
-.makeStyles-panelWrapperList-4::-webkit-scrollbar-track {
- background-color: rgba(25, 25, 25, 0.8);
-}
-
-.compPanelItem {
- height: 100%;
- display: flex;
- justify-content: center;
- align-content: center;
- border-color: #0671e3;
-}
-
-.compPanelItem h3 {
- font-size: 1em;
- letter-spacing: 0.75px;
- padding: 5px 10px;
-}
-
-.compPanelItem:hover {
- cursor: pointer;
-}
-
-/*
-/////////////////////////////////////////////////////
-BOTTOM PANEL
-/////////////////////////////////////////////////////
-*/
-
-.tab-content {
- height: calc(100% - 76px);
- width: 100%;
- overflow-y: scroll;
- overflow-x: auto;
- position: relative;
- z-index: 0;
- background-color: #1e2024;
-}
-
-.tab-content::-webkit-scrollbar {
- width: 0.8rem;
- height: 0.5rem;
-}
-
-.tab-content::-webkit-scrollbar-thumb {
- transition: 0.3s ease all;
- border-color: transparent;
- background-color: gray;
- border-radius: 8px;
- z-index: 40;
-}
-
-.tab-content::-webkit-scrollbar {
- width: 0.8rem;
- height: 0.5rem;
-}
-
-.tab-content::-webkit-scrollbar-thumb {
- transition: 0.3s ease all;
- border-color: transparent;
- background-color: gray;
- border-radius: 8px;
- z-index: 40;
-}
-
-.MuiDataGrid-menuList,
-.MuiDataGrid-columnsPanel,
-.MuiDataGrid-filterForm {
- background-color: #333333;
-}
-.MuiDataGrid-panelHeader,
-.MuiDataGrid-panelFooter {
- background-color: #252525;
-}
-
-#resize-drag {
- cursor: row-resize;
- height: 23px;
- width: 75px;
- background-color: #1e2024;
- text-align: center;
- border-radius: 7px 7px 0 0;
- align-self: center;
- margin-top: -25px;
- color: silver;
- user-select: none;
- z-index: 1;
-}
-
-.bottom-panel {
- transition: width 250ms ease-in-out;
- background-color: #0671e3;
- width: 100%;
- height: 100%;
- display: flex;
- flex-direction: column;
-}
-
-.htmlattr {
- height: 80%;
- overflow-y: scroll;
- margin-left: 50px;
-}
-
-.props-container {
- margin-left: 10px;
- height: 33%;
- width: 100%;
-}
-
-.props-input {
- margin-bottom: 15px;
-}
-
-.chips {
- height: 55%;
- overflow: auto;
-}
-
-.flex-container {
- display: flex;
- flex-direction: row;
- padding-top: 5px;
-}
-
-.flex1 {
- padding: 10px 20px 10px 5px;
-}
-
-.flex2 {
- padding-right: 10px;
- padding-left: 10px;
- border: rgb(68, 68, 68) solid 1px;
- border-radius: 5px;
- background-color: #0671e3;
-}
-
-.event-table {
- height: 300px;
- width: 410px;
- flex-grow: 1;
- padding-left: 25px;
- padding-right: 25px;
- margin-top: 20px;
-}
-
-#message-container::-webkit-scrollbar {
- width: 0.1rem;
- height: 0.1rem;
-}
-
-#message-container::-webkit-scrollbar-thumb {
- transition: 0.3s ease all;
- border-color: transparent;
- background-color: #1e2024;
- background-image: linear-gradient(180deg, #1e2024 15%, grey 15%, grey 85%, #1e2024 85%);
- border-radius: 10px;
- z-index: 40;
- overflow-y: hidden;
-}
-
-#message-input {
- outline: none;
-}
-
-.video-scrollable::-webkit-scrollbar {
- width: 0.1rem;
- height: 0.1rem;
-}
-
-.video-scrollable::-webkit-scrollbar-thumb {
- transition: 0.3s ease all;
- border-color: transparent;
- background-color: #1e2024;
- background-image: linear-gradient(180deg, #1e2024 15%, #31343A 15%, #31343A 85%, #1e2024 85%);
- border-radius: 10px;
- z-index: 40;
- overflow-y: hidden;
-}
-
-/*
-//////////////////////////////////////////
-NAVBAR
-//////////////////////////////////////////
-*/
-
-.main-navbar {
- display: flex;
- justify-content: space-between;
- align-items: center;
- z-index: 9999;
-}
-
-.main-logo {
- display: flex;
- align-items: center;
- gap: 10px;
- margin-left: 15px;
-}
-
-.main-navbar svg {
- width: 30px;
- height: 30px;
- color: gray;
- margin-right: 2vw;
- cursor: pointer;
-}
-
-.navDropDown {
- position: absolute;
- width: 230px;
- border-radius: 8px;
- right: 1vw;
- z-index: 10;
- background-color: #31343a;
- top: 45px;
- padding: 10px 0;
- display: hidden;
-}
-
-.hideNavDropDown {
- display: none;
-}
-
-.navDropDown button {
- width: 100%;
- background-color: #31343a;
- color: #d9dada;
- padding: 9px 25px;
- display: flex;
- align-items: center;
- border: none;
-}
-
-.navDropDown span {
- margin-left: -25px;
-}
-
-.navDropDown p {
- margin: 3px 5%;
- color: #d9dada;
-}
-
-.navDropDown button:hover {
- cursor: pointer;
- background-color: #393c42;
-}
-
-.navDropDown button svg {
- width: 20px;
- height: 20px;
- color: gray;
-}
-
-#navbarButton {
- color: white;
- margin: 0px 5px 0px 5px;
-}
-
-#customized-menu {
- width: 16%;
-}
-
-#canvasContainer::-webkit-scrollbar {
- width: 0.5rem;
- height: 0.7rem;
-}
-
-#canvasContainer::-webkit-scrollbar-thumb {
- transition: 0.3s ease all;
- border-color: transparent;
- background-color: gray;
- border-radius: 8px;
- z-index: 40;
-}
-
-/*
-/////////////////////////////////////////////////
-CANVAS WINDOW
-/////////////////////////////////////////////////
-*/
-
-.delete-button-empty {
- float: right;
- background-color: rgba(0, 0, 0, 0);
- border-radius: 50%;
- border: none;
- font-size: 15px;
- color: white;
-}
-
-.deleteIcon {
- font-size: 18px;
- color: gray;
- cursor: pointer;
-}
-
-.deleteIcon:hover {
- color: white;
-}
-
-.componentContainer {
- display: flex;
- flex-direction: column;
- align-items: center;
- font-family: 'Roboto', 'Raleway', sans-serif;
- height: 100%;
-}
-
-/* Material-UI */
-/* Sortable tree sorting */
-.sortable-tree {
- height: 100%;
- background-color: rgb(37, 37, 38);
-}
-
-div.rst__rowContents {
- width: 45px;
- border: none;
- color: #eee;
- background-color: #333333;
-}
-
-.rst__rowContentsDragDisabled {
- background-color: #333333;
- background-color: rgba(37, 37, 38, 0.4);
-}
-
-.rst__moveHandle,
-.rst__loadingHandle {
- width: 40px;
-}
-
-.rst_tree {
- overflow: auto;
-}
-
-.rst__rowLabel {
- padding-right: 5px;
-}
-
-.closeIcon:hover {
- cursor: pointer;
-}
-
-/* Additional styling for ReacType X */
-.componentDefault {
- width: auto;
- border: 2px solid gray;
- min-height: 150px;
- padding: 10px;
- background-color: rgb(213, 80, 164);
- opacity: 0.75;
-}
-
-.inputName {
- margin-bottom: 35px;
- text-align: center;
-}
-
-.oauth-bttn:hover {
- background-color: #4e76c7;
-}
-
-.text-editor .bttn {
- position: absolute;
- bottom: 90px;
- right: 30px;
- z-index: 999;
-}
-
-.cssSaveBtn {
- position: absolute;
- bottom: 30px;
- right: 30px;
- z-index: 999;
- background-color: #0671e3;
- box-shadow: none;
- padding: 32px;
-}
-
-.useState-btn {
- color: rgb(241, 240, 240);
- background-color: #2d313a;
- border: 1px solid #2d313a;
- border-radius: 10px;
- box-shadow: '2px 2px 2px #1a1a1a';
- font-family: Arial, Helvetica, sans-serif;
- font-size: 15px;
- padding: 12px;
- margin-left: 20px;
-}
-
-.useState-position {
- display: flex;
- flex-direction: column;
- position: fixed;
- align-items: center;
- top: 30%;
- left: 30%;
-}
-
-.useState-header {
- font-size: 35px;
- background-color: #0671e3;
- color: rgb(241, 240, 240);
- width: 600px;
- border: 3px;
- border-style: solid;
- border-color: #0671e3;
- font-family: 'Open Sans', sans-serif;
- border-radius: 15px 15px 0px 0px;
-}
-
-.useState-dropdown {
- align-self: flex-start;
-}
-
-.useState-window {
- width: 600px;
- height: 400px;
- resize: none;
- white-space: pre-line;
- font-size: 18px;
- border: 2px;
- border-style: solid;
- border-color: black;
- border-radius: 0px 0px 15px 15px;
- font-family: Arial, Helvetica, sans-serif;
- background-color: rgb(241, 240, 240);
- display: flex;
- flex-direction: column;
- align-items: center;
-}
-
-li {
- color: black;
-}
-
-.bi {
- margin-right: 0.5rem;
- text-transform: none;
-}
-
-.blue-accent-text {
- color: #2997ff;
-}
-
-a.nav_link {
- display: flex;
- justify-content: center;
- align-items: center;
- color: rgba(0, 0, 0, 0.87);
- cursor: pointer;
- text-decoration: none;
- margin-top: 1rem;
-}
-
-a.nav_link:hover {
- color: #3ec1ac;
- text-decoration: underline;
-}
-
-.other-options {
- margin-bottom: 1r;
- text-transform: none;
- size: 1rem;
- color: white;
- transition: 300ms;
-}
-
-.other-options:hover {
- color: #aaaaaa;
- cursor: pointer;
-}
+@import url('https://fonts.googleapis.com/css?family=Open+Sans&display=swap');
+@import url('https://fonts.googleapis.com/css2?family=Raleway&display=swap');
+@import url('https://fonts.googleapis.com/css2?family=Roboto&display=swap');
+@import url('https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@48,400,0,0');
+/* @import url('https: //fonts.googleapis.com/css2?family=Fira+Code:wght@300;400;500;600;700&display=swap'); */
+
+*,
+*::before,
+*::after {
+ box-sizing: inherit;
+}
+
+html {
+ box-sizing: border-box;
+ overflow: auto;
+}
+
+body {
+ margin: 0;
+ padding: 0;
+ font-family: 'Roboto', 'Raleway', sans-serif;
+ font-size: 0.9 em;
+ font-weight: normal;
+ overflow: hidden;
+ height: 100vh;
+ width: 100vw;
+ background-color: #191919;
+}
+
+h4 {
+ color: #155084;
+ text-align: center;
+}
+
+.creation-panel {
+ color: black;
+ display: flex;
+ flex-direction: row;
+ height: 100%;
+ width: 100%;
+}
+
+.customization-section {
+ color: black;
+ display: flex;
+ flex-direction: row;
+ height: 100%;
+}
+
+.state-panel {
+ margin-top: -15px;
+ flex-grow: 2;
+ display: flex;
+ flex-direction: row;
+ justify-content: space-evenly;
+}
+
+.state-prop-grid {
+ height: 300px;
+ width: 410px;
+ flex-grow: 1;
+ padding-left: 10px;
+ padding-right: 10px;
+}
+
+.HTMLItemCreate {
+ flex-grow: 1;
+ justify-content: center;
+}
+
+:disabled {
+ cursor: not-allowed;
+}
+
+header {
+ display: none;
+}
+
+.app-container {
+ display: flex;
+ width: 100%;
+ height: calc(100vh - 64px);
+}
+
+.mode-change-button {
+ display: flex;
+ align-content: center;
+ padding-top: 50px;
+}
+
+.progress-bar {
+ flex-grow: 1;
+}
+
+.column {
+ background-color: #1e2024;
+ margin-right: 1.5px;
+}
+
+/*
+/////////////////////////////////////////////////
+LEFT COLUMN
+/////////////////////////////////////////////////
+*/
+
+.app-container {
+ position: relative;
+}
+
+.left-container {
+ display: flex;
+ z-index: 1;
+}
+
+span.material-symbols-outlined {
+ padding: 1px;
+ font-size: 18px;
+}
+
+.left {
+ padding: 0px;
+ display: flex;
+ height: 100%;
+ width: 280px;
+ min-width: 225px;
+ flex-direction: column;
+ align-content: center;
+ position: relative;
+ overflow-x: hidden;
+}
+
+.column.left::-webkit-scrollbar {
+ width: 0.5rem;
+ height: 0.5rem;
+}
+
+.column.left::-webkit-scrollbar-thumb {
+ transition: 0.3s ease all;
+ border-color: transparent;
+ background-color: #1e2024;
+ border-radius: 8px;
+ z-index: 40;
+}
+
+.hide-show {
+ position: absolute;
+ height: 100%;
+ left: -252px;
+ transition: all 0.5s ease-in-out;
+}
+
+.hide-show:hover {
+ left: 0px;
+ z-index: 289;
+}
+
+.HTMLItems {
+ display: flex;
+ flex-direction: column;
+ justify-content: space-around;
+ flex-wrap: wrap;
+ width: 100%;
+ flex-grow: 1;
+}
+
+.HTMLItemsDark {
+ display: flex;
+ flex-direction: column;
+ justify-content: space-around;
+ flex-wrap: wrap;
+ flex-grow: 1;
+ border: 3px solid white;
+}
+
+#Half {
+ display: flex;
+ flex-direction: column;
+ justify-content: top;
+ height: 60vh;
+ text-align: center;
+ overflow-x: hidden;
+ overflow-y: auto;
+}
+
+#HTMLItemsTopHalf::-webkit-scrollbar {
+ width: 0.8rem;
+ height: 0.5rem;
+}
+
+#HTMLItemsTopHalf::-webkit-scrollbar-thumb {
+ transition: 0.3s ease all;
+ border-color: transparent;
+ background-color: #1e2024;
+ border-radius: 8px;
+ z-index: 40;
+}
+
+#CompBottomHalf {
+ flex-grow: 1;
+ text-align: center;
+ overflow-x: hidden;
+ overflow-y: auto;
+}
+
+.HTMLElements {
+ display: flex;
+ flex-direction: column;
+ align-items: flex-start;
+ flex-wrap: wrap;
+}
+
+#HTMLgrid {
+ margin: 5px;
+}
+
+#HTMLItemsGrid {
+ display: flex;
+}
+
+#HTMLItem {
+ transition: 0.3s;
+ border: 1px solid transparent;
+ color: #f2fbf8;
+ padding: 10%;
+ border-radius: 10px;
+ box-shadow: #191919;
+}
+
+#HTMLItem:hover {
+ background-color: #474545a8;
+ color: #8ca8ff;
+}
+
+#MUIItem {
+ transition: 0.3s;
+ border: 1px solid transparent;
+ color: #f2fbf8;
+ padding: 10%;
+ border-radius: 10px;
+ box-shadow: #191919;
+}
+
+#MUIItem:hover {
+ background-color: #474545a8;
+ color: #8ca8ff;
+}
+
+#submitButton {
+ margin-left: 5px;
+}
+
+#submitButton:hover {
+ background-color: #0671e3;
+ color: white;
+ border: none;
+ cursor: pointer;
+}
+
+.lineDiv {
+ width: 150%;
+ margin-left: 0px;
+}
+
+.customForm {
+ display: flex;
+ flex-direction: column;
+ align-items: start;
+ color: white;
+ font-size: 95%;
+}
+
+.customForm h4 {
+ align-self: center;
+}
+
+#newElement {
+ border: none;
+ background: none;
+ display: flex;
+ align-items: baseline;
+ justify-content: flex-start;
+ width: 0.5em;
+}
+
+/* span container in HTMLItem.tsx */
+#customHTMLElement {
+ display: flex;
+ flex-direction: row;
+ justify-content: center;
+ width: 125px;
+}
+
+.component-input {
+ bottom: 2.8%;
+}
+
+.clear-workspace {
+ top: 96%;
+ position: sticky !important;
+ z-index: 1;
+}
+
+.hidden {
+ display: none;
+}
+
+/*
+/////////////////////////////////////////////////
+MAIN COLUMN
+////////////////////////////////////////////////
+*/
+
+h1 {
+ color: rgb(75, 75, 75);
+}
+
+.main-container {
+ display: flex;
+ flex-direction: column;
+ flex: 1;
+ min-width: 700px;
+}
+
+.main-header {
+ background-color: #212121;
+ box-shadow: 0 5px 7px -2px rgba(0, 0, 0, 0.1);
+ z-index: 10;
+}
+
+.main-header-buttons {
+ display: flex;
+ font-size: 12px;
+}
+
+.main {
+ background: #fff;
+ flex: 1;
+ width: 100%;
+ overflow: auto;
+ display: flex;
+ background-color: #e4e4e4;
+}
+
+.draggable {
+ position: relative;
+}
+
+.image {
+ top: 0;
+ left: 0;
+ width: 100%;
+ position: relative;
+}
+
+.image-container {
+ position: relative;
+ display: flex;
+ top: 0;
+ left: 0;
+}
+
+.export-preference div span {
+ transition: 0.5s;
+}
+
+#export-modal {
+ transition: 0.5s;
+}
+
+#export-modal:hover {
+ background-color: #66c4eb;
+ cursor: pointer;
+}
+/* ///CHANGED COLOR COLOR */
+#export-modal:hover span {
+ color: white;
+}
+
+.info {
+ width: 100%;
+ height: 100%;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ text-align: center;
+}
+
+.ace_scrollbar {
+ display: none;
+}
+
+.ace_print-margin {
+ display: none;
+}
+
+/*
+//////////////////////////////////////////////////
+RIGHT COLUMN
+/////////////////////////////////////////////////
+// RIGHT NOW, I'M NOT SURE WHAT RIGHT COL IS REFERING TO
+*/
+#rightContainer {
+ display: flex;
+ flex-direction: row;
+ height: 100%;
+ width: 99.9%;
+}
+
+.right {
+ padding-top: 0px;
+ width: 100%;
+ height: 100%;
+}
+
+.rightContainer {
+ margin-top: 65px;
+ color: black;
+}
+
+.rightPanelWrapper {
+ display: flex;
+ flex-direction: row;
+ justify-content: center;
+ height: 100%;
+ width: 100%;
+}
+
+#addComponentButton {
+ height: 49px;
+ width: 130px;
+ background-color: #0671e3;
+ border: 2px solid #0671e3;
+ border-radius: 10px;
+ font-size: 16.5px;
+}
+
+#addComponentButton:hover {
+ background-color: #25366d;
+ border: 2px solid #354e9c;
+ color: white;
+ cursor: pointer;
+}
+
+.export {
+ border-top: 1px solid #ccc;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ flex-direction: row;
+}
+
+.makeStyles-panelWrapperList-4::-webkit-scrollbar-track {
+ background-color: rgba(25, 25, 25, 0.8);
+}
+
+.compPanelItem {
+ height: 100%;
+ display: flex;
+ justify-content: center;
+ align-content: center;
+ border-color: #0671e3;
+}
+
+.compPanelItem h3 {
+ font-size: 1em;
+ letter-spacing: 0.75px;
+ padding: 5px 10px;
+}
+
+.compPanelItem:hover {
+ cursor: pointer;
+}
+
+/*
+/////////////////////////////////////////////////////
+BOTTOM PANEL
+/////////////////////////////////////////////////////
+*/
+
+.tab-content {
+ height: calc(100% - 76px);
+ width: 100%;
+ overflow-y: scroll;
+ overflow-x: auto;
+ position: relative;
+ z-index: 0;
+ background-color: #1e2024;
+}
+
+.tab-content::-webkit-scrollbar {
+ width: 0.8rem;
+ height: 0.5rem;
+}
+
+.tab-content::-webkit-scrollbar-thumb {
+ transition: 0.3s ease all;
+ border-color: transparent;
+ background-color: gray;
+ border-radius: 8px;
+ z-index: 40;
+}
+
+.tab-content::-webkit-scrollbar {
+ width: 0.8rem;
+ height: 0.5rem;
+}
+
+.tab-content::-webkit-scrollbar-thumb {
+ transition: 0.3s ease all;
+ border-color: transparent;
+ background-color: gray;
+ border-radius: 8px;
+ z-index: 40;
+}
+
+.MuiDataGrid-menuList,
+.MuiDataGrid-columnsPanel,
+.MuiDataGrid-filterForm {
+ background-color: #333333;
+}
+.MuiDataGrid-panelHeader,
+.MuiDataGrid-panelFooter {
+ background-color: #252525;
+}
+
+#resize-drag {
+ cursor: row-resize;
+ height: 23px;
+ width: 75px;
+ background-color: #1e2024;
+ text-align: center;
+ border-radius: 7px 7px 0 0;
+ align-self: center;
+ margin-top: -25px;
+ color: silver;
+ user-select: none;
+ z-index: 1;
+}
+
+.bottom-panel {
+ transition: width 250ms ease-in-out;
+ background-color: #0671e3;
+ width: 100%;
+ height: 100%;
+ display: flex;
+ flex-direction: column;
+}
+
+.htmlattr {
+ height: 80%;
+ overflow-y: scroll;
+ margin-left: 50px;
+}
+
+.props-container {
+ margin-left: 10px;
+ height: 33%;
+ width: 100%;
+}
+
+.props-input {
+ margin-bottom: 15px;
+}
+
+.chips {
+ height: 55%;
+ overflow: auto;
+}
+
+.flex-container {
+ display: flex;
+ flex-direction: row;
+ padding-top: 5px;
+}
+
+.flex1 {
+ padding: 10px 20px 10px 5px;
+}
+
+.flex2 {
+ padding-right: 10px;
+ padding-left: 10px;
+ border: rgb(68, 68, 68) solid 1px;
+ border-radius: 5px;
+ background-color: #0671e3;
+}
+
+.event-table {
+ height: 300px;
+ width: 410px;
+ flex-grow: 1;
+ padding-left: 25px;
+ padding-right: 25px;
+ margin-top: 20px;
+}
+
+#message-container::-webkit-scrollbar {
+ width: 0.1rem;
+ height: 0.1rem;
+}
+
+#message-container::-webkit-scrollbar-thumb {
+ transition: 0.3s ease all;
+ border-color: transparent;
+ background-color: #1e2024;
+ background-image: linear-gradient(
+ 180deg,
+ #1e2024 15%,
+ grey 15%,
+ grey 85%,
+ #1e2024 85%
+ );
+ border-radius: 10px;
+ z-index: 40;
+ overflow-y: hidden;
+}
+
+#message-input {
+ outline: none;
+}
+
+.video-scrollable::-webkit-scrollbar {
+ width: 0.1rem;
+ height: 0.1rem;
+}
+
+.video-scrollable::-webkit-scrollbar-thumb {
+ transition: 0.3s ease all;
+ border-color: transparent;
+ background-color: #1e2024;
+ background-image: linear-gradient(
+ 180deg,
+ #1e2024 15%,
+ #31343a 15%,
+ #31343a 85%,
+ #1e2024 85%
+ );
+ border-radius: 10px;
+ z-index: 40;
+ overflow-y: hidden;
+}
+
+/*
+//////////////////////////////////////////
+NAVBAR
+//////////////////////////////////////////
+*/
+
+.main-navbar {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ z-index: 9999;
+}
+
+.main-logo {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ margin-left: 15px;
+}
+
+.main-navbar svg {
+ width: 30px;
+ height: 30px;
+ color: gray;
+ margin-right: 2vw;
+ cursor: pointer;
+}
+
+.navDropDown {
+ position: absolute;
+ width: 230px;
+ border-radius: 8px;
+ right: 1vw;
+ z-index: 10;
+ background-color: #31343a;
+ top: 45px;
+ padding: 10px 0;
+ display: hidden;
+}
+
+.hideNavDropDown {
+ display: none;
+}
+
+.navDropDown button {
+ width: 100%;
+ background-color: #31343a;
+ color: #d9dada;
+ padding: 9px 25px;
+ display: flex;
+ align-items: center;
+ border: none;
+}
+
+.navDropDown span {
+ margin-left: -25px;
+}
+
+.navDropDown p {
+ margin: 3px 5%;
+ color: #d9dada;
+}
+
+.navDropDown button:hover {
+ cursor: pointer;
+ background-color: #393c42;
+}
+
+.navDropDown button svg {
+ width: 20px;
+ height: 20px;
+ color: gray;
+}
+
+#navbarButton {
+ color: white;
+ margin: 0px 5px 0px 5px;
+}
+
+#customized-menu {
+ width: 16%;
+}
+
+#canvasContainer::-webkit-scrollbar {
+ width: 0.5rem;
+ height: 0.7rem;
+}
+
+#canvasContainer::-webkit-scrollbar-thumb {
+ transition: 0.3s ease all;
+ border-color: transparent;
+ background-color: gray;
+ border-radius: 8px;
+ z-index: 40;
+}
+
+/*
+/////////////////////////////////////////////////
+CANVAS WINDOW
+/////////////////////////////////////////////////
+*/
+
+.delete-button-empty {
+ float: right;
+ background-color: rgba(0, 0, 0, 0);
+ border-radius: 50%;
+ border: none;
+ font-size: 15px;
+ color: white;
+}
+
+.deleteIcon {
+ font-size: 18px;
+ color: gray;
+ cursor: pointer;
+}
+
+.deleteIcon:hover {
+ color: white;
+}
+
+.componentContainer {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ font-family: 'Roboto', 'Raleway', sans-serif;
+ height: 100%;
+}
+
+/* Material-UI */
+/* Sortable tree sorting */
+.sortable-tree {
+ height: 100%;
+ background-color: rgb(37, 37, 38);
+}
+
+div.rst__rowContents {
+ width: 45px;
+ border: none;
+ color: #eee;
+ background-color: #333333;
+}
+
+.rst__rowContentsDragDisabled {
+ background-color: #333333;
+ background-color: rgba(37, 37, 38, 0.4);
+}
+
+.rst__moveHandle,
+.rst__loadingHandle {
+ width: 40px;
+}
+
+.rst_tree {
+ overflow: auto;
+}
+
+.rst__rowLabel {
+ padding-right: 5px;
+}
+
+.closeIcon:hover {
+ cursor: pointer;
+}
+
+/* Additional styling for ReacType X */
+.componentDefault {
+ width: auto;
+ border: 2px solid gray;
+ min-height: 150px;
+ padding: 10px;
+ background-color: rgb(213, 80, 164);
+ opacity: 0.75;
+}
+
+.inputName {
+ margin-bottom: 35px;
+ text-align: center;
+}
+
+.oauth-bttn:hover {
+ background-color: #4e76c7;
+}
+
+.text-editor .bttn {
+ position: absolute;
+ bottom: 90px;
+ right: 30px;
+ z-index: 999;
+}
+
+.cssSaveBtn {
+ position: absolute;
+ bottom: 30px;
+ right: 30px;
+ z-index: 999;
+ background-color: #0671e3;
+ box-shadow: none;
+ padding: 32px;
+}
+
+.useState-btn {
+ color: rgb(241, 240, 240);
+ background-color: #2d313a;
+ border: 1px solid #2d313a;
+ border-radius: 10px;
+ box-shadow: '2px 2px 2px #1a1a1a';
+ font-family: Arial, Helvetica, sans-serif;
+ font-size: 15px;
+ padding: 12px;
+ margin-left: 20px;
+}
+
+.useState-position {
+ display: flex;
+ flex-direction: column;
+ position: fixed;
+ align-items: center;
+ top: 30%;
+ left: 30%;
+}
+
+.useState-header {
+ font-size: 35px;
+ background-color: #0671e3;
+ color: rgb(241, 240, 240);
+ width: 600px;
+ border: 3px;
+ border-style: solid;
+ border-color: #0671e3;
+ font-family: 'Open Sans', sans-serif;
+ border-radius: 15px 15px 0px 0px;
+}
+
+.useState-dropdown {
+ align-self: flex-start;
+}
+
+.useState-window {
+ width: 600px;
+ height: 400px;
+ resize: none;
+ white-space: pre-line;
+ font-size: 18px;
+ border: 2px;
+ border-style: solid;
+ border-color: black;
+ border-radius: 0px 0px 15px 15px;
+ font-family: Arial, Helvetica, sans-serif;
+ background-color: rgb(241, 240, 240);
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+}
+
+li {
+ color: black;
+}
+
+.bi {
+ margin-right: 0.5rem;
+ text-transform: none;
+}
+
+.blue-accent-text {
+ color: #2997ff;
+}
+
+a.nav_link {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ color: rgba(0, 0, 0, 0.87);
+ cursor: pointer;
+ text-decoration: none;
+ margin-top: 1rem;
+}
+
+a.nav_link:hover {
+ color: #3ec1ac;
+ text-decoration: underline;
+}
+
+.other-options {
+ margin-bottom: 1r;
+ text-transform: none;
+ size: 1rem;
+ color: white;
+ transition: 300ms;
+}
+
+.other-options:hover {
+ color: #aaaaaa;
+ cursor: pointer;
+}
diff --git a/app/src/redux/MUITypes.ts b/app/src/redux/MUITypes.ts
new file mode 100644
index 00000000..6d31525f
--- /dev/null
+++ b/app/src/redux/MUITypes.ts
@@ -0,0 +1,147 @@
+import { MUIType } from '../interfaces/Interfaces';
+import { TroubleshootSharp } from '@mui/icons-material';
+
+const MUITypes: MUIType[] = [
+ {
+ id: 21,
+ tag: 'mui button',
+ name: 'Button',
+ style: {},
+ placeHolderShort: 'mui button',
+ placeHolderLong: 'Material UI Button Component',
+ icon: 'EditAttributes',
+ framework: 'reactClassic',
+ nestable: true,
+ imports: ["import Button from '@mui/material/Button'"],
+ props: [
+ 'children',
+ 'classes',
+ 'color',
+ 'component',
+ 'disabled',
+ 'disableElevation',
+ 'disableFocusRipple',
+ 'disableRipple',
+ 'endIcon',
+ 'fullWidth',
+ 'href',
+ 'size',
+ 'startIcon',
+ 'sx',
+ 'variant'
+ ]
+ },
+ // do not move this separator element out of index 1 in this array
+ // in componentReducer.ts, separator is referenced as 'initialState.HTMLTypes[1]'
+ {
+ id: 1000,
+ tag: 'separator',
+ name: 'separator',
+ style: { border: 'none' },
+ placeHolderShort: '',
+ placeHolderLong: '',
+ icon: null,
+ framework: '',
+ nestable: true,
+ imports: [],
+ props: []
+ },
+ {
+ id: 31,
+ tag: 'textfield',
+ name: 'TextField',
+ style: {},
+ placeHolderShort: 'textfield',
+ placeHolderLong: 'Material UI TextField Component',
+ icon: 'Input',
+ framework: 'reactClassic',
+ nestable: false,
+ imports: [
+ "import Box from '@mui/material/Box'",
+ "import TextField from '@mui/material/TextField'"
+ ],
+ props: [
+ 'autoComplete',
+ 'autoFocus',
+ 'classes',
+ 'color',
+ 'defaultValue',
+ 'disabled',
+ 'error',
+ 'FormHelperTextProps',
+ 'fullWidth',
+ 'helperText',
+ 'id',
+ 'InputLabelProps',
+ 'inputProps',
+ 'InputProps',
+ 'inputRef',
+ 'label',
+ 'margin',
+ 'maxRows',
+ 'minRows',
+ 'multiline',
+ 'name',
+ 'onChange',
+ 'placeholder',
+ 'required',
+ 'rows',
+ 'select',
+ 'SelectProps',
+ 'size',
+ 'sx',
+ 'type',
+ 'value',
+ 'variant'
+ ]
+ },
+ {
+ id: 41,
+ tag: 'card',
+ name: 'Card',
+ style: {},
+ placeHolderShort: 'card',
+ placeHolderLong: 'Material UI Card Component',
+ icon: 'CardTravel',
+ framework: 'reactClassic',
+ nestable: true,
+ imports: [
+ "import Box from '@mui/material/Box'",
+ "import Card from '@mui/material/Card'",
+ "import CardActions from '@mui/material/CardActions'",
+ "import CardContent from '@mui/material/CardContent'",
+ "import Button from '@mui/material/Button'",
+ "import Typography from '@mui/material/Typography'"
+ ],
+ props: ['children', 'classes', 'raised', 'sx']
+ },
+ {
+ id: 51,
+ tag: 'typography',
+ name: 'Typography',
+ style: {},
+ placeHolderShort: 'typography',
+ placeHolderLong: 'Material UI Typography Component',
+ icon: 'TextFields',
+ framework: 'reactClassic',
+ nestable: true,
+ imports: [
+ "import Box from '@mui/material/Box'",
+ "import Typography from '@mui/material/Typography'"
+ ],
+ props: [
+ 'align',
+ 'children',
+ 'classes',
+ 'component',
+ 'gutterBottom',
+ 'noWrap',
+ 'paragraph',
+ 'sx',
+ 'variant',
+ 'variantMapping'
+ ]
+ }
+];
+
+export default MUITypes;
diff --git a/app/src/redux/reducers/slice/appStateSlice.ts b/app/src/redux/reducers/slice/appStateSlice.ts
index 7333d280..18bf5f3c 100644
--- a/app/src/redux/reducers/slice/appStateSlice.ts
+++ b/app/src/redux/reducers/slice/appStateSlice.ts
@@ -1,1368 +1,1396 @@
-// Main slice for all the component state.///
-import { createSlice } from '@reduxjs/toolkit';
-// Import Interfaces for State, and HTML Types
-import {
- State,
- Component,
- ChildElement,
- HTMLType
-} from '../../../interfaces/Interfaces';
-import HTMLTypes from '../../HTMLTypes';
-import generateCode from '../../../helperFunctions/generateCode';
-import manageSeparators from '../../../helperFunctions/manageSeparators';
-
-export const initialState: State = {
- name: '',
- _id: '',
- forked: false,
- published: false,
- isLoggedIn: false,
- components: [
- {
- id: 1,
- name: 'App',
- style: {},
- code: 'Drag in a component or HTML element into the canvas!
',
- children: [],
- isPage: true,
- past: [],
- future: [],
- stateProps: [],
- useStateCodes: [], // array of strings for each useState codes
- events: {}, // Add the missing 'events' property
- passedInProps: [] // Add the missing 'passedInProps' property
- }
- ],
- projectType: 'Classic React',
- rootComponents: [1],
- canvasFocus: { componentId: 1, childId: null },
- nextComponentId: 2,
- nextChildId: 1,
- nextTopSeparatorId: 1000,
- HTMLTypes: HTMLTypes, // left as is for now
- tailwind: false,
- stylesheet: '',
- codePreview: false,
- screenshotTrigger: false,
- customElementId: 10001
-};
-
-let separator = initialState.HTMLTypes[1];
-
-const findComponent = (components: Component[], componentId: number) => {
- return components.find((elem) => elem.id === componentId);
-};
-
-// Finds a parent
-// returns object with parent object and index value of child
-const findParent = (component: Component, childId: number) => {
- // using a breadth first search to search through instance tree
- // We're going to keep track of the nodes we need to search through with an Array
- // Initialize this array with the top level node
- const nodeArr: (Component | ChildElement)[] = [component];
- // iterate through each node in the array as long as there are elements in the array
- while (nodeArr.length > 0) {
- // shift off the first value and assign to an element
- const currentNode = nodeArr.shift();
- // try to find id of childNode in children
- if (currentNode?.children) {
- if (currentNode.name !== 'input' && currentNode.name !== 'img') {
- for (let i = 0; i <= currentNode.children.length - 1; i++) {
- // if match is found return object with both the parent and the index value of the child
- if (currentNode.children[i].childId === childId) {
- return { directParent: currentNode, childIndexValue: i };
- }
- }
- // if child node isn't found add each of the current node's children to the search array
- currentNode.children.forEach((node: ChildElement) =>
- nodeArr.push(node)
- );
- }
- }
- }
- // if no search is found return -1
- return { directParent: null, childIndexValue: null };
-};
-
-// determine if there's a child of a given type in a component
-const childTypeExists = (
- type: string,
- typeId: number,
- component: Component
-) => {
- const nodeArr = [...component.children];
- // breadth-first search through component tree to see if a child exists
- while (nodeArr.length > 0) {
- // shift off the first value and assign to an element
- const currentNode = nodeArr.shift();
- if (currentNode?.children) {
- if (currentNode.type === type && currentNode.typeId === typeId)
- return true;
- // if child node isn't found add each of the current node's children to the search array
- currentNode.children.forEach((node) => nodeArr.push(node));
- }
- }
- // if no match is found return false
- return false;
-};
-// find child in component and return child object
-const findChild = (component: Component, childId: number) => {
- if (childId === null) return component;
- const nodeArr = [...component.children];
- // breadth first search through component tree to see if a child exists
- while (nodeArr.length > 0) {
- // shift off the first value and assign to an element
- const currentNode = nodeArr.shift();
- if (currentNode?.children) {
- if (currentNode.childId === childId) return currentNode;
- // if child node isn't found add each of the current node's children to the search array
- if (currentNode.name !== 'input' && currentNode.name !== 'img')
- currentNode.children.forEach((node) => nodeArr.push(node));
- }
- }
- // if no match is found return false
- return;
-};
-// update all ids and typeIds to match one another
-const updateAllIds = (comp: Component[] | ChildElement[]) => {
- // put components' names and ids into an obj
- const obj = { spr: 1000, others: 1 };
- // for each of the components, if it has children, iterate through that children array
- comp.forEach((el: Component | ChildElement) => {
- if (el.children.length > 0) {
- for (let i = 0; i < el.children.length; i++) {
- // update each child's childId
- if (el.children[i].name === 'separator') {
- el.children[i].childId = obj['spr']++;
- } else {
- el.children[i].childId = obj['others']++;
- }
- // // if the child's name and id exists in the object
- // recursively call the updateAllIds function on the child's children array if
- // the child's children array is greater than 0
- if (el.children[i].children.length > 0) {
- updateAllIds(el.children[i].children);
- }
- }
- }
- });
-};
-const updateIds = (components: Component[]) => {
- // component IDs should be array index + 1
- components.forEach((comp, i) => (comp.id = i + 1));
- updateAllIds(components);
- // create key-value pairs of component names and corresponding IDs
- const componentIds = {};
- components.forEach((component) => {
- if (!component.isPage) componentIds[component.name] = component.id;
- });
- // assign correct ID to components that are children inside of remaining pages
- components.forEach((page) => {
- if (page.isPage) {
- page.children.forEach((child) => {
- if (child.type === 'Component') child.typeId = componentIds[child.name];
- });
- }
- });
- return components;
-};
-// updated compoment updateRoots with TS number type implemented
-const updateRoots = (components: Component[]): number[] => {
- const roots: number[] = [];
- components.forEach((comp) => {
- if (comp.isPage) roots.push(comp.id);
- });
- return roots;
-};
-// updated state property to state from object
-const deleteById = (id: number, name: string, state: State): Component[] => {
- // name of the component we want to delete
- const checkChildren = (child: Component[] | ChildElement[]) => {
- // for each of the components in the passed in components array, if the child
- // component has a children array, iterate through the array of children
- child.forEach((el: Component | ChildElement) => {
- if (el.children.length) {
- const arr: ChildElement[] = [];
- for (let i = 0; i < el.children.length; i++) {
- // check to see if the name variable doesn't match the name of the child
- if (el.children[i].name !== name) {
- // if so, push into the new array the child component
- arr.push(el.children[i]);
- }
- }
- // set the children array to be the new array
- el.children = arr;
- // recursively call the checkChildren function with the updated children array
- checkChildren(el.children);
- }
- });
- };
- // creating a copy of the components array
- const copyComp = [...state.components];
- if (copyComp.length) {
- checkChildren(copyComp);
- }
- const filteredArr = [...copyComp].filter((comp) => comp.id != id);
- return updateIds(filteredArr);
-};
-
-const updateUseStateCodes = (currentComponent: Component | ChildElement) => {
- // array of snippets of state prop codes
- const localStateCode: string[] = []; // avoid never by assigning it to string
- currentComponent.stateProps
- .filter((n, i) => i % 2 === 0)
- .forEach((stateProp) => {
- const useStateCode = `const [${stateProp.key}, set${
- stateProp.key.charAt(0).toUpperCase() + stateProp.key.slice(1)
- }] = useState<${stateProp.type} | undefined>(${JSON.stringify(
- stateProp.value
- )})`;
- localStateCode.push(useStateCode);
- });
- if (currentComponent.name !== 'App' && currentComponent.name !== 'Index') {
- currentComponent.passedInProps.forEach((passedInProp) => {
- const prop = `const ${passedInProp.key} = props.${passedInProp.key}`;
- localStateCode.push(prop);
- });
- }
- // store localStateCodes in global state context
- return localStateCode;
-};
-
-// Creates new slice for components with applicable reducers
-const appStateSlice = createSlice({
- name: 'appState',
- initialState,
- reducers: {
- addComponent: (state, action) => {
- if (
- typeof action.payload.componentName !== 'string' ||
- action.payload.componentName === ''
- ) {
- return;
- }
-
- const newComponent = {
- id: state.components.length + 1,
- name: action.payload.componentName,
- nextChildId: 1,
- style: {},
- attributes: {},
- events: {},
- code: '',
- children: [],
- isPage: action.payload.root,
- past: [],
- future: [],
- stateProps: [],
- useStateCodes: [],
- passedInProps: []
- };
- state.components.push(newComponent);
- // functionality if the new component will become the root component
- if (action.payload.root) state.rootComponents.push(newComponent.id);
- // updates the focus to the new component, which redirects to the new blank canvas of said new component
-
- // change canvas focus to just created component
-
- const nextComponentId = state.nextComponentId + 1;
- newComponent.code = generateCode(
- state.components,
- newComponent.id,
- [...state.rootComponents],
- state.projectType,
- state.HTMLTypes,
- state.tailwind,
- action.payload.contextParam
- );
-
- state.nextComponentId = nextComponentId;
- },
-
- addChild: (state, action) => {
- let parentComponentId: number;
- const {
- type,
- typeId,
- childId
- }: { type: string; typeId: number; childId: any } = action.payload;
-
- // determine the parent component id
- if (action.payload.copyId) {
- parentComponentId = action.payload.copyId;
- } else {
- parentComponentId = state.canvasFocus.componentId;
- }
-
- // make a copy of the components from state
- const components = [...state.components];
-
- // find the parent component
- const parentComponent = findComponent(components, parentComponentId);
-
- // if type is 'Component', loop through components to find componentName and componentChildren
- let componentName: string = '';
- let componentChildren: ChildElement[] = [];
- if (type === 'Component') {
- components.forEach((comp) => {
- if (comp.id === typeId) {
- componentName = comp.name;
- componentChildren = comp.children;
- }
- });
- }
- if (type === 'Component') {
- const originalComponent = findComponent(state.components, typeId);
- if (originalComponent) {
- if (
- childTypeExists('Component', parentComponentId, originalComponent)
- )
- return state;
- }
- }
-
- // create a new name based on the type of element
- let newName = state.HTMLTypes.reduce((name, el) => {
- if (typeId === el.id) {
- name = type === 'Component' ? componentName : el.tag;
- }
- return name;
- }, '');
-
- if (type === 'Route Link') {
- components.find((comp) => {
- if (comp.id === typeId) {
- newName = comp.name;
- return;
- }
- });
- }
-
- const newChild: ChildElement = {
- type,
- typeId,
- name: newName,
- childId: state.nextChildId,
- style: {},
- attributes: {},
- events: {},
- children: componentChildren, // work in progress possible solution // children: componentChildren as ChildElement[],
- stateProps: [], //legacy pd: added stateprops and passedinprops
- passedInProps: []
- };
-
- // added missing properties
- const topSeparator: ChildElement = {
- type: 'HTML Element',
- typeId: separator.id,
- name: 'separator',
- childId: state.nextTopSeparatorId,
- style: separator.style,
- attributes: {},
- events: {}, // Added
- children: [],
- stateProps: [], // Added
- passedInProps: [] // Added
- };
- // if the childId is null, this signifies that we are adding a child to the top-level component rather than another child element
- // we also add a separator before any new child
- // if the newChild Element is an input or img type, delete the children key/value pair
- // if (newChild.name === 'input' && newChild.name === 'img')
- // delete newChild.children;
- let directParent: HTMLElement | any;
- if (childId === null) {
- if (parentComponent) {
- parentComponent.children.push(topSeparator);
- parentComponent.children.push(newChild);
- }
- }
- // if there is a childId (childId here references the direct parent of the new child) find that child and add new child to its children array
- else {
- if (parentComponent) {
- directParent = findChild(parentComponent, childId);
- //disable nesting a component inside a HTML element
- if (directParent.type === 'HTML Element' && type === 'HTML Element') {
- directParent.children.push(topSeparator);
- directParent.children.push(newChild);
- } else {
- return { ...state };
- }
- }
- }
- // update canvasFocus to the new child
- const canvasFocus = {
- ...state.canvasFocus,
- componentId: state.canvasFocus.componentId,
- childId: newChild.childId
- };
-
- const nextChildId = state.nextChildId + 1;
- let nextTopSeparatorId = state.nextTopSeparatorId + 1;
- let addChildArray = components[canvasFocus.componentId - 1].children;
- // merge separator needs interface and type
- addChildArray = manageSeparators.mergeSeparator(addChildArray, 1);
- if (directParent && directParent.name === 'separator')
- nextTopSeparatorId = manageSeparators.handleSeparators(
- // handle separator needs interface and type
- addChildArray,
- 'add'
- );
-
- components[canvasFocus.componentId - 1].children = addChildArray;
-
- // generate code
- if (parentComponent) {
- parentComponent.code = generateCode(
- components,
- parentComponentId,
- [...state.rootComponents],
- state.projectType,
- state.HTMLTypes,
- state.tailwind,
- action.payload.contextParam
- );
- }
-
- // update the state with the new values
- state.components = components;
- state.nextChildId = nextChildId;
- state.canvasFocus = canvasFocus;
- state.nextTopSeparatorId = nextTopSeparatorId;
- },
-
- changeTailwind: (state, action) => {
- // return { ...state, tailwind: action.payload }
- state.tailwind = action.payload;
- },
- changeFocus: (state, action) => {
- const { componentId, childId } = action.payload;
- // makes separators not selectable
- if (childId < 1000) {
- //update componentId and childId in the state
- state.canvasFocus = { ...state.canvasFocus, componentId, childId };
- //makes it so the code preview will update when clicking on a new component
- state.components = state.components.map((element) => {
- return Object.assign({}, element);
- });
- }
- },
-
- updateStateUsed: (state, action) => {
- const { stateUsedObj } = action.payload;
- const components = [...state.components];
- const component = findComponent(
- components,
- state.canvasFocus.componentId
- );
- if (component) {
- if (state.canvasFocus.childId !== null) {
- const targetChild = findChild(component, state.canvasFocus.childId);
- if (targetChild) {
- targetChild.stateUsed = stateUsedObj;
- component.code = generateCode(
- components,
- state.canvasFocus.componentId,
- [...state.rootComponents],
- state.projectType,
- state.HTMLTypes,
- state.tailwind,
- action.payload.contextParam
- );
- state.components = components;
- }
- }
- }
- },
-
- updateUseContext: (state, action) => {
- const { useContextObj } = action.payload;
- const components = [...state.components];
- const component = findComponent(
- components,
- state.canvasFocus.componentId
- );
- if (component) {
- component.useContext = useContextObj;
- component.code = generateCode(
- components,
- state.canvasFocus.componentId,
- [...state.rootComponents],
- state.projectType,
- state.HTMLTypes,
- state.tailwind,
- action.payload.contextParam
- );
- state.components = components;
- }
- },
-
- resetAllState: (state) => {
- if (state.isLoggedIn) {
- Object.assign(state, initialState);
- state.isLoggedIn = true;
- return;
- }
- Object.assign(state, initialState);
- },
- changePosition: (state, action) => {
- const { currentChildId, newParentChildId } = action.payload;
- // if the currentChild Id is the same as the newParentId (i.e. a component is trying to drop itself into itself), don't update state
- if (currentChildId === newParentChildId) return state;
- // find the current component in focus
- let components = [...state.components];
- const component = findComponent(
- components,
- state.canvasFocus.componentId
- );
- // find the moved element's former parent
- // delete the element from it's former parent's children array
- if (component) {
- const { directParent, childIndexValue } = findParent(
- component,
- currentChildId
- );
- // BREAKING HERE during manipulation of positions. Sometimes get a null value when manipulating positions
- // Only run if the directParent exists
- if (directParent) {
- if (directParent.children) {
- const child = { ...directParent.children[childIndexValue] };
- directParent.children.splice(childIndexValue, 1);
- // if the childId is null, this signifies that we are adding a child to the top level component rather than another child element
- if (newParentChildId === null) {
- component.children.push(child);
- }
- // if there is a childId (childId here references the direct parent of the new child) find that child and a new child to its children array
- else {
- const directParent = findChild(component, newParentChildId);
- if (directParent?.children) {
- directParent.children.push(child);
- }
- }
- }
- }
- let nextTopSeparatorId = state.nextTopSeparatorId;
- components[state.canvasFocus.componentId - 1].children =
- manageSeparators.mergeSeparator(
- components[state.canvasFocus.componentId - 1].children,
- 0
- );
- nextTopSeparatorId = manageSeparators.handleSeparators(
- components[state.canvasFocus.componentId - 1].children,
- 'change position'
- );
- component.code = generateCode(
- components,
- state.canvasFocus.componentId,
- [...state.rootComponents],
- state.projectType,
- state.HTMLTypes,
- state.tailwind,
- action.payload.contextParam
- );
- state.components = components;
- state.nextTopSeparatorId = nextTopSeparatorId;
- }
- },
-
- updateCss: (state, action) => {
- const { style } = action.payload;
- const components = [...state.components];
- const component = findComponent(
- components,
- state.canvasFocus.componentId
- );
- // closed if statement at the end of the block
- if (component && state.canvasFocus.childId) {
- const targetChild = findChild(component, state.canvasFocus.childId);
- if (targetChild) {
- targetChild.style = style;
- component.code = generateCode(
- components,
- state.canvasFocus.componentId,
- [...state.rootComponents],
- state.projectType,
- state.HTMLTypes,
- state.tailwind,
- action.payload.contextParam
- );
- state.components = components;
- }
- }
- },
-
- updateAttributes: (state, action) => {
- const { attributes } = action.payload;
-
- const components = [...state.components];
- const component = findComponent(
- components,
- state.canvasFocus.componentId
- );
- // closed if statement at the end of the block
- if (component && state.canvasFocus.childId) {
- const targetChild = findChild(component, state.canvasFocus.childId);
- if (targetChild) {
- targetChild.attributes = attributes;
-
- component.code = generateCode(
- components,
- state.canvasFocus.componentId,
- [...state.rootComponents],
- state.projectType,
- state.HTMLTypes,
- state.tailwind,
- action.payload.contextParam
- );
- state.components = components;
- }
- }
- },
-
- updateEvents: (state, action) => {
- const { events } = action.payload;
- if (JSON.stringify(events) === '{}') return state;
- const components = [...state.components];
- const component = findComponent(
- components,
- state.canvasFocus.componentId
- );
- if (component && state.canvasFocus.childId) {
- const targetChild = findChild(component, state.canvasFocus.childId);
- const event = Object.keys(events)[0];
- const funcName = events[event];
- if (targetChild) {
- targetChild.events[event] = funcName;
-
- component.code = generateCode(
- components,
- state.canvasFocus.componentId,
- [...state.rootComponents],
- state.projectType,
- state.HTMLTypes,
- state.tailwind,
- action.payload.contextParam
- );
- state.components = components;
- }
- }
- },
-
- deleteEventAction: (state, action) => {
- const { event } = action.payload;
- const components = [...state.components];
- const component = findComponent(
- components,
- state.canvasFocus.componentId
- );
- if (component && state.canvasFocus.childId) {
- const targetChild = findChild(component, state.canvasFocus.childId);
- if (targetChild) {
- delete targetChild.events[event];
-
- component.code = generateCode(
- components,
- state.canvasFocus.componentId,
- [...state.rootComponents],
- state.projectType,
- state.HTMLTypes,
- state.tailwind,
- action.payload.contextParam
- );
- state.components = components;
- }
- }
- },
-
- deletePage: (state, action) => {
- const id: number = state.canvasFocus.componentId;
- const name: string = state.components[id - 1].name;
- const components: Component[] = deleteById(id, name, state);
- // rebuild rootComponents with correct page IDs
- const rootComponents = updateRoots(components);
- const canvasFocus = { componentId: 1, childId: null };
- state.rootComponents = rootComponents;
- state.components = components;
- state.canvasFocus = canvasFocus;
- },
-
- deleteReusableComponent: (state, action) => {
- const id: number = state.canvasFocus.componentId;
- const name: string = state.components[id - 1].name;
- // updated list of components after deleting a component
- const components: Component[] = deleteById(id, name, state);
- const rootComponents: number[] = updateRoots(components);
- // iterate over the length of the components array
- for (let i = 0; i < components.length; i++) {
- //if the component uses context from component being deleted
- if (components[i].useContext && components[i].useContext[id]) {
- // iterate over children to see where it is being used, then reset that compText/compLink/useState
- for (let child of components[i].children) {
- if (child.stateUsed) {
- if (child.stateUsed.compTextProviderId === id) {
- child.attributes.compText = '';
- delete child.stateUsed.compText;
- delete child.stateUsed.compTextProviderId;
- delete child.stateUsed.compTextPropsId;
- }
- if (child.stateUsed.compLinkProviderId === id) {
- child.attributes.compLink = '';
- delete child.stateUsed.compLink;
- delete child.stateUsed.compLinkProviderId;
- delete child.stateUsed.compLinkPropsId;
- }
- }
- }
- delete components[i].useContext[id];
- }
-
- // for each component's code, run the generateCode function to
- // update the code preview on the app
- components[i].code = generateCode(
- components,
- components[i].id,
- rootComponents,
- state.projectType,
- state.HTMLTypes,
- state.tailwind,
- action.payload.contextParam
- );
- }
- const canvasFocus = { componentId: 1, childId: null };
-
- state.rootComponents = rootComponents;
- state.components = components;
- state.canvasFocus = canvasFocus;
- state.nextComponentId = id;
- },
- setProjectName: (state, action) => {
- state.name = action.payload;
- },
- changeProjectType: (state, action) => {
- // when a project type is changed, both change the project type in state and also regenerate the code for each component
- const { projectType } = action.payload;
-
- const components = [...state.components];
- // also update the name of the root component of the application to fit classic React and next.js/gatsby conventions
- if (projectType === 'Next.js' || projectType === 'Gatsby.js')
- components[0]['name'] = 'index';
- else components[0]['name'] = 'App';
- components.forEach((component) => {
- component.code = generateCode(
- components,
- component.id,
- [...state.rootComponents],
- projectType,
- state.HTMLTypes,
- state.tailwind,
- action.payload.contextParam
- );
- });
- state.components = components;
- state.projectType = projectType;
- },
-
- resetState: (state, action) => {
- const nextChildId = 1;
- const nextTopSeparatorId = 1000;
- const rootComponents = [1];
- const nextComponentId = 2;
- const canvasFocus = {
- ...state.canvasFocus,
- componentId: 1,
- childId: null
- };
- const rootComponent = {
- ...state.components[0],
- code: 'Drag in a component or HTML element into the canvas!
',
- children: [],
- style: {}
- };
- const components = [rootComponent];
-
- const stylesheet = '';
- const resetHTMLTypes = HTMLTypes;
-
- return {
- ...state,
- nextChildId,
- nextTopSeparatorId,
- rootComponents,
- nextComponentId,
- components,
- canvasFocus,
- stylesheet,
- HTMLTypes: resetHTMLTypes
- };
- },
- updateProjectName: (state, action) => {
- const projectName = action.payload;
- state.name = projectName;
- },
- updateProjectId: (state, action) => {
- const projectId = action.payload; //updates the slice with new _id
- state._id = projectId;
- },
- updateProjectPublished: (state, action) => {
- const projectPublished = action.payload;
- state.published = projectPublished;
- },
- deleteElement: (state, action) => {
- let name: string = '';
- const HTMLTypes: HTMLType[] = [...state.HTMLTypes].filter((el) => {
- if (el.id === action.payload.id) {
- name = el.tag;
- }
- return el.id !== action.payload.id;
- });
- const components: Component[] = deleteById(
- action.payload.id,
- name,
- state
- );
- const rootComponents: number[] = updateRoots(components);
- const canvasFocus = { ...state.canvasFocus, childId: null };
- components.forEach((el, i) => {
- el.code = generateCode(
- components,
- components[i].id,
- rootComponents,
- state.projectType,
- state.HTMLTypes,
- state.tailwind,
- action.payload.contextParam
- );
- });
-
- state.canvasFocus = canvasFocus;
- state.HTMLTypes = HTMLTypes;
- },
-
- deleteChild: (state, action) => {
- // if in-focus instance is a top-level component and not a child, don't delete anything
- if (!state.canvasFocus.childId) return state;
- // find the current component in focus
- const components = [...state.components];
- const component = findComponent(
- components,
- state.canvasFocus.componentId
- );
- // find the moved element's former parent
- const { directParent, childIndexValue } = findParent(
- component,
- state.canvasFocus.childId
- );
-
- // ------------------------------------------- ALSO added code below -------------------------------------------
-
- let canvasFocus = { ...state.canvasFocus, childId: null }; // ADDED to avoid null error
- let nextTopSeparatorId = 1000; // ADDED to avoid null error
- const childIdDeleteClicked = action.payload.id; // ADDED to ensure no cross-element deletion possible
-
- // delete the element from its former parent's children array, subject to below conditional to avoid null error
- if (
- directParent &&
- (state.canvasFocus.childId === childIdDeleteClicked ||
- JSON.stringify(action.payload.id) === '{}') // Ensuring deletion works for mouseclick OR using delete key, from 2 different dispatch sources
- ) {
- if (directParent.children) {
- directParent.children.splice(childIndexValue, 1);
- let nextTopSeparatorId = manageSeparators.handleSeparators(
- components[canvasFocus.componentId - 1].children,
- 'delete'
- );
- }
- }
-
- // ------------------------------------------- ALSO added code above -------------------------------------------
- component.code = generateCode(
- components,
- state.canvasFocus.componentId,
- [...state.rootComponents],
- state.projectType,
- state.HTMLTypes,
- state.tailwind,
- action.payload.contextParam
- );
- state.components = components;
- state.canvasFocus = canvasFocus;
- state.nextTopSeparatorId = nextTopSeparatorId;
- },
- setInitialState: (state, action) => {
- // set the canvas focus to be the first component
- const canvasFocus = {
- ...action.payload.canvasFocus,
- componentId: 1,
- childId: null
- };
- state.canvasFocus = canvasFocus;
- },
- //deleted 'convertToJSX' function, which threw errors upon opening
- openProject: (state, action) => {
- // returning the action.payload is a Redux shortcut that updates the entire app state at the same time
- return action.payload;
- },
- addElement: (state, action) => {
- const HTMLTypes = [...state.HTMLTypes];
- HTMLTypes.push(action.payload);
- state.HTMLTypes = HTMLTypes;
- state.customElementId += 1;
- },
- //Undo & Redo functions are not working properly. Redo & Undo target the last component rather than last added HTML Element.
- undo: (state, action) => {
- const focusIndex = state.canvasFocus.componentId - 1;
- // if the past array is empty, return state
- if (state.components[focusIndex].past.length <= 1) {
- return { ...state };
- }
- // the children array of the focused component will equal the last element of the past array
- state.components[focusIndex].children =
- state.components[focusIndex].past[
- state.components[focusIndex].past.length - 1
- ];
- // the last element of the past array gets popped off
- const poppedEl: Component = state.components[focusIndex].past.pop();
- // the last element of the past array gets popped off and pushed into the future array
- state.components[focusIndex].future.push(poppedEl);
- //generate code for the Code Preview
- state.components.forEach((el, i) => {
- el.code = generateCode(
- state.components,
- state.components[i].id,
- state.rootComponents,
- state.projectType,
- state.HTMLTypes,
- state.tailwind,
- action.payload.contextParam
- );
- });
-
- state = state;
- },
- redo: (state, action) => {
- const focusIndex = state.canvasFocus.componentId - 1;
- //if future array is empty, return state
- if (state.components[focusIndex].future.length === 0) return { ...state };
- //the children array of the focused component will equal the last element of the future array
- state.components[focusIndex].children =
- state.components[focusIndex].future[
- state.components[focusIndex].future.length - 1
- ];
- //the last element of the future array gets pushed into the past
- const poppedEl: Component = state.components[focusIndex].future.pop();
- //the last element of the future array gets popped out
- state.components[focusIndex].past.push(poppedEl);
- // generate code for the Code Preview
- state.components.forEach((el, i) => {
- el.code = generateCode(
- state.components,
- state.components[i].id,
- state.rootComponents,
- state.projectType,
- state.HTMLTypes,
- state.tailwind,
- action.payload.contextParam
- );
- });
- state = state;
- },
- addState: (state, action) => {
- // find the current component in focus
- const components = [...state.components];
- const currComponent = findComponent(
- components,
- state.canvasFocus.componentId
- );
- //will add update StateProps to current components' array
- currComponent.stateProps.push(action.payload.newState);
- currComponent.useStateCodes = updateUseStateCodes(currComponent);
- currComponent.stateProps.push(action.payload.setNewState);
- currComponent.code = generateCode(
- components,
- state.canvasFocus.componentId,
- [...state.rootComponents],
- state.projectType,
- state.HTMLTypes,
- state.tailwind,
- action.payload.contextParam
- );
- state.components = components;
- },
- addPassedInProps: (state, action) => {
- //When props are passed from a parent to a child in the State Manager tab, it will update the components available passedinprops
- // find the current component in focus
- const components = [...state.components];
- const currComponent = findComponent(
- components,
- state.canvasFocus.componentId
- );
- //prevents passing in props more than one time to the current component
- for (let i = 0; i < currComponent.passedInProps.length; i++) {
- let curr = currComponent.passedInProps[i];
- if (curr.id === action.payload.passedInProps.id) {
- return { ...state, components };
- }
- }
-
- //find the parent for deleting instances of where the parent is passing props to children
- let parent: Component;
- for (let i = 0; i < components.length; i++) {
- let currComponent = components[i];
- for (let j = 0; j < currComponent.children.length; j++) {
- let currChild = currComponent.children[j];
- if (currChild.typeId === state.canvasFocus.componentId) {
- parent = currComponent;
- }
- }
- }
-
- //search for whether the child exists in the parent's children array
- //if so update the passed in props child element with the updates passed in props
- parent.children.forEach((child) => {
- if (child.name === currComponent.name) {
- child.passedInProps.push(action.payload.passedInProps);
- }
- });
-
- // check each components passedInProps property and updating there as well.
- currComponent.passedInProps.push(action.payload.passedInProps);
-
- //update the import codes for the current component
- currComponent.useStateCodes = updateUseStateCodes(currComponent);
- //update code preview for current component
- currComponent.code = generateCode(
- components,
- state.canvasFocus.componentId,
- [...state.rootComponents],
- state.projectType,
- state.HTMLTypes,
- state.tailwind,
- action.payload.contextParam
- );
- //update code preview for parent component (since we have added it to the children array)
- parent.code = generateCode(
- components,
- parent.id,
- [...state.rootComponents],
- state.projectType,
- state.HTMLTypes,
- state.tailwind,
- action.payload.contextParam
- );
-
- // return { ...state, components };
- state.components = components;
- },
- deletePassedInProps: (state, action) => {
- const components = [...state.components];
- let currComponent = findComponent(
- components,
- state.canvasFocus.componentId
- );
-
- //find the parent of the component that we are deleting from
- let parent: Component;
- for (let i = 0; i < components.length; i++) {
- let currComponent = components[i];
- for (let j = 0; j < currComponent.children.length; j++) {
- let currChild = currComponent.children[j];
- if (currChild.typeId === state.canvasFocus.componentId) {
- parent = currComponent;
- }
- }
- }
-
- //deletes all instances of passedInProps from the children arrays of the current Component
- const deletePassedInPropsChildren = (currComponent: Component) => {
- const innerFunc = (currChild: Component | ChildElement) => {
- // when there are no children, return up a level
- if (
- currChild.children.filter((el) => el.type === 'Component')
- .length === 0
- )
- return;
- if (currChild.children.length) {
- currChild.children
- .filter((el) => el.type === 'Component')
- .forEach((child, j) => {
- child.passedInProps.forEach((prop, k) => {
- if (prop.id === action.payload.rowId) {
- child.passedInProps.splice(k, 1);
- innerFunc(child);
- }
- });
- });
- }
- };
- //for every component we update, generate new code
- innerFunc(currComponent);
- currComponent.code = generateCode(
- components,
- currComponent.id,
- [...state.rootComponents],
- state.projectType,
- state.HTMLTypes,
- state.tailwind,
- action.payload.contextParam
- );
- };
- //delete from the components passedInProps array
- const deletePassedInProps = (myComponent: Component) => {
- if (
- myComponent.children.filter((el) => el.type === 'Component')
- .length === 0
- ) {
- if (myComponent.passedInProps.length > 0) {
- myComponent.passedInProps.forEach((prop, index) => {
- if (prop.id === action.payload.rowId) {
- myComponent.passedInProps.splice(index, 1);
- }
- });
- }
- myComponent.useStateCodes = updateUseStateCodes(myComponent);
- myComponent.code = generateCode(
- components,
- myComponent.id,
- [...state.rootComponents],
- state.projectType,
- state.HTMLTypes,
- state.tailwind,
- action.payload.contextParam
- );
- return;
- }
- myComponent.passedInProps.forEach((prop, i) => {
- if (prop.id === action.payload.rowId) {
- myComponent.passedInProps.splice(i, 1);
- myComponent.children
- .filter((el) => el.type === 'Component')
- .forEach((child, i) => {
- let next = components.find((comp) => comp.id === child.typeId);
- deletePassedInProps(next);
- });
- }
- });
- myComponent.useStateCodes = updateUseStateCodes(myComponent);
- myComponent.code = generateCode(
- components,
- myComponent.id,
- [...state.rootComponents],
- state.projectType,
- state.HTMLTypes,
- state.tailwind,
- action.payload.contextParam
- );
- };
-
- deletePassedInPropsChildren(parent);
- deletePassedInProps(currComponent);
-
- parent.code = generateCode(
- components,
- parent.id,
- [...state.rootComponents],
- state.projectType,
- state.HTMLTypes,
- state.tailwind,
- action.payload.contextParam
- );
- state.components = components;
- },
- deleteState: (state, action) => {
- const components = [...state.components];
- let currComponent = findComponent(
- components,
- state.canvasFocus.componentId
- );
- //updates the stateProps array to reflect total state initialized in component minus the selected state to be deleted
- currComponent.stateProps = action.payload.stateProps;
-
- //CHILDREN ARRAY LOOP (needed for code preview)
- //iterate through all components, starting from top, and delete ALL instances of deleted state (provided to us
- // in the passedInProps array within the children array of the component
- // using the action.payload.rowId (variable name) and action.payload.otherId (setVariable name))
- components.forEach((component) => {
- //find all instances of state within child elements and delete state
-
- component.children.forEach((child) => {
- if (child.type === 'Component') {
- for (let i = 0; i < child.passedInProps?.length; i++) {
- if (
- child.passedInProps[i]['id'] === action.payload.rowId ||
- child.passedInProps[i]['id'] === action.payload.otherId
- ) {
- child.passedInProps.splice(i, 1);
- i--;
- }
- }
- }
- });
-
- // COMPONENT LOOP (needed for tables in State Management Tab)
- //iterate through all components, starting from top, and delete ALL instances of deleted state (provided to us
- // in the passedInProps array within each component
- // using the action.payload.rowId (variable name) and action.payload.otherId (setVariable name))
- for (let i = 0; i < component.passedInProps?.length; i++) {
- if (
- component.passedInProps[i]['id'] === action.payload.rowId ||
- component.passedInProps[i]['id'] === action.payload.otherId
- ) {
- component.passedInProps.splice(i, 1);
- i--;
- }
- }
- // curr component = where you are deleting from state from, also is the canvas focus
- // curr component id = providerId
- // we then iterate through the rest of the components
- // check if a useContext if created and if the useContext contains the providerId
- // we then delete from the set, statesFromProvider, the row id, and regenerate the code
- // Ex: useContext {1: {statesFromProvider: Set, compLink, compText}, 2 : ..., 3 : ...}
- if (
- component.useContext &&
- component.useContext[state.canvasFocus.componentId]
- ) {
- component.useContext[
- state.canvasFocus.componentId
- ].statesFromProvider.delete(action.payload.rowId);
- // iterate over children to see where it is being used, then reset that compText/compLink/useState
- for (let child of component.children) {
- if (child.stateUsed) {
- if (
- child.stateUsed.compTextProviderId === currComponent.id &&
- child.stateUsed.compTextPropsId === action.payload.rowId
- ) {
- child.attributes.compText = '';
- delete child.stateUsed.compText;
- delete child.stateUsed.compTextProviderId;
- delete child.stateUsed.compTextPropsId;
- }
- if (
- child.stateUsed.compLinkProviderId === currComponent.id &&
- child.stateUsed.compLinkPropsId === action.payload.rowId
- ) {
- child.attributes.compLink = '';
- delete child.stateUsed.compLink;
- delete child.stateUsed.compLinkProviderId;
- delete child.stateUsed.compLinkPropsId;
- }
- }
- }
- }
-
- // find parent
- let parent;
- for (let i = 0; i < components.length; i++) {
- let currComponent = components[i];
- for (let j = 0; j < currComponent.children.length; j++) {
- let currChild = currComponent.children[j];
- if (currChild.typeId === component.id) {
- parent = currComponent;
- }
- }
- }
- if (parent) {
- parent.code = generateCode(
- components,
- parent.id,
- [...state.rootComponents],
- state.projectType,
- state.HTMLTypes,
- state.tailwind,
- action.payload.contextParam
- );
- }
-
- component.useStateCodes = updateUseStateCodes(component);
- component.code = generateCode(
- components,
- component.id,
- [...state.rootComponents],
- state.projectType,
- state.HTMLTypes,
- state.tailwind,
- action.payload.contextParam
- );
- });
- state.components = components;
- },
-
- toggleLoggedIn: (state, action) => {
- state.isLoggedIn = action.payload;
- },
-
- snapShotAction: (state, action) => {
- state.components[action.payload.focusIndex].past.push(
- action.payload.deepCopiedState.components[action.payload.focusIndex]
- .children
- );
- },
- allCooperativeState: (state, action) => {
- // return Object.assign({}, state, action.payload);
- // it is safe to directly assign the properties from 'action.payload' to the state in Redux Toolkit
- Object.assign(state, action.payload);
- },
- updateStylesheet: (state, action) => {
- state.stylesheet = action.payload;
- },
-
- // toggles the active code preview editor for conditional rendering
- toggleCodePreview: (state) => {
- state.codePreview = !state.codePreview;
- },
-
- // toggles the state of the screenshot trigger to allow a child component to trigger the screenshot of the main component
- toggleScreenshotTrigger: (state) => {
- state.screenshotTrigger = !state.screenshotTrigger;
- }
-
- // ,configToggle: (state) => {
- // state.config.saveFlag = !state.config.saveFlag;
- // state.config.saveTimer = !state.config.saveTimer;
- // }
- }
-});
-
-// Exports the action creator function to be used with useDispatch
-
-export const {
- addComponent,
- addChild,
- changeFocus,
- changeTailwind,
- changePosition,
- updateStateUsed,
- resetAllState,
- updateUseContext,
- updateCss,
- updateEvents,
- deleteEventAction,
- deletePage,
- deleteReusableComponent,
- setProjectName,
- changeProjectType,
- resetState,
- updateProjectName,
- updateProjectId,
- updateProjectPublished,
- deleteElement,
- updateAttributes,
- deleteChild,
- setInitialState,
- openProject,
- addElement,
- undo,
- redo,
- addState,
- addPassedInProps,
- deletePassedInProps,
- deleteState,
- toggleLoggedIn,
- //configToggle,
- snapShotAction,
- allCooperativeState,
- updateStylesheet,
- toggleCodePreview,
- toggleScreenshotTrigger
-} = appStateSlice.actions;
-
-// Exports so we can combine in rootReducer
-export default appStateSlice.reducer;
+// Main slice for all the component state.///
+import { createSlice } from '@reduxjs/toolkit';
+// Import Interfaces for State, and HTML Types
+import {
+ State,
+ Component,
+ ChildElement,
+ HTMLType,
+ MUIType
+} from '../../../interfaces/Interfaces';
+import HTMLTypes from '../../HTMLTypes';
+import MUITypes from '../../MUITypes';
+import generateCode from '../../../helperFunctions/generateCode';
+import manageSeparators from '../../../helperFunctions/manageSeparators';
+
+export const initialState: State = {
+ name: '',
+ _id: '',
+ forked: false,
+ published: false,
+ isLoggedIn: false,
+ components: [
+ {
+ id: 1,
+ name: 'App',
+ style: {},
+ code: 'Drag in a component or HTML element into the canvas!
',
+ children: [],
+ isPage: true,
+ past: [],
+ future: [],
+ stateProps: [],
+ useStateCodes: [], // array of strings for each useState codes
+ events: {}, // Add the missing 'events' property
+ passedInProps: [] // Add the missing 'passedInProps' property
+ }
+ ],
+ projectType: 'Classic React',
+ rootComponents: [1],
+ canvasFocus: { componentId: 1, childId: null },
+ nextComponentId: 2,
+ nextChildId: 1,
+ nextTopSeparatorId: 1000,
+ HTMLTypes: HTMLTypes, // left as is for now
+ MUITypes: MUITypes, // left as is for now
+ tailwind: false,
+ stylesheet: '',
+ codePreview: false,
+ screenshotTrigger: false,
+ customElementId: 10001
+};
+
+let separator = initialState.HTMLTypes[1];
+
+const findComponent = (components: Component[], componentId: number) => {
+ return components.find((elem) => elem.id === componentId);
+};
+
+// Finds a parent
+// returns object with parent object and index value of child
+const findParent = (component: Component, childId: number) => {
+ // using a breadth first search to search through instance tree
+ // We're going to keep track of the nodes we need to search through with an Array
+ // Initialize this array with the top level node
+ const nodeArr: (Component | ChildElement)[] = [component];
+ // iterate through each node in the array as long as there are elements in the array
+ while (nodeArr.length > 0) {
+ // shift off the first value and assign to an element
+ const currentNode = nodeArr.shift();
+ // try to find id of childNode in children
+ if (currentNode?.children) {
+ if (currentNode.name !== 'input' && currentNode.name !== 'img') {
+ for (let i = 0; i <= currentNode.children.length - 1; i++) {
+ // if match is found return object with both the parent and the index value of the child
+ if (currentNode.children[i].childId === childId) {
+ return { directParent: currentNode, childIndexValue: i };
+ }
+ }
+ // if child node isn't found add each of the current node's children to the search array
+ currentNode.children.forEach((node: ChildElement) =>
+ nodeArr.push(node)
+ );
+ }
+ }
+ }
+ // if no search is found return -1
+ return { directParent: null, childIndexValue: null };
+};
+
+// determine if there's a child of a given type in a component
+const childTypeExists = (
+ type: string,
+ typeId: number,
+ component: Component
+) => {
+ const nodeArr = [...component.children];
+ // breadth-first search through component tree to see if a child exists
+ while (nodeArr.length > 0) {
+ // shift off the first value and assign to an element
+ const currentNode = nodeArr.shift();
+ if (currentNode?.children) {
+ if (currentNode.type === type && currentNode.typeId === typeId)
+ return true;
+ // if child node isn't found add each of the current node's children to the search array
+ currentNode.children.forEach((node) => nodeArr.push(node));
+ }
+ }
+ // if no match is found return false
+ return false;
+};
+// find child in component and return child object
+const findChild = (component: Component, childId: number) => {
+ if (childId === null) return component;
+ const nodeArr = [...component.children];
+ // breadth first search through component tree to see if a child exists
+ while (nodeArr.length > 0) {
+ // shift off the first value and assign to an element
+ const currentNode = nodeArr.shift();
+ if (currentNode?.children) {
+ if (currentNode.childId === childId) return currentNode;
+ // if child node isn't found add each of the current node's children to the search array
+ if (currentNode.name !== 'input' && currentNode.name !== 'img')
+ currentNode.children.forEach((node) => nodeArr.push(node));
+ }
+ }
+ // if no match is found return false
+ return;
+};
+function createHTMLElement(data) {
+ return {
+ ...data,
+ isHTMLElement: true
+ };
+}
+
+// update all ids and typeIds to match one another
+const updateAllIds = (comp: Component[] | ChildElement[]) => {
+ // put components' names and ids into an obj
+ const obj = { spr: 1000, others: 1 };
+ // for each of the components, if it has children, iterate through that children array
+ comp.forEach((el: Component | ChildElement) => {
+ if (el.children.length > 0) {
+ for (let i = 0; i < el.children.length; i++) {
+ // update each child's childId
+ if (el.children[i].name === 'separator') {
+ el.children[i].childId = obj['spr']++;
+ } else {
+ el.children[i].childId = obj['others']++;
+ }
+ // // if the child's name and id exists in the object
+ // recursively call the updateAllIds function on the child's children array if
+ // the child's children array is greater than 0
+ if (el.children[i].children.length > 0) {
+ updateAllIds(el.children[i].children);
+ }
+ }
+ }
+ });
+};
+const updateIds = (components: Component[]) => {
+ // component IDs should be array index + 1
+ components.forEach((comp, i) => (comp.id = i + 1));
+ updateAllIds(components);
+ // create key-value pairs of component names and corresponding IDs
+ const componentIds = {};
+ components.forEach((component) => {
+ if (!component.isPage) componentIds[component.name] = component.id;
+ });
+ // assign correct ID to components that are children inside of remaining pages
+ components.forEach((page) => {
+ if (page.isPage) {
+ page.children.forEach((child) => {
+ if (child.type === 'Component') child.typeId = componentIds[child.name];
+ });
+ }
+ });
+ return components;
+};
+// updated compoment updateRoots with TS number type implemented
+const updateRoots = (components: Component[]): number[] => {
+ const roots: number[] = [];
+ components.forEach((comp) => {
+ if (comp.isPage) roots.push(comp.id);
+ });
+ return roots;
+};
+// updated state property to state from object
+const deleteById = (id: number, name: string, state: State): Component[] => {
+ // name of the component we want to delete
+ const checkChildren = (child: Component[] | ChildElement[]) => {
+ // for each of the components in the passed in components array, if the child
+ // component has a children array, iterate through the array of children
+ child.forEach((el: Component | ChildElement) => {
+ if (el.children.length) {
+ const arr: ChildElement[] = [];
+ for (let i = 0; i < el.children.length; i++) {
+ // check to see if the name variable doesn't match the name of the child
+ if (el.children[i].name !== name) {
+ // if so, push into the new array the child component
+ arr.push(el.children[i]);
+ }
+ }
+ // set the children array to be the new array
+ el.children = arr;
+ // recursively call the checkChildren function with the updated children array
+ checkChildren(el.children);
+ }
+ });
+ };
+ // creating a copy of the components array
+ const copyComp = [...state.components];
+ if (copyComp.length) {
+ checkChildren(copyComp);
+ }
+ const filteredArr = [...copyComp].filter((comp) => comp.id != id);
+ return updateIds(filteredArr);
+};
+
+const updateUseStateCodes = (currentComponent: Component | ChildElement) => {
+ // array of snippets of state prop codes
+ const localStateCode: string[] = []; // avoid never by assigning it to string
+ currentComponent.stateProps
+ .filter((n, i) => i % 2 === 0)
+ .forEach((stateProp) => {
+ const useStateCode = `const [${stateProp.key}, set${
+ stateProp.key.charAt(0).toUpperCase() + stateProp.key.slice(1)
+ }] = useState<${stateProp.type} | undefined>(${JSON.stringify(
+ stateProp.value
+ )})`;
+ localStateCode.push(useStateCode);
+ });
+ if (currentComponent.name !== 'App' && currentComponent.name !== 'Index') {
+ currentComponent.passedInProps.forEach((passedInProp) => {
+ const prop = `const ${passedInProp.key} = props.${passedInProp.key}`;
+ localStateCode.push(prop);
+ });
+ }
+ // store localStateCodes in global state context
+ return localStateCode;
+};
+
+// Creates new slice for components with applicable reducers
+const appStateSlice = createSlice({
+ name: 'appState',
+ initialState,
+ reducers: {
+ addComponent: (state, action) => {
+ if (
+ typeof action.payload.componentName !== 'string' ||
+ action.payload.componentName === ''
+ ) {
+ return;
+ }
+
+ const newComponent = {
+ id: state.components.length + 1,
+ name: action.payload.componentName,
+ nextChildId: 1,
+ style: {},
+ attributes: {},
+ events: {},
+ code: '',
+ children: [],
+ isPage: action.payload.root,
+ past: [],
+ future: [],
+ stateProps: [],
+ useStateCodes: [],
+ passedInProps: []
+ };
+ state.components.push(newComponent);
+ // functionality if the new component will become the root component
+ if (action.payload.root) state.rootComponents.push(newComponent.id);
+ // updates the focus to the new component, which redirects to the new blank canvas of said new component
+
+ // change canvas focus to just created component
+
+ const nextComponentId = state.nextComponentId + 1;
+ newComponent.code = generateCode(
+ state.components,
+ newComponent.id,
+ [...state.rootComponents],
+ state.projectType,
+ state.HTMLTypes,
+ state.MUITypes,
+ state.tailwind,
+ action.payload.contextParam
+ );
+
+ state.nextComponentId = nextComponentId;
+ },
+
+ addChild: (state, action) => {
+ const {
+ type,
+ typeId,
+ childId
+ }: { type: string; typeId: number; childId: any } = action.payload;
+
+ // determine the parent component id
+ const parentComponentId =
+ action.payload.copyId || state.canvasFocus.componentId;
+
+ // make a copy of the components from state
+ const components = [...state.components];
+
+ // find the parent component
+ const parentComponent = findComponent(components, parentComponentId);
+ if (!parentComponent) return state; // ensure that parent component exists
+
+ // if type is 'Component', loop through components to find componentName and componentChildren
+ let componentName: string = '';
+ let componentChildren: ChildElement[] = [];
+
+ if (type === 'Component') {
+ const originalComponent = findComponent(state.components, typeId);
+ if (originalComponent) {
+ componentName = originalComponent.name;
+ componentChildren = originalComponent.children;
+ if (
+ childTypeExists('Component', parentComponentId, originalComponent)
+ )
+ return state;
+ }
+ } else if (type === 'Route Link') {
+ const routeLinkComponent = components.find(
+ (comp) => comp.id === typeId
+ );
+ if (routeLinkComponent) {
+ componentName = routeLinkComponent.name;
+ }
+ } else if (type === 'HTML Element') {
+ componentName = state.HTMLTypes.reduce((name, el) => {
+ if (typeId === el.id) {
+ name = el.tag;
+ }
+ return name;
+ }, '');
+ } else {
+ componentName = state.MUITypes.reduce((name, el) => {
+ if (typeId === el.id) {
+ name = el.tag;
+ }
+ return name;
+ }, '');
+ }
+
+ const newChild: ChildElement = {
+ type,
+ typeId,
+ name: componentName,
+ childId: state.nextChildId,
+ style: {},
+ attributes: {},
+ events: {},
+ children: componentChildren, // work in progress possible solution // children: componentChildren as ChildElement[],
+ stateProps: [], //legacy pd: added stateprops and passedinprops
+ passedInProps: []
+ };
+
+ // added missing properties
+ const topSeparator: ChildElement = {
+ type: 'HTML Element',
+ typeId: separator.id,
+ name: 'separator',
+ childId: state.nextTopSeparatorId,
+ style: separator.style,
+ attributes: {},
+ events: {}, // Added
+ children: [],
+ stateProps: [], // Added
+ passedInProps: [] // Added
+ };
+ // if the childId is null, this signifies that we are adding a child to the top-level component rather than another child element
+ // we also add a separator before any new child
+ // if the newChild Element is an input or img type, delete the children key/value pair
+ // if (newChild.name === 'input' && newChild.name === 'img')
+ // delete newChild.children;
+ // let directParent: HTMLElement | any;
+ if (childId === null) {
+ parentComponent.children.push(topSeparator, newChild);
+ } else {
+ const directParent = findChild(parentComponent, childId);
+ if (directParent) {
+ if (directParent.type === 'HTML Element' && type === 'HTML Element') {
+ directParent.children.push(topSeparator, newChild);
+ } else {
+ return { ...state };
+ }
+ }
+ }
+
+ // update canvasFocus to the new child
+ const canvasFocus = {
+ ...state.canvasFocus,
+ componentId: state.canvasFocus.componentId,
+ childId: newChild.childId
+ };
+
+ // Increment IDs
+ state.nextChildId += 1;
+ state.nextTopSeparatorId += 1;
+
+ // Update the components array and potentially other parts of the state
+ components[parentComponentId - 1].children =
+ manageSeparators.mergeSeparator(
+ components[parentComponentId - 1].children,
+ 1
+ );
+
+ // Generate code for the component
+ parentComponent.code = generateCode(
+ components,
+ parentComponentId,
+ [...state.rootComponents],
+ state.projectType,
+ state.HTMLTypes,
+ state.MUITypes,
+ state.tailwind,
+ action.payload.contextParam
+ );
+
+ // Update state
+ state.components = components;
+ state.canvasFocus = canvasFocus;
+
+ return state;
+ },
+
+ changeTailwind: (state, action) => {
+ // return { ...state, tailwind: action.payload }
+ state.tailwind = action.payload;
+ },
+ changeFocus: (state, action) => {
+ const { componentId, childId } = action.payload;
+ // makes separators not selectable
+ if (childId < 1000) {
+ //update componentId and childId in the state
+ state.canvasFocus = { ...state.canvasFocus, componentId, childId };
+ //makes it so the code preview will update when clicking on a new component
+ state.components = state.components.map((element) => {
+ return Object.assign({}, element);
+ });
+ }
+ },
+
+ updateStateUsed: (state, action) => {
+ const { stateUsedObj } = action.payload;
+ const components = [...state.components];
+ const component = findComponent(
+ components,
+ state.canvasFocus.componentId
+ );
+ if (component) {
+ if (state.canvasFocus.childId !== null) {
+ const targetChild = findChild(component, state.canvasFocus.childId);
+ if (targetChild) {
+ targetChild.stateUsed = stateUsedObj;
+ component.code = generateCode(
+ components,
+ state.canvasFocus.componentId,
+ [...state.rootComponents],
+ state.projectType,
+ state.HTMLTypes,
+ state.MUITypes,
+ state.tailwind,
+ action.payload.contextParam
+ );
+ state.components = components;
+ }
+ }
+ }
+ },
+
+ updateUseContext: (state, action) => {
+ const { useContextObj } = action.payload;
+ const components = [...state.components];
+ const component = findComponent(
+ components,
+ state.canvasFocus.componentId
+ );
+ if (component) {
+ component.useContext = useContextObj;
+ component.code = generateCode(
+ components,
+ state.canvasFocus.componentId,
+ [...state.rootComponents],
+ state.projectType,
+ state.HTMLTypes,
+ state.MUITypes,
+ state.tailwind,
+ action.payload.contextParam
+ );
+ state.components = components;
+ }
+ },
+
+ resetAllState: (state) => {
+ if (state.isLoggedIn) {
+ Object.assign(state, initialState);
+ state.isLoggedIn = true;
+ return;
+ }
+ Object.assign(state, initialState);
+ },
+ changePosition: (state, action) => {
+ const { currentChildId, newParentChildId } = action.payload;
+ // if the currentChild Id is the same as the newParentId (i.e. a component is trying to drop itself into itself), don't update state
+ if (currentChildId === newParentChildId) return state;
+ // find the current component in focus
+ let components = [...state.components];
+ const component = findComponent(
+ components,
+ state.canvasFocus.componentId
+ );
+ // find the moved element's former parent
+ // delete the element from it's former parent's children array
+ if (component) {
+ const { directParent, childIndexValue } = findParent(
+ component,
+ currentChildId
+ );
+ // BREAKING HERE during manipulation of positions. Sometimes get a null value when manipulating positions
+ // Only run if the directParent exists
+ if (directParent) {
+ if (directParent.children) {
+ const child = { ...directParent.children[childIndexValue] };
+ directParent.children.splice(childIndexValue, 1);
+ // if the childId is null, this signifies that we are adding a child to the top level component rather than another child element
+ if (newParentChildId === null) {
+ component.children.push(child);
+ }
+ // if there is a childId (childId here references the direct parent of the new child) find that child and a new child to its children array
+ else {
+ const directParent = findChild(component, newParentChildId);
+ if (directParent?.children) {
+ directParent.children.push(child);
+ }
+ }
+ }
+ }
+ let nextTopSeparatorId = state.nextTopSeparatorId;
+ components[state.canvasFocus.componentId - 1].children =
+ manageSeparators.mergeSeparator(
+ components[state.canvasFocus.componentId - 1].children,
+ 0
+ );
+ nextTopSeparatorId = manageSeparators.handleSeparators(
+ components[state.canvasFocus.componentId - 1].children,
+ 'change position'
+ );
+ component.code = generateCode(
+ components,
+ state.canvasFocus.componentId,
+ [...state.rootComponents],
+ state.projectType,
+ state.HTMLTypes,
+ state.MUITypes,
+ state.tailwind,
+ action.payload.contextParam
+ );
+ state.components = components;
+ state.nextTopSeparatorId = nextTopSeparatorId;
+ }
+ },
+
+ updateCss: (state, action) => {
+ const { style } = action.payload;
+ const components = [...state.components];
+ const component = findComponent(
+ components,
+ state.canvasFocus.componentId
+ );
+ // closed if statement at the end of the block
+ if (component && state.canvasFocus.childId) {
+ const targetChild = findChild(component, state.canvasFocus.childId);
+ if (targetChild) {
+ targetChild.style = style;
+ component.code = generateCode(
+ components,
+ state.canvasFocus.componentId,
+ [...state.rootComponents],
+ state.projectType,
+ state.HTMLTypes,
+ state.MUITypes,
+ state.tailwind,
+ action.payload.contextParam
+ );
+ state.components = components;
+ }
+ }
+ },
+
+ updateAttributes: (state, action) => {
+ const { attributes } = action.payload;
+
+ const components = [...state.components];
+ const component = findComponent(
+ components,
+ state.canvasFocus.componentId
+ );
+ // closed if statement at the end of the block
+ if (component && state.canvasFocus.childId) {
+ const targetChild = findChild(component, state.canvasFocus.childId);
+ if (targetChild) {
+ targetChild.attributes = attributes;
+
+ component.code = generateCode(
+ components,
+ state.canvasFocus.componentId,
+ [...state.rootComponents],
+ state.projectType,
+ state.HTMLTypes,
+ state.MUITypes,
+ state.tailwind,
+ action.payload.contextParam
+ );
+ state.components = components;
+ }
+ }
+ },
+
+ updateEvents: (state, action) => {
+ const { events } = action.payload;
+ if (JSON.stringify(events) === '{}') return state;
+ const components = [...state.components];
+ const component = findComponent(
+ components,
+ state.canvasFocus.componentId
+ );
+ if (component && state.canvasFocus.childId) {
+ const targetChild = findChild(component, state.canvasFocus.childId);
+ const event = Object.keys(events)[0];
+ const funcName = events[event];
+ if (targetChild) {
+ targetChild.events[event] = funcName;
+
+ component.code = generateCode(
+ components,
+ state.canvasFocus.componentId,
+ [...state.rootComponents],
+ state.projectType,
+ state.HTMLTypes,
+ state.MUITypes,
+ state.tailwind,
+ action.payload.contextParam
+ );
+ state.components = components;
+ }
+ }
+ },
+
+ deleteEventAction: (state, action) => {
+ const { event } = action.payload;
+ const components = [...state.components];
+ const component = findComponent(
+ components,
+ state.canvasFocus.componentId
+ );
+ if (component && state.canvasFocus.childId) {
+ const targetChild = findChild(component, state.canvasFocus.childId);
+ if (targetChild) {
+ delete targetChild.events[event];
+
+ component.code = generateCode(
+ components,
+ state.canvasFocus.componentId,
+ [...state.rootComponents],
+ state.projectType,
+ state.HTMLTypes,
+ state.MUITypes,
+ state.tailwind,
+ action.payload.contextParam
+ );
+ state.components = components;
+ }
+ }
+ },
+
+ deletePage: (state, action) => {
+ const id: number = state.canvasFocus.componentId;
+ const name: string = state.components[id - 1].name;
+ const components: Component[] = deleteById(id, name, state);
+ // rebuild rootComponents with correct page IDs
+ const rootComponents = updateRoots(components);
+ const canvasFocus = { componentId: 1, childId: null };
+ state.rootComponents = rootComponents;
+ state.components = components;
+ state.canvasFocus = canvasFocus;
+ },
+
+ deleteReusableComponent: (state, action) => {
+ const id: number = state.canvasFocus.componentId;
+ const name: string = state.components[id - 1].name;
+ // updated list of components after deleting a component
+ const components: Component[] = deleteById(id, name, state);
+ const rootComponents: number[] = updateRoots(components);
+ // iterate over the length of the components array
+ for (let i = 0; i < components.length; i++) {
+ //if the component uses context from component being deleted
+ if (components[i].useContext && components[i].useContext[id]) {
+ // iterate over children to see where it is being used, then reset that compText/compLink/useState
+ for (let child of components[i].children) {
+ if (child.stateUsed) {
+ if (child.stateUsed.compTextProviderId === id) {
+ child.attributes.compText = '';
+ delete child.stateUsed.compText;
+ delete child.stateUsed.compTextProviderId;
+ delete child.stateUsed.compTextPropsId;
+ }
+ if (child.stateUsed.compLinkProviderId === id) {
+ child.attributes.compLink = '';
+ delete child.stateUsed.compLink;
+ delete child.stateUsed.compLinkProviderId;
+ delete child.stateUsed.compLinkPropsId;
+ }
+ }
+ }
+ delete components[i].useContext[id];
+ }
+
+ // for each component's code, run the generateCode function to
+ // update the code preview on the app
+ components[i].code = generateCode(
+ components,
+ components[i].id,
+ rootComponents,
+ state.projectType,
+ state.HTMLTypes,
+ state.MUITypes,
+ state.tailwind,
+ action.payload.contextParam
+ );
+ }
+ const canvasFocus = { componentId: 1, childId: null };
+
+ state.rootComponents = rootComponents;
+ state.components = components;
+ state.canvasFocus = canvasFocus;
+ state.nextComponentId = id;
+ },
+ setProjectName: (state, action) => {
+ state.name = action.payload;
+ },
+ changeProjectType: (state, action) => {
+ // when a project type is changed, both change the project type in state and also regenerate the code for each component
+ const { projectType } = action.payload;
+
+ const components = [...state.components];
+ // also update the name of the root component of the application to fit classic React and next.js/gatsby conventions
+ if (projectType === 'Next.js' || projectType === 'Gatsby.js')
+ components[0]['name'] = 'index';
+ else components[0]['name'] = 'App';
+ components.forEach((component) => {
+ component.code = generateCode(
+ components,
+ component.id,
+ [...state.rootComponents],
+ projectType,
+ state.HTMLTypes,
+ state.MUITypes,
+ state.tailwind,
+ action.payload.contextParam
+ );
+ });
+ state.components = components;
+ state.projectType = projectType;
+ },
+
+ resetState: (state, action) => {
+ const nextChildId = 1;
+ const nextTopSeparatorId = 1000;
+ const rootComponents = [1];
+ const nextComponentId = 2;
+ const canvasFocus = {
+ ...state.canvasFocus,
+ componentId: 1,
+ childId: null
+ };
+ const rootComponent = {
+ ...state.components[0],
+ code: 'Drag in a component or HTML element into the canvas!
',
+ children: [],
+ style: {}
+ };
+ const components = [rootComponent];
+
+ const stylesheet = '';
+ const resetHTMLTypes = HTMLTypes;
+ const resetMUITypes = MUITypes;
+
+ return {
+ ...state,
+ nextChildId,
+ nextTopSeparatorId,
+ rootComponents,
+ nextComponentId,
+ components,
+ canvasFocus,
+ stylesheet,
+ HTMLTypes: resetHTMLTypes,
+ MUITypes: resetMUITypes
+ };
+ },
+ updateProjectName: (state, action) => {
+ const projectName = action.payload;
+ state.name = projectName;
+ },
+ updateProjectId: (state, action) => {
+ const projectId = action.payload; //updates the slice with new _id
+ state._id = projectId;
+ },
+ updateProjectPublished: (state, action) => {
+ const projectPublished = action.payload;
+ state.published = projectPublished;
+ },
+ deleteElement: (state, action) => {
+ let name: string = '';
+ const HTMLTypes: HTMLType[] = [...state.HTMLTypes].filter((el) => {
+ if (el.id === action.payload.id) {
+ name = el.tag;
+ }
+ return el.id !== action.payload.id;
+ });
+ // const MUITypes: MUIType[] = [...state.MUITypes].filter((el) => {
+ // if (el.id === action.payload.id) {
+ // name = el.tag;
+ // }
+ // return el.id !== action.payload.id;
+ // });
+ const components: Component[] = deleteById(
+ action.payload.id,
+ name,
+ state
+ );
+ const rootComponents: number[] = updateRoots(components);
+ const canvasFocus = { ...state.canvasFocus, childId: null };
+ components.forEach((el, i) => {
+ el.code = generateCode(
+ components,
+ components[i].id,
+ rootComponents,
+ state.projectType,
+ state.HTMLTypes,
+ state.MUITypes,
+ state.tailwind,
+ action.payload.contextParam
+ );
+ });
+
+ state.canvasFocus = canvasFocus;
+ state.HTMLTypes = HTMLTypes;
+ // state.MUITypes = MUITypes;
+ },
+
+ deleteChild: (state, action) => {
+ // if in-focus instance is a top-level component and not a child, don't delete anything
+ if (!state.canvasFocus.childId) return state;
+ // find the current component in focus
+ const components = [...state.components];
+ const component = findComponent(
+ components,
+ state.canvasFocus.componentId
+ );
+ // find the moved element's former parent
+ const { directParent, childIndexValue } = findParent(
+ component,
+ state.canvasFocus.childId
+ );
+
+ // ------------------------------------------- ALSO added code below -------------------------------------------
+
+ let canvasFocus = { ...state.canvasFocus, childId: null }; // ADDED to avoid null error
+ let nextTopSeparatorId = 1000; // ADDED to avoid null error
+ const childIdDeleteClicked = action.payload.id; // ADDED to ensure no cross-element deletion possible
+
+ // delete the element from its former parent's children array, subject to below conditional to avoid null error
+ if (
+ directParent &&
+ (state.canvasFocus.childId === childIdDeleteClicked ||
+ JSON.stringify(action.payload.id) === '{}') // Ensuring deletion works for mouseclick OR using delete key, from 2 different dispatch sources
+ ) {
+ if (directParent.children) {
+ directParent.children.splice(childIndexValue, 1);
+ let nextTopSeparatorId = manageSeparators.handleSeparators(
+ components[canvasFocus.componentId - 1].children,
+ 'delete'
+ );
+ }
+ }
+
+ // ------------------------------------------- ALSO added code above -------------------------------------------
+ component.code = generateCode(
+ components,
+ state.canvasFocus.componentId,
+ [...state.rootComponents],
+ state.projectType,
+ state.HTMLTypes,
+ state.MUITypes,
+ state.tailwind,
+ action.payload.contextParam
+ );
+ state.components = components;
+ state.canvasFocus = canvasFocus;
+ state.nextTopSeparatorId = nextTopSeparatorId;
+ },
+ setInitialState: (state, action) => {
+ // set the canvas focus to be the first component
+ const canvasFocus = {
+ ...action.payload.canvasFocus,
+ componentId: 1,
+ childId: null
+ };
+ state.canvasFocus = canvasFocus;
+ },
+ //deleted 'convertToJSX' function, which threw errors upon opening
+ openProject: (state, action) => {
+ // returning the action.payload is a Redux shortcut that updates the entire app state at the same time
+ return action.payload;
+ },
+ addElement: (state, action) => {
+ const HTMLTypes = [...state.HTMLTypes];
+ HTMLTypes.push(action.payload);
+ state.HTMLTypes = HTMLTypes;
+ state.customElementId += 1;
+ },
+ //Undo & Redo functions are not working properly. Redo & Undo target the last component rather than last added HTML Element.
+ undo: (state, action) => {
+ const focusIndex = state.canvasFocus.componentId - 1;
+ // if the past array is empty, return state
+ if (state.components[focusIndex].past.length <= 1) {
+ return { ...state };
+ }
+ // the children array of the focused component will equal the last element of the past array
+ state.components[focusIndex].children =
+ state.components[focusIndex].past[
+ state.components[focusIndex].past.length - 1
+ ];
+ // the last element of the past array gets popped off
+ const poppedEl: Component = state.components[focusIndex].past.pop();
+ // the last element of the past array gets popped off and pushed into the future array
+ state.components[focusIndex].future.push(poppedEl);
+ //generate code for the Code Preview
+ state.components.forEach((el, i) => {
+ el.code = generateCode(
+ state.components,
+ state.components[i].id,
+ state.rootComponents,
+ state.projectType,
+ state.HTMLTypes,
+ state.MUITypes,
+ state.tailwind,
+ action.payload.contextParam
+ );
+ });
+
+ state = state;
+ },
+ redo: (state, action) => {
+ const focusIndex = state.canvasFocus.componentId - 1;
+ //if future array is empty, return state
+ if (state.components[focusIndex].future.length === 0) return { ...state };
+ //the children array of the focused component will equal the last element of the future array
+ state.components[focusIndex].children =
+ state.components[focusIndex].future[
+ state.components[focusIndex].future.length - 1
+ ];
+ //the last element of the future array gets pushed into the past
+ const poppedEl: Component = state.components[focusIndex].future.pop();
+ //the last element of the future array gets popped out
+ state.components[focusIndex].past.push(poppedEl);
+ // generate code for the Code Preview
+ state.components.forEach((el, i) => {
+ el.code = generateCode(
+ state.components,
+ state.components[i].id,
+ state.rootComponents,
+ state.projectType,
+ state.HTMLTypes,
+ state.MUITypes,
+ state.tailwind,
+ action.payload.contextParam
+ );
+ });
+ state = state;
+ },
+ addState: (state, action) => {
+ // find the current component in focus
+ const components = [...state.components];
+ const currComponent = findComponent(
+ components,
+ state.canvasFocus.componentId
+ );
+ //will add update StateProps to current components' array
+ currComponent.stateProps.push(action.payload.newState);
+ currComponent.useStateCodes = updateUseStateCodes(currComponent);
+ currComponent.stateProps.push(action.payload.setNewState);
+ currComponent.code = generateCode(
+ components,
+ state.canvasFocus.componentId,
+ [...state.rootComponents],
+ state.projectType,
+ state.HTMLTypes,
+ state.MUITypes,
+ state.tailwind,
+ action.payload.contextParam
+ );
+ state.components = components;
+ },
+ addPassedInProps: (state, action) => {
+ //When props are passed from a parent to a child in the State Manager tab, it will update the components available passedinprops
+ // find the current component in focus
+ const components = [...state.components];
+ const currComponent = findComponent(
+ components,
+ state.canvasFocus.componentId
+ );
+ //prevents passing in props more than one time to the current component
+ for (let i = 0; i < currComponent.passedInProps.length; i++) {
+ let curr = currComponent.passedInProps[i];
+ if (curr.id === action.payload.passedInProps.id) {
+ return { ...state, components };
+ }
+ }
+
+ //find the parent for deleting instances of where the parent is passing props to children
+ let parent: Component;
+ for (let i = 0; i < components.length; i++) {
+ let currComponent = components[i];
+ for (let j = 0; j < currComponent.children.length; j++) {
+ let currChild = currComponent.children[j];
+ if (currChild.typeId === state.canvasFocus.componentId) {
+ parent = currComponent;
+ }
+ }
+ }
+
+ //search for whether the child exists in the parent's children array
+ //if so update the passed in props child element with the updates passed in props
+ parent.children.forEach((child) => {
+ if (child.name === currComponent.name) {
+ child.passedInProps.push(action.payload.passedInProps);
+ }
+ });
+
+ // check each components passedInProps property and updating there as well.
+ currComponent.passedInProps.push(action.payload.passedInProps);
+
+ //update the import codes for the current component
+ currComponent.useStateCodes = updateUseStateCodes(currComponent);
+ //update code preview for current component
+ currComponent.code = generateCode(
+ components,
+ state.canvasFocus.componentId,
+ [...state.rootComponents],
+ state.projectType,
+ state.HTMLTypes,
+ state.MUITypes,
+ state.tailwind,
+ action.payload.contextParam
+ );
+ //update code preview for parent component (since we have added it to the children array)
+ parent.code = generateCode(
+ components,
+ parent.id,
+ [...state.rootComponents],
+ state.projectType,
+ state.HTMLTypes,
+ state.MUITypes,
+ state.tailwind,
+ action.payload.contextParam
+ );
+
+ // return { ...state, components };
+ state.components = components;
+ },
+ deletePassedInProps: (state, action) => {
+ const components = [...state.components];
+ let currComponent = findComponent(
+ components,
+ state.canvasFocus.componentId
+ );
+
+ //find the parent of the component that we are deleting from
+ let parent: Component;
+ for (let i = 0; i < components.length; i++) {
+ let currComponent = components[i];
+ for (let j = 0; j < currComponent.children.length; j++) {
+ let currChild = currComponent.children[j];
+ if (currChild.typeId === state.canvasFocus.componentId) {
+ parent = currComponent;
+ }
+ }
+ }
+
+ //deletes all instances of passedInProps from the children arrays of the current Component
+ const deletePassedInPropsChildren = (currComponent: Component) => {
+ const innerFunc = (currChild: Component | ChildElement) => {
+ // when there are no children, return up a level
+ if (
+ currChild.children.filter((el) => el.type === 'Component')
+ .length === 0
+ )
+ return;
+ if (currChild.children.length) {
+ currChild.children
+ .filter((el) => el.type === 'Component')
+ .forEach((child, j) => {
+ child.passedInProps.forEach((prop, k) => {
+ if (prop.id === action.payload.rowId) {
+ child.passedInProps.splice(k, 1);
+ innerFunc(child);
+ }
+ });
+ });
+ }
+ };
+ //for every component we update, generate new code
+ innerFunc(currComponent);
+ currComponent.code = generateCode(
+ components,
+ currComponent.id,
+ [...state.rootComponents],
+ state.projectType,
+ state.HTMLTypes,
+ state.MUITypes,
+ state.tailwind,
+ action.payload.contextParam
+ );
+ };
+ //delete from the components passedInProps array
+ const deletePassedInProps = (myComponent: Component) => {
+ if (
+ myComponent.children.filter((el) => el.type === 'Component')
+ .length === 0
+ ) {
+ if (myComponent.passedInProps.length > 0) {
+ myComponent.passedInProps.forEach((prop, index) => {
+ if (prop.id === action.payload.rowId) {
+ myComponent.passedInProps.splice(index, 1);
+ }
+ });
+ }
+ myComponent.useStateCodes = updateUseStateCodes(myComponent);
+ myComponent.code = generateCode(
+ components,
+ myComponent.id,
+ [...state.rootComponents],
+ state.projectType,
+ state.HTMLTypes,
+ state.MUITypes,
+ state.tailwind,
+ action.payload.contextParam
+ );
+ return;
+ }
+ myComponent.passedInProps.forEach((prop, i) => {
+ if (prop.id === action.payload.rowId) {
+ myComponent.passedInProps.splice(i, 1);
+ myComponent.children
+ .filter((el) => el.type === 'Component')
+ .forEach((child, i) => {
+ let next = components.find((comp) => comp.id === child.typeId);
+ deletePassedInProps(next);
+ });
+ }
+ });
+ myComponent.useStateCodes = updateUseStateCodes(myComponent);
+ myComponent.code = generateCode(
+ components,
+ myComponent.id,
+ [...state.rootComponents],
+ state.projectType,
+ state.HTMLTypes,
+ state.MUITypes,
+ state.tailwind,
+ action.payload.contextParam
+ );
+ };
+
+ deletePassedInPropsChildren(parent);
+ deletePassedInProps(currComponent);
+
+ parent.code = generateCode(
+ components,
+ parent.id,
+ [...state.rootComponents],
+ state.projectType,
+ state.HTMLTypes,
+ state.MUITypes,
+ state.tailwind,
+ action.payload.contextParam
+ );
+ state.components = components;
+ },
+ deleteState: (state, action) => {
+ const components = [...state.components];
+ let currComponent = findComponent(
+ components,
+ state.canvasFocus.componentId
+ );
+ //updates the stateProps array to reflect total state initialized in component minus the selected state to be deleted
+ currComponent.stateProps = action.payload.stateProps;
+
+ //CHILDREN ARRAY LOOP (needed for code preview)
+ //iterate through all components, starting from top, and delete ALL instances of deleted state (provided to us
+ // in the passedInProps array within the children array of the component
+ // using the action.payload.rowId (variable name) and action.payload.otherId (setVariable name))
+ components.forEach((component) => {
+ //find all instances of state within child elements and delete state
+
+ component.children.forEach((child) => {
+ if (child.type === 'Component') {
+ for (let i = 0; i < child.passedInProps?.length; i++) {
+ if (
+ child.passedInProps[i]['id'] === action.payload.rowId ||
+ child.passedInProps[i]['id'] === action.payload.otherId
+ ) {
+ child.passedInProps.splice(i, 1);
+ i--;
+ }
+ }
+ }
+ });
+
+ // COMPONENT LOOP (needed for tables in State Management Tab)
+ //iterate through all components, starting from top, and delete ALL instances of deleted state (provided to us
+ // in the passedInProps array within each component
+ // using the action.payload.rowId (variable name) and action.payload.otherId (setVariable name))
+ for (let i = 0; i < component.passedInProps?.length; i++) {
+ if (
+ component.passedInProps[i]['id'] === action.payload.rowId ||
+ component.passedInProps[i]['id'] === action.payload.otherId
+ ) {
+ component.passedInProps.splice(i, 1);
+ i--;
+ }
+ }
+ // curr component = where you are deleting from state from, also is the canvas focus
+ // curr component id = providerId
+ // we then iterate through the rest of the components
+ // check if a useContext if created and if the useContext contains the providerId
+ // we then delete from the set, statesFromProvider, the row id, and regenerate the code
+ // Ex: useContext {1: {statesFromProvider: Set, compLink, compText}, 2 : ..., 3 : ...}
+ if (
+ component.useContext &&
+ component.useContext[state.canvasFocus.componentId]
+ ) {
+ component.useContext[
+ state.canvasFocus.componentId
+ ].statesFromProvider.delete(action.payload.rowId);
+ // iterate over children to see where it is being used, then reset that compText/compLink/useState
+ for (let child of component.children) {
+ if (child.stateUsed) {
+ if (
+ child.stateUsed.compTextProviderId === currComponent.id &&
+ child.stateUsed.compTextPropsId === action.payload.rowId
+ ) {
+ child.attributes.compText = '';
+ delete child.stateUsed.compText;
+ delete child.stateUsed.compTextProviderId;
+ delete child.stateUsed.compTextPropsId;
+ }
+ if (
+ child.stateUsed.compLinkProviderId === currComponent.id &&
+ child.stateUsed.compLinkPropsId === action.payload.rowId
+ ) {
+ child.attributes.compLink = '';
+ delete child.stateUsed.compLink;
+ delete child.stateUsed.compLinkProviderId;
+ delete child.stateUsed.compLinkPropsId;
+ }
+ }
+ }
+ }
+
+ // find parent
+ let parent;
+ for (let i = 0; i < components.length; i++) {
+ let currComponent = components[i];
+ for (let j = 0; j < currComponent.children.length; j++) {
+ let currChild = currComponent.children[j];
+ if (currChild.typeId === component.id) {
+ parent = currComponent;
+ }
+ }
+ }
+ if (parent) {
+ parent.code = generateCode(
+ components,
+ parent.id,
+ [...state.rootComponents],
+ state.projectType,
+ state.HTMLTypes,
+ state.MUITypes,
+ state.tailwind,
+ action.payload.contextParam
+ );
+ }
+
+ component.useStateCodes = updateUseStateCodes(component);
+ component.code = generateCode(
+ components,
+ component.id,
+ [...state.rootComponents],
+ state.projectType,
+ state.HTMLTypes,
+ state.MUITypes,
+ state.tailwind,
+ action.payload.contextParam
+ );
+ });
+ state.components = components;
+ },
+
+ toggleLoggedIn: (state, action) => {
+ state.isLoggedIn = action.payload;
+ },
+
+ snapShotAction: (state, action) => {
+ state.components[action.payload.focusIndex].past.push(
+ action.payload.deepCopiedState.components[action.payload.focusIndex]
+ .children
+ );
+ },
+ allCooperativeState: (state, action) => {
+ // return Object.assign({}, state, action.payload);
+ // it is safe to directly assign the properties from 'action.payload' to the state in Redux Toolkit
+ Object.assign(state, action.payload);
+ },
+ updateStylesheet: (state, action) => {
+ state.stylesheet = action.payload;
+ },
+
+ // toggles the active code preview editor for conditional rendering
+ toggleCodePreview: (state) => {
+ state.codePreview = !state.codePreview;
+ },
+
+ // toggles the state of the screenshot trigger to allow a child component to trigger the screenshot of the main component
+ toggleScreenshotTrigger: (state) => {
+ state.screenshotTrigger = !state.screenshotTrigger;
+ }
+
+ // ,configToggle: (state) => {
+ // state.config.saveFlag = !state.config.saveFlag;
+ // state.config.saveTimer = !state.config.saveTimer;
+ // }
+ }
+});
+
+// Exports the action creator function to be used with useDispatch
+
+export const {
+ addComponent,
+ addChild,
+ changeFocus,
+ changeTailwind,
+ changePosition,
+ updateStateUsed,
+ resetAllState,
+ updateUseContext,
+ updateCss,
+ updateEvents,
+ deleteEventAction,
+ deletePage,
+ deleteReusableComponent,
+ setProjectName,
+ changeProjectType,
+ resetState,
+ updateProjectName,
+ updateProjectId,
+ updateProjectPublished,
+ deleteElement,
+ updateAttributes,
+ deleteChild,
+ setInitialState,
+ openProject,
+ addElement,
+ undo,
+ redo,
+ addState,
+ addPassedInProps,
+ deletePassedInProps,
+ deleteState,
+ toggleLoggedIn,
+ //configToggle,
+ snapShotAction,
+ allCooperativeState,
+ updateStylesheet,
+ toggleCodePreview,
+ toggleScreenshotTrigger
+} = appStateSlice.actions;
+
+// Exports so we can combine in rootReducer
+export default appStateSlice.reducer;
diff --git a/package-lock.json b/package-lock.json
index 52eabcc3..92a81fea 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -54,7 +54,6 @@
"html2canvas": "^1.4.1",
"identity-obj-proxy": "^3.0.0",
"js-cookie": "^3.0.5",
- "jsdoc": "^4.0.2",
"jszip": "^3.10.1",
"localforage": "^1.10.0",
"lodash": "^4.17.21",
@@ -136,6 +135,7 @@
"html-webpack-plugin": "^4.5.2",
"jest": "^28.1.3",
"jest-environment-jsdom": "^28.1.3",
+ "jsdoc": "^4.0.2",
"mini-css-extract-plugin": "^2.7.6",
"mongodb": "^3.5.9",
"nodemon": "^3.0.2",
@@ -10954,6 +10954,7 @@
"version": "0.2.8",
"resolved": "https://registry.npmjs.org/@jsdoc/salty/-/salty-0.2.8.tgz",
"integrity": "sha512-5e+SFVavj1ORKlKaKr2BmTOekmXbelU7dC0cDkQLqag7xfuTPuGMUFx7KWJuv4bYZrTsoL2Z18VVCOKYxzoHcg==",
+ "dev": true,
"dependencies": {
"lodash": "^4.17.21"
},
@@ -14746,7 +14747,8 @@
"node_modules/@types/linkify-it": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-3.0.5.tgz",
- "integrity": "sha512-yg6E+u0/+Zjva+buc3EIb+29XEg4wltq7cSmd4Uc2EE/1nUVmxyzpX6gUXD0V8jIrG0r7YeOGVIbYRkxeooCtw=="
+ "integrity": "sha512-yg6E+u0/+Zjva+buc3EIb+29XEg4wltq7cSmd4Uc2EE/1nUVmxyzpX6gUXD0V8jIrG0r7YeOGVIbYRkxeooCtw==",
+ "dev": true
},
"node_modules/@types/long": {
"version": "4.0.2",
@@ -14757,6 +14759,7 @@
"version": "12.2.3",
"resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-12.2.3.tgz",
"integrity": "sha512-GKMHFfv3458yYy+v/N8gjufHO6MSZKCOXpZc5GXIWWy8uldwfmPn98vp81gZ5f9SVw8YYBctgfJ22a2d7AOMeQ==",
+ "dev": true,
"dependencies": {
"@types/linkify-it": "*",
"@types/mdurl": "*"
@@ -14765,7 +14768,8 @@
"node_modules/@types/mdurl": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-1.0.5.tgz",
- "integrity": "sha512-6L6VymKTzYSrEf4Nev4Xa1LCHKrlTlYCBMTlQKFuddo1CvQcE52I0mwfOJayueUC7MJuXOeHTcIU683lzd0cUA=="
+ "integrity": "sha512-6L6VymKTzYSrEf4Nev4Xa1LCHKrlTlYCBMTlQKFuddo1CvQcE52I0mwfOJayueUC7MJuXOeHTcIU683lzd0cUA==",
+ "dev": true
},
"node_modules/@types/mime": {
"version": "1.3.5",
@@ -18102,7 +18106,8 @@
"node_modules/bluebird": {
"version": "3.7.2",
"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz",
- "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg=="
+ "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==",
+ "dev": true
},
"node_modules/bluebird-lst": {
"version": "1.0.9",
@@ -18638,6 +18643,7 @@
"version": "0.9.0",
"resolved": "https://registry.npmjs.org/catharsis/-/catharsis-0.9.0.tgz",
"integrity": "sha512-prMTQVpcns/tzFgFVkVp6ak6RykZyWb3gu8ckUpd6YkTlacOd3DXGJjIpD4Q6zJirizvaiAjSSHlOsA+6sNh2A==",
+ "dev": true,
"dependencies": {
"lodash": "^4.17.15"
},
@@ -27152,6 +27158,7 @@
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/js2xmlparser/-/js2xmlparser-4.0.2.tgz",
"integrity": "sha512-6n4D8gLlLf1n5mNLQPRfViYzu9RATblzPEtm1SthMX1Pjao0r9YI9nw7ZIfRxQMERS87mcswrg+r/OYrPRX6jA==",
+ "dev": true,
"dependencies": {
"xmlcreate": "^2.0.4"
}
@@ -27244,6 +27251,7 @@
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-4.0.2.tgz",
"integrity": "sha512-e8cIg2z62InH7azBBi3EsSEqrKx+nUtAS5bBcYTSpZFA+vhNPyhv8PTFZ0WsjOPDj04/dOLlm08EDcQJDqaGQg==",
+ "dev": true,
"dependencies": {
"@babel/parser": "^7.20.15",
"@jsdoc/salty": "^0.2.1",
@@ -27272,6 +27280,7 @@
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz",
"integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==",
+ "dev": true,
"engines": {
"node": ">=8"
}
@@ -27543,6 +27552,7 @@
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/klaw/-/klaw-3.0.0.tgz",
"integrity": "sha512-0Fo5oir+O9jnXu5EefYbVK+mHMBeEVEy2cmctR1O1NECcCkPRreJKrS6Qt/j3KC2C148Dfo9i3pCmCMsdqGr0g==",
+ "dev": true,
"dependencies": {
"graceful-fs": "^4.1.9"
}
@@ -27652,6 +27662,7 @@
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-3.0.3.tgz",
"integrity": "sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ==",
+ "dev": true,
"dependencies": {
"uc.micro": "^1.0.1"
}
@@ -28431,6 +28442,7 @@
"version": "12.3.2",
"resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-12.3.2.tgz",
"integrity": "sha512-TchMembfxfNVpHkbtriWltGWc+m3xszaRD0CZup7GFFhzIgQqxIfn3eGj1yZpfuflzPvfkt611B2Q/Bsk1YnGg==",
+ "dev": true,
"dependencies": {
"argparse": "^2.0.1",
"entities": "~2.1.0",
@@ -28446,6 +28458,7 @@
"version": "8.6.7",
"resolved": "https://registry.npmjs.org/markdown-it-anchor/-/markdown-it-anchor-8.6.7.tgz",
"integrity": "sha512-FlCHFwNnutLgVTflOYHPW2pPcl2AACqVzExlkGQNsi4CJgqOHN7YTgDd4LuhgN1BFO3TS0vLAruV1Td6dwWPJA==",
+ "dev": true,
"peerDependencies": {
"@types/markdown-it": "*",
"markdown-it": "*"
@@ -28454,12 +28467,14 @@
"node_modules/markdown-it/node_modules/argparse": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
- "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="
+ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
+ "dev": true
},
"node_modules/markdown-it/node_modules/entities": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz",
"integrity": "sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==",
+ "dev": true,
"funding": {
"url": "https://github.com/fb55/entities?sponsor=1"
}
@@ -28468,6 +28483,7 @@
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz",
"integrity": "sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==",
+ "dev": true,
"bin": {
"marked": "bin/marked.js"
},
@@ -28496,7 +28512,8 @@
"node_modules/mdurl": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz",
- "integrity": "sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g=="
+ "integrity": "sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==",
+ "dev": true
},
"node_modules/media-typer": {
"version": "0.3.0",
@@ -32392,6 +32409,7 @@
"version": "0.2.4",
"resolved": "https://registry.npmjs.org/requizzle/-/requizzle-0.2.4.tgz",
"integrity": "sha512-JRrFk1D4OQ4SqovXOgdav+K8EAhSB/LJZqCz8tbX0KObcdeM15Ss59ozWMBWmmINMagCwmqn4ZNryUGpBsl6Jw==",
+ "dev": true,
"dependencies": {
"lodash": "^4.17.21"
}
@@ -34921,7 +34939,8 @@
"node_modules/uc.micro": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz",
- "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA=="
+ "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==",
+ "dev": true
},
"node_modules/uid-safe": {
"version": "2.1.5",
@@ -36424,7 +36443,8 @@
"node_modules/xmlcreate": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/xmlcreate/-/xmlcreate-2.0.4.tgz",
- "integrity": "sha512-nquOebG4sngPmGPICTS5EnxqhKbCmz5Ox5hsszI2T6U5qdrJizBc+0ilYSEjTSzU0yZcmvppztXe/5Al5fUwdg=="
+ "integrity": "sha512-nquOebG4sngPmGPICTS5EnxqhKbCmz5Ox5hsszI2T6U5qdrJizBc+0ilYSEjTSzU0yZcmvppztXe/5Al5fUwdg==",
+ "dev": true
},
"node_modules/xmlhttprequest-ssl": {
"version": "2.0.0",
diff --git a/package.json b/package.json
index ecc69404..9b0350bb 100644
--- a/package.json
+++ b/package.json
@@ -167,7 +167,6 @@
"html2canvas": "^1.4.1",
"identity-obj-proxy": "^3.0.0",
"js-cookie": "^3.0.5",
- "jsdoc": "^4.0.2",
"jszip": "^3.10.1",
"localforage": "^1.10.0",
"lodash": "^4.17.21",
@@ -249,6 +248,7 @@
"html-webpack-plugin": "^4.5.2",
"jest": "^28.1.3",
"jest-environment-jsdom": "^28.1.3",
+ "jsdoc": "^4.0.2",
"mini-css-extract-plugin": "^2.7.6",
"mongodb": "^3.5.9",
"nodemon": "^3.0.2",