From 6a6226d598a65bb34a3f8a0d7081395e1bfb0016 Mon Sep 17 00:00:00 2001 From: Sean Ryan Date: Thu, 9 May 2024 11:04:02 -0700 Subject: [PATCH] Sean 5/9 Fixed render issues with more MUI Components; added JSDoc comment blocks throughout the codebase for better inline documentation that adheres to industry best practices; added a window resizer to iframe > > Co-authored-by: Heather Pfeiffer Co-authored-by: Jesse Wowczuk Co-authored-by: Zack Vandiver Co-authored-by: Austin Alvarez --- app/src/Dashboard/NavbarDash.tsx | 21 +- app/src/Dashboard/Project.tsx | 20 + app/src/Dashboard/ProjectContainer.tsx | 78 +- app/src/components/App.tsx | 16 + .../AssignTab/AssignContainer.tsx | 19 +- .../components/ComponentDropDrown.tsx | 19 +- .../AssignTab/components/ComponentTable.tsx | 26 + .../AssignTab/components/ContextDropDown.tsx | 16 +- .../AssignTab/components/ContextTable.tsx | 39 +- .../ContextAPIManager/ContextManager.tsx | 11 + .../CreateTab/CreateContainer.tsx | 36 +- .../CreateTab/components/AddContextForm.tsx | 21 + .../CreateTab/components/AddDataForm.tsx | 21 +- .../CreateTab/components/DataTable.tsx | 48 +- .../DisplayTab/DisplayContainer.tsx | 30 +- .../CreateTab/CreateContainer.tsx | 34 +- .../CreateTab/components/StatePropsPanel.tsx | 11 + .../CreateTab/components/TableParentProps.tsx | 16 +- .../components/TablePassedInProps.tsx | 12 +- .../CreateTab/components/TableStateProps.tsx | 15 +- .../StateManagement/DisplayTab/DataTable.tsx | 194 ++-- .../DisplayTab/DisplayContainer.tsx | 24 +- .../StateManagement/DisplayTab/Tree.tsx | 65 +- .../DisplayTab/useResizeObserver.ts | 40 +- .../StateManagement/StateManagement.tsx | 20 + app/src/components/bottom/BottomPanel.tsx | 25 +- app/src/components/bottom/BottomTabs.tsx | 18 +- app/src/components/bottom/CodePreview.tsx | 31 +- app/src/components/bottom/CreationPanel.tsx | 16 +- app/src/components/bottom/StylesEditor.tsx | 21 +- app/src/components/bottom/UseStateModal.tsx | 22 +- app/src/components/form/Selector.tsx | 21 + app/src/components/left/ComponentDrag.tsx | 9 + .../components/left/ComponentsContainer.tsx | 13 +- app/src/components/left/ContentArea.tsx | 25 + app/src/components/left/DragDropPanel.tsx | 12 + app/src/components/left/ElementsContainer.tsx | 15 +- app/src/components/left/HTMLItem.tsx | 15 +- app/src/components/left/HTMLPanel.tsx | 31 +- app/src/components/left/MUIDragDropPanel.tsx | 14 +- app/src/components/left/MUIItem.tsx | 2 +- app/src/components/left/ProfilePage.tsx | 17 +- app/src/components/left/RoomsContainer.tsx | 23 +- app/src/components/left/Settings.tsx | 12 +- app/src/components/left/Sidebar.tsx | 26 +- app/src/components/main/AddLink.tsx | 14 +- app/src/components/main/AddRoute.tsx | 8 + app/src/components/main/Arrow.tsx | 129 ++- app/src/components/main/Canvas.tsx | 668 +++++++------- app/src/components/main/CanvasContainer.tsx | 26 +- app/src/components/main/DeleteButton.tsx | 17 +- app/src/components/main/DemoRender.tsx | 770 +--------------- .../components/main/DirectChildComponent.tsx | 18 +- app/src/components/main/DirectChildHTML.tsx | 22 +- .../main/DirectChildHTMLNestable.tsx | 26 +- app/src/components/main/DirectChildMUI.tsx | 16 +- .../main/DirectChildMUINestable.tsx | 16 + app/src/components/main/IndirectChild.tsx | 23 +- app/src/components/main/RouteLink.tsx | 13 + app/src/components/main/SeparatorChild.tsx | 13 + .../marketplace/MarketplaceCard.tsx | 36 +- .../marketplace/MarketplaceCardContainer.tsx | 13 +- app/src/components/marketplace/Searchbar.tsx | 83 +- app/src/components/right/ComponentPanel.tsx | 194 ++-- .../components/right/ComponentPanelItem.tsx | 24 +- .../right/ComponentPanelRoutingItem.tsx | 28 +- app/src/components/right/DeleteProjects.tsx | 24 +- app/src/components/right/ExportButton.tsx | 8 +- app/src/components/right/LoginButton.tsx | 10 +- app/src/components/right/OpenProjects.tsx | 15 +- app/src/components/right/ProjectManager.tsx | 13 +- .../components/right/SaveProjectButton.tsx | 57 +- app/src/components/right/SimpleModal.tsx | 19 +- app/src/components/right/createModal.tsx | 17 +- app/src/components/top/NavBar.tsx | 25 +- app/src/components/top/NavBarButtons.tsx | 20 +- app/src/components/top/NewExportButton.tsx | 24 +- app/src/components/top/PublishModal.tsx | 43 +- app/src/containers/AppContainer.tsx | 20 + app/src/containers/CustomizationPanel.tsx | 21 +- app/src/containers/LeftContainer.tsx | 19 + app/src/containers/MainContainer.tsx | 19 + app/src/containers/MarketplaceContainer.tsx | 67 +- app/src/helperFunctions/DemoRenderHTML.ts | 838 ++++++++++++++++++ .../changePositionValidation.ts | 12 +- app/src/helperFunctions/cloneDeep.ts | 10 +- app/src/helperFunctions/combineStyles.ts | 6 + app/src/helperFunctions/componentBuilder.tsx | 103 +++ .../componentNestValidation.ts | 11 +- app/src/helperFunctions/cssRefresh.tsx | 8 +- app/src/helperFunctions/esbuildService.ts | 6 +- app/src/helperFunctions/generateCode.ts | 327 +++++-- app/src/helperFunctions/manageSeparators.ts | 10 +- app/src/helperFunctions/projectGetSaveDel.ts | 26 + app/src/helperFunctions/randomPassword.ts | 16 + app/src/helperFunctions/renderChildren.tsx | 12 +- app/src/helperFunctions/socket.ts | 17 +- app/src/helperFunctions/zipFiles.ts | 9 +- app/src/index.tsx | 27 +- app/src/interfaces/Interfaces.ts | 20 +- app/src/plugins/fetch-plugin.ts | 45 +- app/src/plugins/unpkg-path-plugin.ts | 24 +- app/src/public/styles/style.css | 10 + app/src/redux/MUITypes.ts | 421 +++++---- app/src/tree/TreeChart.tsx | 21 +- app/src/tree/useResizeObserver.ts | 11 +- app/src/tutorial/CSSEditor.tsx | 48 +- app/src/tutorial/Canvas.tsx | 10 +- app/src/tutorial/CodePreview.tsx | 10 +- app/src/tutorial/ComponentTree.tsx | 72 +- app/src/tutorial/Customization.tsx | 89 +- app/src/tutorial/HtmlElements.tsx | 103 ++- app/src/tutorial/KeyboardShortcuts.tsx | 10 +- app/src/tutorial/Pages.tsx | 10 +- app/src/tutorial/ReusableComponents.tsx | 61 +- app/src/tutorial/RouteLinks.tsx | 75 +- app/src/tutorial/States.tsx | 10 +- app/src/tutorial/Styling.tsx | 10 +- app/src/tutorial/Tutorial.tsx | 9 +- app/src/tutorial/TutorialPage.tsx | 8 +- app/src/utils/createApplication.util.ts | 158 +++- app/src/utils/createFiles.util.ts | 17 +- app/src/utils/createGatsbyApp.util.ts | 127 ++- app/src/utils/createGatsbyFiles.util.ts | 27 +- app/src/utils/createNextApp.util.ts | 142 ++- app/src/utils/createNextFiles.util.ts | 23 +- app/src/utils/exportProject.util.ts | 47 +- server/controllers/cookieController.ts | 35 +- server/controllers/marketplaceController.ts | 164 ++-- server/controllers/projectController.ts | 93 +- server/controllers/sessionController.ts | 35 +- server/controllers/userController.ts | 62 +- server/controllers/userStylesController.ts | 14 +- server/graphQL/resolvers/mutation.ts | 55 +- server/graphQL/resolvers/query.ts | 19 +- server/routers/auth.ts | 20 + server/routers/passport-setup.ts | 16 + server/routers/stylesRouter.ts | 8 +- server/server.ts | 26 +- 139 files changed, 5225 insertions(+), 2176 deletions(-) create mode 100644 app/src/helperFunctions/DemoRenderHTML.ts create mode 100644 app/src/helperFunctions/componentBuilder.tsx diff --git a/app/src/Dashboard/NavbarDash.tsx b/app/src/Dashboard/NavbarDash.tsx index 328c0ec81..a84bc125c 100644 --- a/app/src/Dashboard/NavbarDash.tsx +++ b/app/src/Dashboard/NavbarDash.tsx @@ -74,8 +74,25 @@ const StyledMenuItem = withStyles((theme) => ({ } } }))(MenuItem); -// TO DO: set types of props validation -export default function NavBar(props) { + +/** + * NavBar is a component that provides navigation and theming controls for an application. + * It includes a logo, navigation links, and buttons for sorting projects and toggling the theme. + * The NavBar uses a mix of MUI components styled with custom themes and styles. + * + * @param {Object} props - The properties passed to the component. + * @param {Function} props.optionClicked - Callback function called when a sort option is selected. + * @param {boolean} props.isThemeLight - Indicates if the light theme is currently active. + * @param {Function} props.setTheme - Function to toggle the theme between light and dark. + * @returns {JSX.Element} The NavBar component which includes a logo, navigation links, and control buttons. + * + * The NavBar is styled using `makeStyles` to handle theming and spacing. It features a responsive AppBar + * containing an Avatar with the application's logo, a Typography component for the application name, + * and various buttons for navigating the application, sorting data, and toggling the theme. + * The sorting options are presented in a custom-styled Menu component, `StyledMenu`, with specific + * icons indicating the sort method. The theme toggle button changes icon based on the current theme. + */ +export default function NavBar(props): JSX.Element { // TO DO: import setStyle const classes = useStyles(); const style = useSelector((store) => store.styleSlice); diff --git a/app/src/Dashboard/Project.tsx b/app/src/Dashboard/Project.tsx index fb91843ec..7cc44274a 100644 --- a/app/src/Dashboard/Project.tsx +++ b/app/src/Dashboard/Project.tsx @@ -32,6 +32,26 @@ type props = { const currUserSSID = window.localStorage.getItem('ssid') || 'unavailable'; const currUsername = window.localStorage.getItem('username') || 'unavailable'; +/** + * `Project` is a React component that displays information about a specific project, such as its name, + * author, and the number of likes it has received. It provides functionality for liking, copying, publishing, + * commenting on, and deleting projects. The component integrates several mutations using Apollo Client to interact + * with a GraphQL backend. + * @param {Object} props - The properties passed to the component. + * @param {string} props.name - The name of the project. + * @param {string} props.id - The unique identifier for the project. + * @param {string} props.userId - The user ID of the project owner. + * @param {string} props.username - The username of the project owner. + * @param {number} props.likes - The number of likes the project has received. + * @param {boolean} props.published - Indicates if the project is currently published. + * @param {Array} props.comments - An array of comment objects associated with the project. + * @returns {JSX.Element} The rendered component which allows interaction with project data and includes + * buttons for different actions depending on the user and project status. + * + * Each button in the component is associated with a specific action (like, copy, publish, comment, delete) and + * uses GraphQL mutations to perform these actions. The component also manages local state for handling comments, + * modals, and toggling UI elements based on the user's interaction. + */ const Project = ({ name, likes, diff --git a/app/src/Dashboard/ProjectContainer.tsx b/app/src/Dashboard/ProjectContainer.tsx index 931ae2b0b..e80c991c9 100644 --- a/app/src/Dashboard/ProjectContainer.tsx +++ b/app/src/Dashboard/ProjectContainer.tsx @@ -1,5 +1,10 @@ -import React, { useState} from 'react'; -import { ThemeProvider, Theme, StyledEngineProvider, useTheme } from '@mui/material/styles'; +import React, { useState } from 'react'; +import { + ThemeProvider, + Theme, + StyledEngineProvider, + useTheme +} from '@mui/material/styles'; import makeStyles from '@mui/styles/makeStyles'; import { useQuery } from '@apollo/client'; import Tabs from '@mui/material/Tabs'; @@ -11,22 +16,27 @@ import NavBarDash from './NavbarDash'; import { useSelector } from 'react-redux'; import { theme1, theme2 } from '../public/styles/theme'; - declare module '@mui/styles/defaultTheme' { interface DefaultTheme extends Theme {} } - // Implement Apollo Client useQuery hook to retrieve data from the server through graphQL. This includes 2 steps: // 1) Impliment Apollo Provider in the top component in ./src/index.js, this allows children components access to the queried data // 2) useQuery hook will update the data stored in Apollo Client's cache and automatically trigger child components rendering - // setting light and dark themes (navbar and background); linked to theme.ts const lightTheme = theme1; const darkTheme = theme2; // dark mode color in theme.ts not reached -const arrToComponent = arr => +/** + * Transforms an array of project data into an array of React components. + * Each project in the array is passed as props to the component, creating a list + * of these components based on the provided array. Each component is given a unique `key` prop + * based on its index in the array to optimize React's rendering process. + * @param {Array} arr - An array of project objects, where each object contains project data. + * @returns {Array} An array of components populated with data from the input array. + */ +const arrToComponent = (arr): Array => arr.map((proj, index) => ( /> )); -// Start Pulled from materialUI to create a tab panel -const a11yProps = (index: any) => ({ +/** + * Generates accessibility props for a tab component within a tab panel. These properties help in + * linking the tab to its corresponding tab panel, improving accessibility and usability for users + * with assistive technologies. + * @param {any} index - The index of the tab and its corresponding panel. + * @returns {Object} An object containing the `id` and `aria-controls` attributes for accessibility purposes. + */ +const a11yProps = (index: any): Object => ({ id: `vertical-tab-${index}`, 'aria-controls': `vertical-tabpanel-${index}` }); @@ -75,7 +91,7 @@ const TabPanelItem = (props: TabPanelProps): JSX.Element => { ); }; -const useStyles = makeStyles(theme => ({ +const useStyles = makeStyles((theme) => ({ root: { flexGrow: 1, // backgroundColor: theme.palette.background.paper, @@ -85,7 +101,31 @@ const useStyles = makeStyles(theme => ({ // borderRight: `1px solid ${theme.palette.divider}` } })); -// End of prefab code to generate a tab panel + +/** + * `ProjectContainer` is a React component that manages the display and sorting of project data retrieved + * from a GraphQL server using the Apollo Client. It provides a user interface with two dashboards: + * a shared dashboard that displays all published projects and a private dashboard that shows projects + * created by the currently logged-in user. The component includes sorting functionality based on project + * rating, creation date, or user. + * + * @returns {JSX.Element} The ProjectContainer component which provides a tabbed interface for navigating + * between shared and private dashboards, each displaying a list of projects. + * + * This component utilizes MUI's Tabs for navigation between the dashboards. It leverages the Apollo Client's + * `useQuery` hook to fetch projects data and automatically update the UI upon data changes. Sorting preferences + * are managed through local state and applied to the project data based on user interaction. The component + * supports theming and uses `ThemeProvider` to switch between light and dark themes based on user settings. + * + * Dependencies: + * - Apollo Client: For data fetching and state management with GraphQL. + * - Material-UI: For UI components and theming. + * - Redux: For managing application-wide state like theme settings. + * + * The component is designed to be responsive and accessible, with appropriate ARIA attributes for navigation + * and content sections. It includes error handling for data fetching issues and a loading state during data + * retrieval. + */ const ProjectContainer = (): JSX.Element => { const classes = useStyles(); const [value, setValue] = useState(0); @@ -98,26 +138,26 @@ const ProjectContainer = (): JSX.Element => { const userSSID = window.localStorage.getItem('ssid') || 'unavailable'; const username = window.localStorage.getItem('username') || 'unavailable'; const [isThemeLight, setTheme] = useState(true); - const style = useSelector(store => store.styleSlice) + const style = useSelector((store) => store.styleSlice); // hook for sorting menu const [selectedOption, setSelectedOption] = useState('RATING'); - const sortByRating = projects => { + const sortByRating = (projects) => { // generate a sorted array of public projects based on likes const sortedRatings = projects.sort((a, b) => b.likes - a.likes); return sortedRatings; }; - const sortByDate = projects => { + const sortByDate = (projects) => { // generate a sorted array of public projects based on date const sortedRatings = projects.sort((a, b) => b.createdAt - a.createdAt); return sortedRatings; }; - const sortByUser = projects => { + const sortByUser = (projects) => { // generate a sorted array of public projects based on username const sortedRatings = projects.sort((a, b) => b.username - a.username); return sortedRatings; }; // function for selecting drop down sorting menu - const optionClicked = value => { + const optionClicked = (value) => { setSelectedOption(value); }; // useQuery hook abstracts fetch request @@ -129,12 +169,12 @@ const ProjectContainer = (): JSX.Element => { if (error) return

Error :{error}

; // based on resolver(getAllProject) for this query, the data is stored in the data object with the key 'getAllProjects' const projects = data.getAllProjects; - + //create array to hold the data recieved in the public dashboard the will be conditionally rendered - let sortedProjects = projects.filter(proj => { + let sortedProjects = projects.filter((proj) => { return proj.published; }); - const userProjects = projects.filter(proj => { + const userProjects = projects.filter((proj) => { return proj.username === username; }); // checking which sorting method was selected from drop down menu and invoking correct sorting function @@ -156,7 +196,7 @@ const ProjectContainer = (): JSX.Element => {
diff --git a/app/src/components/App.tsx b/app/src/components/App.tsx index f97e98a27..520a9232d 100644 --- a/app/src/components/App.tsx +++ b/app/src/components/App.tsx @@ -6,6 +6,22 @@ import { useDispatch } from 'react-redux'; import AppContainer from '../containers/AppContainer'; +/** + * The `App` component is the root component of the React application. It performs an initial check + * to determine if a user is logged in (not a 'guest') by inspecting local storage, and updates the + * application's state accordingly using Redux. It then renders the `AppContainer`, which serves as + * the main container for the application's user interface. + * + * The `useEffect` hook is used to perform the login check once on component mount, ensuring that + * the login state is correctly set based on the presence of a specific item in local storage. + * + * @returns {JSX.Element} Renders the `AppContainer` wrapped within a div with a class of 'app', + * serving as the root of the user interface. + * + * This component interacts with Redux by dispatching actions to modify the global state, particularly + * the logged-in status of the user. This is central for managing conditional rendering and access + * throughout the application based on user authentication status. + */ export const App: React.FC = (): JSX.Element => { const dispatch = useDispatch(); useEffect(() => { diff --git a/app/src/components/ContextAPIManager/AssignTab/AssignContainer.tsx b/app/src/components/ContextAPIManager/AssignTab/AssignContainer.tsx index ff1d5e4f4..3880e726e 100644 --- a/app/src/components/ContextAPIManager/AssignTab/AssignContainer.tsx +++ b/app/src/components/ContextAPIManager/AssignTab/AssignContainer.tsx @@ -13,7 +13,24 @@ import { deleteElement } from '../../../redux/reducers/slice/appStateSlice'; import { RootState } from '../../../redux/store'; import { emitEvent } from '../../../../src/helperFunctions/socket'; -const AssignContainer = () => { +/** + * Provides an interface for assigning components to contexts within an application. + * The component allows the selection of contexts and components through dropdown menus, + * displays related data in tables, and permits the assignment of components to selected contexts. + * It leverages Redux for state management and may trigger socket events for real-time updates across sessions. + * + * This component integrates several subcomponents: + * - `ContextDropDown` for selecting contexts which triggers updates to the data table. + * - `DataTable` for displaying key-value pairs related to the selected context. + * - `ComponentDropDown` for selecting components which triggers updates to the component table. + * - `ComponentTable` for displaying a list of contexts associated with a selected component. + * - A button for assigning the selected component to the selected context, potentially emitting socket events if a room code is present. + * + * The state management involves interaction with the Redux store to fetch state information and dispatch actions related to context and component management. + * + * @returns {JSX.Element} A React component structured with a Grid layout, integrating forms and tables for managing and viewing context and component assignments. + */ +const AssignContainer = (): JSX.Element => { const dispatch = useDispatch(); const defaultTableData = [{ key: 'Key', value: 'Value' }]; const [tableState, setTableState] = React.useState(defaultTableData); diff --git a/app/src/components/ContextAPIManager/AssignTab/components/ComponentDropDrown.tsx b/app/src/components/ContextAPIManager/AssignTab/components/ComponentDropDrown.tsx index b06802471..ef105197f 100644 --- a/app/src/components/ContextAPIManager/AssignTab/components/ComponentDropDrown.tsx +++ b/app/src/components/ContextAPIManager/AssignTab/components/ComponentDropDrown.tsx @@ -7,11 +7,26 @@ import { RootState } from '../../../../redux/store'; const filter = createFilterOptions(); +/** + * Renders an autocomplete dropdown list that allows the user to select or enter a component. + * When a component is selected or entered, this triggers a specified callback to render the component table. + * The dropdown uses a custom filter for suggestions, allowing users to add components not already listed in the options. + * + * @param {Object} props - The props passed to the ComponentDropDown component. + * @param {Function} props.renderComponentTable - Callback function that is triggered to render the component table based on the selected component. + * @param {Object|null} props.componentInput - The currently selected component object or null if nothing is selected. + * @param {Function} props.setComponentInput - Sets the state of the componentInput in the parent component. + * + * Redux State Dependencies: + * - `appState`: Expects `appState.components` from the Redux store to provide the list of available components. + * + * @returns {JSX.Element} A React Fragment that includes a Box containing the Autocomplete component which provides a dropdown for selecting components. + */ const ComponentDropDown = ({ renderComponentTable, componentInput, setComponentInput -}) => { +}): JSX.Element => { const { state } = useSelector((store: RootState) => ({ state: store.appState })); @@ -70,7 +85,7 @@ const ComponentDropDown = ({ return ( - + ({ [`&.${tableCellClasses.head}`]: { backgroundColor: theme.palette.common.black, @@ -18,6 +26,14 @@ const StyledTableCell = styled(TableCell)(({ theme }) => ({ } })); +/** + * Styles the TableRow component to enhance table row appearance. Custom styles include: + * - Every odd row is styled with a background color for hover state. + * - Removes the border from the last child table cells and headers to enhance appearance. + * + * @param {object} theme - The theme object provided by Material-UI's ThemeProvider. + * @returns {JSX.Element} A styled TableRow component with alternate row coloring and modified border visibility. + */ const StyledTableRow = styled(TableRow)(({ theme }) => ({ '&:nth-of-type(odd)': { backgroundColor: theme.palette.action.hover @@ -28,6 +44,16 @@ const StyledTableRow = styled(TableRow)(({ theme }) => ({ } })); +/** + * A styled data table component that displays a list of items in a single column format. + * This component uses Material-UI to create a visually distinct table with styled cells and rows. + * It is specifically designed to display 'contexts consumed' but can be generalized for other single-column data. + * + * @param {Object} props - Component props. + * @param {Array} props.target - The data array containing strings to be displayed in the table. + * + * @returns {JSX.Element} A TableContainer component housing a Table, where each row displays an element from the `target` prop. + */ export default function DataTable({ target }) { return ( diff --git a/app/src/components/ContextAPIManager/AssignTab/components/ContextDropDown.tsx b/app/src/components/ContextAPIManager/AssignTab/components/ContextDropDown.tsx index f1591ebe4..582305c59 100644 --- a/app/src/components/ContextAPIManager/AssignTab/components/ContextDropDown.tsx +++ b/app/src/components/ContextAPIManager/AssignTab/components/ContextDropDown.tsx @@ -3,15 +3,27 @@ import TextField from '@mui/material/TextField'; import Autocomplete, { createFilterOptions } from '@mui/material/Autocomplete'; import Box from '@mui/material/Box'; - const filter = createFilterOptions(); +/** + * A React component that provides an autocomplete dropdown for selecting or adding new contexts. + * It integrates with the contextStore to list available contexts and allows the user to create a new context by entering a unique name. + * When a context is selected or a new one is entered, the specified `renderTable` function is triggered to reflect changes elsewhere in the UI. + * + * @param {Object} props - Component props. + * @param {Object} props.contextStore - The store holding all context data. + * @param {Function} props.renderTable - Function to call when a context is selected or created to update associated data display. + * @param {Object|null} props.contextInput - The currently selected or entered context. + * @param {Function} props.setContextInput - Function to update the contextInput state. + * + * @returns {JSX.Element} A React fragment containing an Autocomplete component wrapped in a Box for layout adjustments. + */ const ContextDropDown = ({ contextStore, renderTable, contextInput, setContextInput -}) => { +}): JSX.Element => { const { allContext } = contextStore; const onChange = (event, newValue) => { diff --git a/app/src/components/ContextAPIManager/AssignTab/components/ContextTable.tsx b/app/src/components/ContextAPIManager/AssignTab/components/ContextTable.tsx index 5906b3093..286c48005 100644 --- a/app/src/components/ContextAPIManager/AssignTab/components/ContextTable.tsx +++ b/app/src/components/ContextAPIManager/AssignTab/components/ContextTable.tsx @@ -8,6 +8,14 @@ import TableHead from '@mui/material/TableHead'; import TableRow from '@mui/material/TableRow'; import Paper from '@mui/material/Paper'; +/** + * Styles the TableCell component using Material-UI's styling system. Custom styles are applied to table head and body cells: + * - Head cells are styled with a black background and white text. + * - Body cells have a font size of 14. + * + * @param {object} theme - The theme object provided by Material-UI's ThemeProvider. + * @returns {JSX.Element} A styled TableCell component with customized appearance. + */ const StyledTableCell = styled(TableCell)(({ theme }) => ({ [`&.${tableCellClasses.head}`]: { backgroundColor: theme.palette.common.black, @@ -18,6 +26,14 @@ const StyledTableCell = styled(TableCell)(({ theme }) => ({ } })); +/** + * Styles the TableRow component to enhance table row appearance. Custom styles include: + * - Every odd row is styled with a background color for hover state. + * - Removes the border from the last child table cells and headers to enhance appearance. + * + * @param {object} theme - The theme object provided by Material-UI's ThemeProvider. + * @returns {JSX.Element} A styled TableRow component with alternate row coloring and modified border visibility. + */ const StyledTableRow = styled(TableRow)(({ theme }) => ({ '&:nth-of-type(odd)': { backgroundColor: theme.palette.action.hover @@ -28,6 +44,16 @@ const StyledTableRow = styled(TableRow)(({ theme }) => ({ } })); +/** + * Constructs a data object for table rows. This function simplifies the creation of data entries for the table. + * + * @param {string} name - The name field of the data entry. + * @param {number} calories - Numeric data typically representing calorie count. + * @param {number} fat - Numeric data typically representing fat content. + * @param {number} carbs - Numeric data typically representing carbohydrate content. + * @param {number} protein - Numeric data typically representing protein content. + * @returns {object} An object representing a single row of data suitable for insertion into a table. + */ function createData( name: string, calories: number, @@ -46,7 +72,18 @@ const rows = [ createData('Gingerbread', 356, 16.0, 49, 3.9) ]; -export default function ContextTable() { +/** + * Displays a styled table with context and component data using Material-UI components. + * This component uses custom styled table cells and rows to enhance visual presentation. + * The data is statically defined in the component and includes fields for context names and corresponding component data. + * + * The table is intended to display any list of data that fits the structure of contexts and their associated components, + * and it features alternating row colors and a custom style for headers and body cells to align with a theme. + * + * @returns {JSX.Element} A TableContainer component that houses a Table with two columns: Context and Component. + * Each row displays the name of the context and a numeric value representing associated component data. + */ +export default function ContextTable(): JSX.Element { return ( diff --git a/app/src/components/ContextAPIManager/ContextManager.tsx b/app/src/components/ContextAPIManager/ContextManager.tsx index 8927f9dc0..024a01f55 100644 --- a/app/src/components/ContextAPIManager/ContextManager.tsx +++ b/app/src/components/ContextAPIManager/ContextManager.tsx @@ -18,6 +18,17 @@ const useStyles = makeStyles({ } }); +/** + * Manages and displays tabs for creating, assigning, and displaying contexts in a React application. + * Utilizes Material-UI's TabContext for tab management. This component allows users to switch between + * different functionalities related to context manipulation within the application, such as creating or editing, + * assigning, and displaying contexts. + * + * @returns {JSX.Element} - A component structure with tabs managing different context-related containers. + * Each tab switches to a respective panel that loads a specific container for creating/editing contexts, + * assigning contexts, or displaying contexts. The active tab is highlighted, and each tab panel contains + * specific functionalities encapsulated in the respective container components. + */ const ContextManager = (props): JSX.Element => { const style = useSelector((store: RootState) => store.styleSlice); const classes = useStyles(); diff --git a/app/src/components/ContextAPIManager/CreateTab/CreateContainer.tsx b/app/src/components/ContextAPIManager/CreateTab/CreateContainer.tsx index 2bc87a598..30065270f 100644 --- a/app/src/components/ContextAPIManager/CreateTab/CreateContainer.tsx +++ b/app/src/components/ContextAPIManager/CreateTab/CreateContainer.tsx @@ -14,7 +14,41 @@ import { useSelector, useDispatch } from 'react-redux'; import { RootState } from '../../../redux/store'; import { emitEvent } from '../../../../src/helperFunctions/socket'; -const CreateContainer = () => { +/** + * A React component that provides an interface for managing contexts and their key-value pairs within a Redux state. + * It allows users to add new contexts, delete existing ones, and add key-value pairs to specific contexts. This component also supports + * real-time synchronization with other clients through socket events if a room code is present. + * + * Uses `AddContextForm` and `AddDataForm` components for input forms, and `DataTable` to display the current context's key-value pairs. + * + * @component + * @example + * ```jsx + * + * ``` + * + * State: + * - `contextInput`: Tracks the user's input for adding a new context. + * - `currentContext`: Stores the currently selected context for which data is being displayed or modified. + * - `errorMsg`: Contains the error message to display based on validation failure. + * - `errorStatus`: Boolean to indicate whether an error is present. + * + * Redux State Dependencies: + * - `contextSlice`: Contains all contexts and their respective key-value pairs. + * - `roomSlice`: Contains the current room code which is used to identify the room in socket communications. + * + * Socket Events: + * - Emits events for adding or deleting contexts, and adding key-value pairs to a context, if a room code is present. + * + * Methods: + * - `handleClickSelectContext`: Validates the new context name and dispatches actions to add a context in the Redux state and through socket events. + * - `handleClickInputData`: Dispatches actions to add a new key-value pair to the selected context in the Redux state and through socket events. + * - `handleDeleteContextClick`: Dispatches actions to delete the selected context from the Redux state and through socket events. + * - `triggerError`: Sets error messages based on the type of input validation error. + * + * @returns {JSX.Element} A React component structured with Grid layout, featuring forms for input and a data table for displaying contexts. + */ +const CreateContainer = (): JSX.Element => { const state = useSelector((store: RootState) => store.contextSlice); const roomCode = useSelector((store: RootState) => store.roomSlice.roomCode); diff --git a/app/src/components/ContextAPIManager/CreateTab/components/AddContextForm.tsx b/app/src/components/ContextAPIManager/CreateTab/components/AddContextForm.tsx index beae68878..1323fc45f 100644 --- a/app/src/components/ContextAPIManager/CreateTab/components/AddContextForm.tsx +++ b/app/src/components/ContextAPIManager/CreateTab/components/AddContextForm.tsx @@ -10,6 +10,27 @@ import { InputLabel, MenuItem, Typography } from '@mui/material'; import { useSelector } from 'react-redux'; import { RootState } from '../../../../redux/store'; +/** + * Provides a form interface for creating new contexts or selecting existing ones within an application. + * Users can input a new context name and submit it for creation or select from a list of existing contexts. + * This component also displays feedback through a Snackbar when a new context is successfully created, + * and handles the error messaging related to context creation. + * + * @param {Object} props - Component props. + * @param {Object} props.contextStore - Contains the state slice from Redux store that holds context data. + * @param {Function} props.handleClickSelectContext - Function to call when submitting a new context. + * @param {Function} props.handleDeleteContextClick - Function to call when a context is selected for deletion. + * @param {string} props.contextInput - The current input value for the new context name. + * @param {Function} props.setContextInput - Function to update the contextInput state. + * @param {string} props.currentContext - The currently selected context. + * @param {Function} props.setCurrentContext - Function to update the currentContext state. + * @param {string} props.errorMsg - Error message to display in the text field helper text if there's an error. + * @param {boolean} props.errorStatus - Boolean indicating if an error exists. + * @param {Function} props.setErrorStatus - Function to update the errorStatus state. + * + * @returns {JSX.Element} The component returns a set of React elements that include input fields, buttons, and a snackbar for feedback. + * + */ const AddContextForm = ({ contextStore, handleClickSelectContext, diff --git a/app/src/components/ContextAPIManager/CreateTab/components/AddDataForm.tsx b/app/src/components/ContextAPIManager/CreateTab/components/AddDataForm.tsx index de38a087f..c18a21c73 100644 --- a/app/src/components/ContextAPIManager/CreateTab/components/AddDataForm.tsx +++ b/app/src/components/ContextAPIManager/CreateTab/components/AddDataForm.tsx @@ -6,7 +6,26 @@ import { Typography } from '@mui/material'; // import { useSelector } from 'react-redux'; // import { RootState } from '../../../../redux/store'; -const AddDataForm = ({ handleClickInputData, currentContext }) => { +/** + * Renders a form to add key-value pairs to a specified context. The form includes two text fields for the key and value, + * and a button to submit the data. Upon submission, if any field is empty, an alert is shown; otherwise, the data is passed + * to the `handleClickInputData` callback function provided by the parent component. + * + * @param {{ handleClickInputData: Function, currentContext: string }} props + * - `handleClickInputData`: A function to handle the submission of key-value pairs. It takes the current context and an object with key and value. + * - `currentContext`: The name of the context to which the key-value pairs are being added. This is used when submitting the form. + * + * @returns {JSX.Element} A form with two text fields for entering a key and a value, and a button to submit the form. + * + * @example + * ```jsx + * console.log(context, data)} + * currentContext="Example Context" + * /> + * ``` + */ +const AddDataForm = ({ handleClickInputData, currentContext }): JSX.Element => { //const [contextInput, setContextInput] = React.useState(null); const defaultInputData = { inputKey: '', inputValue: '' }; const [dataContext, setDataContext] = React.useState(defaultInputData); diff --git a/app/src/components/ContextAPIManager/CreateTab/components/DataTable.tsx b/app/src/components/ContextAPIManager/CreateTab/components/DataTable.tsx index 2f9bd24d8..848caf9bc 100644 --- a/app/src/components/ContextAPIManager/CreateTab/components/DataTable.tsx +++ b/app/src/components/ContextAPIManager/CreateTab/components/DataTable.tsx @@ -8,6 +8,14 @@ import Paper from '@mui/material/Paper'; import { styled } from '@mui/material/styles'; import TableCell, { tableCellClasses } from '@mui/material/TableCell'; +/** + * Custom styled table cell component using Material-UI's styling system. Applies specific styles to table head and body cells. + * - Head cells (`TableCell.head`) are styled with a black background and white text color. + * - Body cells (`TableCell.body`) are set with a font size of 14px. + * + * @param {Object} props - Inherits all the props from the Material-UI TableCell component. + * @returns {JSX.Element} A TableCell component with applied styles. + */ const StyledTableCell = styled(TableCell)(({ theme }) => ({ [`&.${tableCellClasses.head}`]: { backgroundColor: theme.palette.common.black, @@ -18,6 +26,14 @@ const StyledTableCell = styled(TableCell)(({ theme }) => ({ } })); +/** + * Custom styled table row component using Material-UI's styling system. Applies conditional styles to table rows. + * - Every odd row is given a hover background color from the theme. + * - Removes the border from the last child table cells and table headers in a row to hide the last border. + * + * @param {Object} props - Inherits all the props from the Material-UI TableRow component. + * @returns {JSX.Element} A TableRow component with applied styles. + */ const StyledTableRow = styled(TableRow)(({ theme }) => ({ '&:nth-of-type(odd)': { backgroundColor: theme.palette.action.hover @@ -28,7 +44,25 @@ const StyledTableRow = styled(TableRow)(({ theme }) => ({ } })); -export default function DataTable({ target, currentContext }) { +/** + * Displays a styled data table using MUI components. This table is used to show key-value pairs for a given context. + * It receives data as props and presents it in a scrollable and sticky-header table format. + * + * @param {{ target: Array<{ key: string; value: string }>, currentContext: string }} props + * - `target`: An array of objects with 'key' and 'value' properties representing the data to be displayed. + * - `currentContext`: The name of the context currently being displayed. If not provided, defaults to 'Context Name'. + * + * @returns {JSX.Element} A component that renders a table with two columns: one for keys and one for their corresponding values. + * + * @example + * ```jsx + * + * ``` + */ +export default function DataTable({ target, currentContext }): JSX.Element { return ( <> @@ -39,7 +73,11 @@ export default function DataTable({ target, currentContext }) { > - + {currentContext ? currentContext : 'Context Name'} @@ -55,7 +93,11 @@ export default function DataTable({ target, currentContext }) { > {data.key} - + {data.value} diff --git a/app/src/components/ContextAPIManager/DisplayTab/DisplayContainer.tsx b/app/src/components/ContextAPIManager/DisplayTab/DisplayContainer.tsx index 3d5b59b19..4cf40fae3 100644 --- a/app/src/components/ContextAPIManager/DisplayTab/DisplayContainer.tsx +++ b/app/src/components/ContextAPIManager/DisplayTab/DisplayContainer.tsx @@ -4,7 +4,35 @@ import { Chart } from 'react-google-charts'; import Grid from '@mui/material/Grid'; import { RootState } from '../../../redux/store'; -const DisplayContainer = () => { +/** + * A React component that displays a WordTree chart based on context data from a Redux store. + * Utilizes the `react-google-charts` library to visualize relationships between components in the application, + * fetching data from the `contextSlice.allContext` in the Redux store. The data should include objects with names and components arrays. + * + * @component + * @example + * ```jsx + * + * ``` + * + * @param {Object} props - The component does not accept any props. + * + * State: + * - `contextData`: An array of arrays structured for the WordTree chart, with the initial element being ["Phrases"]. + * + * Redux State Dependencies: + * - `allContext`: Retrieved from `contextSlice.allContext`, expected to be an array of objects each containing a `name` and a `components` array. + * + * Effects: + * - On mount, transforms the Redux store data into a format suitable for the WordTree chart by calling `transformData`. + * + * Methods: + * - `transformData`: Transforms raw context data from the Redux store to be usable by the `Chart` component, + * mapping each context object to phrases connecting the application name with the context name and its components. + * + * @returns {JSX.Element} A React element containing a Grid layout. If data is available, displays a WordTree chart, otherwise shows a message indicating no data. + */ +const DisplayContainer = (): JSX.Element => { const allContext = useSelector( (store: RootState) => store.contextSlice.allContext ); diff --git a/app/src/components/StateManagement/CreateTab/CreateContainer.tsx b/app/src/components/StateManagement/CreateTab/CreateContainer.tsx index cdd6b324f..2775d856f 100644 --- a/app/src/components/StateManagement/CreateTab/CreateContainer.tsx +++ b/app/src/components/StateManagement/CreateTab/CreateContainer.tsx @@ -2,12 +2,36 @@ import React from 'react'; import Grid from '@mui/material/Grid'; import StatePropsPanel from './components/StatePropsPanel'; -const CreateContainer = ({isThemeLight, data}) => { - +/** + * `CreateContainer` is a React functional component that renders a container for managing the creation or configuration + * of component properties and state within an application. It is primarily used as a layout container for the `StatePropsPanel`. + * + * @param {Object} props - Properties passed to the component. + * @param {boolean} props.isThemeLight - Indicates if the application's theme is light. This is used to adjust the visual styling of the contents. + * @param {Array} props.data - The data passed to the `StatePropsPanel`, typically representing the application's components or elements. + * @returns {JSX.Element} A `Grid` container from Material-UI that includes a `StatePropsPanel` component, configured for managing and displaying component properties and state based on the passed `isThemeLight` and `data` props. + * + * This component uses Material-UI's `Grid` to create a flexible column layout that adjusts based on its content. It serves as a wrapper + * for the `StatePropsPanel` to provide consistent spacing and alignment. + * + * Example Usage: + * ```jsx + * + * ``` + */ +const CreateContainer = ({ isThemeLight, data }) => { return ( - - - + + + ); }; diff --git a/app/src/components/StateManagement/CreateTab/components/StatePropsPanel.tsx b/app/src/components/StateManagement/CreateTab/components/StatePropsPanel.tsx index f43c57d31..7ffa7c11f 100644 --- a/app/src/components/StateManagement/CreateTab/components/StatePropsPanel.tsx +++ b/app/src/components/StateManagement/CreateTab/components/StatePropsPanel.tsx @@ -18,6 +18,17 @@ import TablePassedInProps from './TablePassedInProps'; import { RootState } from '../../../../redux/store'; import { emitEvent } from '../../../../helperFunctions/socket'; +/** + * `StatePropsPanel` is a React functional component that provides an interface for managing state properties of components within an application. + * This component allows users to create, modify, and view state properties, as well as linking properties from parent components. + * + * @param {Object} props - Properties passed to the component. + * @param {boolean} props.isThemeLight - Indicates if the current theme is light, which influences the styling of the UI components. + * @param {Array} props.data - The data related to components from which the state properties are derived or linked. + * + * @returns {JSX.Element} A `div` element containing various form controls for inputting new state properties, a button for submission, + * and tables that display the current state properties, parent props, and props passed in from parent components. + */ const StatePropsPanel = ({ isThemeLight, data }): JSX.Element => { const state = useSelector((store: RootState) => store.appState); const contextParam = useSelector((store: RootState) => store.contextSlice); diff --git a/app/src/components/StateManagement/CreateTab/components/TableParentProps.tsx b/app/src/components/StateManagement/CreateTab/components/TableParentProps.tsx index cf8a1567f..8b617b567 100644 --- a/app/src/components/StateManagement/CreateTab/components/TableParentProps.tsx +++ b/app/src/components/StateManagement/CreateTab/components/TableParentProps.tsx @@ -8,7 +8,21 @@ import { addPassedInProps } from '../../../../redux/reducers/slice/appStateSlice import { RootState } from '../../../../redux/store'; import { emitEvent } from '../../../../helperFunctions/socket'; -const TableParentProps = (props) => { +/** + * `TableParentProps` is a React component that renders a table displaying properties passed from parent components. + * It provides functionality to add these properties to the current component's state using buttons in the table. + * The table is rendered using Material-UI's DataGrid component. + * + * @param {Object} props - Properties passed to the component. + * @param {boolean} props.isThemeLight - Indicates if the current theme is light or dark, used to adjust the visual styling of the table. + * @param {Array} props.parentProps - Array of properties inherited from the parent component. + * @param {Array} props.parentPassedInProps - Additional properties passed to the parent component. + * @param {Object} props.parentComponent - The parent component object which includes details like name, type, etc. + * @param {boolean} props.canDeleteState - Flag indicating whether deletion of state is allowed. + * + * @returns {JSX.Element} The `DataGrid` component filled with parent properties and controls to manage them. + */ +const TableParentProps = (props): JSX.Element => { const state = useSelector((store: RootState) => store.appState); const contextParam = useSelector((store: RootState) => store.contextSlice); const roomCode = useSelector((store: RootState) => store.roomSlice.roomCode); diff --git a/app/src/components/StateManagement/CreateTab/components/TablePassedInProps.tsx b/app/src/components/StateManagement/CreateTab/components/TablePassedInProps.tsx index 46d1db99a..f328fb9bc 100644 --- a/app/src/components/StateManagement/CreateTab/components/TablePassedInProps.tsx +++ b/app/src/components/StateManagement/CreateTab/components/TablePassedInProps.tsx @@ -9,7 +9,17 @@ import { RootState } from '../../../../redux/store'; import { ColumnTab } from '../../../../interfaces/Interfaces'; import { emitEvent } from '../../../../helperFunctions/socket'; -const TablePassedInProps = (props) => { +/** + * `TablePassedInProps` renders a data grid (using Material-UI's DataGrid) displaying properties passed into a component. + * It allows for the deletion of these properties through an interactive table interface. The component adapts its style based on the theme. + * + * @param {Object} props - Properties passed to the component. + * @param {boolean} props.isThemeLight - Indicates if the theme is light, affecting the styling of the DataGrid. + * @param {boolean} props.canDeleteState - Indicates whether the delete functionality should be enabled for passed-in properties. + * + * @returns {JSX.Element} A styled DataGrid component containing rows of passed-in properties with functionality to delete them. + */ +const TablePassedInProps = (props): JSX.Element => { const state = useSelector((store: RootState) => store.appState); const contextParam = useSelector((store: RootState) => store.contextSlice); const roomCode = useSelector((store: RootState) => store.roomSlice.roomCode); diff --git a/app/src/components/StateManagement/CreateTab/components/TableStateProps.tsx b/app/src/components/StateManagement/CreateTab/components/TableStateProps.tsx index 26f8666e2..1880d2e68 100644 --- a/app/src/components/StateManagement/CreateTab/components/TableStateProps.tsx +++ b/app/src/components/StateManagement/CreateTab/components/TableStateProps.tsx @@ -10,8 +10,19 @@ import { RootState } from '../../../../redux/store'; import { ColumnTab } from '../../../../interfaces/Interfaces'; import { emitEvent } from '../../../../helperFunctions/socket'; -// updates state mgmt boxes and data grid -const TableStateProps = (props) => { +/** + * `TableStateProps` is a React component that displays a data grid for managing state properties within an application. + * The component allows for the interactive addition, deletion, and modification of state properties directly within a table format. + * The component adapts its style and functionality based on the current theme and the ability to delete state entries. + * + * @param {Object} props - Properties passed to the component. + * @param {boolean} props.isThemeLight - Determines the theme of the data grid (light or dark). + * @param {boolean} props.canDeleteState - Indicates whether the deletion of state entries is allowed. + * @param {Function} props.selectHandler - Function called when a row is clicked, typically used to handle row selection. + * + * @returns {JSX.Element} A data grid component that displays and allows interaction with state properties of a component. + */ +const TableStateProps = (props): JSX.Element => { const state = useSelector((store: RootState) => store.appState); const contextParam = useSelector((store: RootState) => store.contextSlice); const roomCode = useSelector((store: RootState) => store.roomSlice.roomCode); diff --git a/app/src/components/StateManagement/DisplayTab/DataTable.tsx b/app/src/components/StateManagement/DisplayTab/DataTable.tsx index 6d066e906..2673afbcf 100644 --- a/app/src/components/StateManagement/DisplayTab/DataTable.tsx +++ b/app/src/components/StateManagement/DisplayTab/DataTable.tsx @@ -8,34 +8,82 @@ import Paper from '@mui/material/Paper'; import { styled } from '@mui/material/styles'; import TableCell, { tableCellClasses } from '@mui/material/TableCell'; import { useSelector } from 'react-redux'; -import { RootState } from '../../../redux/store' +import { RootState } from '../../../redux/store'; +/** + * `StyledTableCell` is a styled version of the MUI `TableCell` component. It applies custom styling + * to table cells to differentiate between header cells and body cells. + * + * - Header cells (`thead`) are styled with a black background and white text. + * - Body cells (`tbody`) are styled with a default black text color and a font size of 14px. + * + * This component is typically used within `Table` components to ensure that the table adheres to + * a consistent theming and layout that matches the rest of the application's design. + * + * @param {Object} theme - The theme object provided by Material-UI's `ThemeProvider`. + * @returns {React.Component} A `TableCell` component with custom styles applied. + */ const StyledTableCell = styled(TableCell)(({ theme }) => ({ [`&.${tableCellClasses.head}`]: { backgroundColor: theme.palette.common.black, - color: theme.palette.common.white, + color: theme.palette.common.white }, [`&.${tableCellClasses.body}`]: { color: theme.palette.common.black, - fontSize: 14, - }, + fontSize: 14 + } })); +/** + * `StyledTableRow` is a styled version of the MUI `TableRow` component. It enhances the visual + * presentation of table rows with the following styles: + * + * - Odd rows are given a slightly different background color to improve readability by alternating row colors. + * - The last child (last row) of the table has no bottom border, creating a cleaner look at the end of the table. + * + * This component is typically used within the `TableBody` component of a `Table` to provide a consistent + * and more readable styling for table rows. + * + * @param {Object} theme - The theme object provided by Material-UI's `ThemeProvider`. + * @returns {React.Component} A `TableRow` component with custom styles applied. + */ const StyledTableRow = styled(TableRow)(({ theme }) => ({ '&:nth-of-type(odd)': { backgroundColor: theme.palette.action.hover }, // hide last border '&:last-child td, &:last-child th': { - border: 0, - }, + border: 0 + } })); -export default function DataTable(props) { - const { - currComponentState, parentProps, clickedComp, data, - } = props; - const state = useSelector((store:RootState) => store.appState) +/** + * `DataTable` is a React component that renders a detailed view of the properties and state + * associated with a specific component selected by the user. It displays this information in + * two separate sections within a table: one for the properties passed from the parent component, + * and another for the state initialized within the current component itself. + * + * The component uses Material-UI's styled components for styling individual cells and rows, + * and it conditionally renders sections based on whether the selected component is a root component. + * It leverages Redux to access the global state to check if the current component is a root component. + * + * @param {Object} props - Properties passed to the component including: + * @param {Array} props.currComponentState - Array of objects describing the state within the current component. + * @param {Array} props.parentProps - Array of objects describing the properties passed from the parent. + * @param {string} props.clickedComp - The name of the component that was clicked. + * @param {Array} props.data - The full data array representing all components. + * @returns {JSX.Element} A table rendered inside a scrollable container that lists properties and state details. + * + * The table is split into two sections if the selected component is not a root: + * 1. "Props Passed in from Parent:" - Lists the keys, types, and initial values of properties passed from the parent. + * 2. "State Initialized in Current Component:" - Lists the keys, types, and initial values of the state initialized in the component. + * + * This component enhances the user interface by providing clear and accessible information regarding + * component configurations, aiding developers in understanding and managing the application structure. + */ +export default function DataTable(props): JSX.Element { + const { currComponentState, parentProps, clickedComp, data } = props; + const state = useSelector((store: RootState) => store.appState); // determine if the current component is a root component let isRoot = false; @@ -48,64 +96,100 @@ export default function DataTable(props) { return ( -
- +
{/* we are checking if the clicked component is a root component-- if yes, it doesn't have any parents so don't need passed-in props table */} - {(!isRoot - && ( - <> - - - - Props Passed in from Parent: - - - - - - Key - Type - Initial Value - - {parentProps ? parentProps.map((data, index) => ( - - {data.key} - {data.type} - {data.value} - - )) : ''} - - - ) + {!isRoot && ( + <> + + + + Props Passed in from Parent: + + + + + + + Key + + + Type + + + Initial Value + + + {parentProps + ? parentProps.map((data, index) => ( + + + {data.key} + + + {data.type} + + + {data.value} + + + )) + : ''} + + )} {/* The below table will contain the state initialized within the clicked component */} - + State Initialized in Current Component: - Key - Type - Initial Value + + Key + + + Type + + + Initial Value + - {currComponentState ? currComponentState.map((data, index) => ( - - {data.key} - {data.type} - {data.value} - - )) : ''} + {currComponentState + ? currComponentState.map((data, index) => ( + + + {data.key} + + + {data.type} + + + {data.value} + + + )) + : ''}
); -} \ No newline at end of file +} diff --git a/app/src/components/StateManagement/DisplayTab/DisplayContainer.tsx b/app/src/components/StateManagement/DisplayTab/DisplayContainer.tsx index 22a7d8ff1..7fb925444 100644 --- a/app/src/components/StateManagement/DisplayTab/DisplayContainer.tsx +++ b/app/src/components/StateManagement/DisplayTab/DisplayContainer.tsx @@ -7,7 +7,27 @@ import Tree from './Tree'; import { useSelector } from 'react-redux'; import { RootState } from '../../../redux/store'; -function DisplayContainer({ data, props }) { +/** + * `DisplayContainer` is a React component that provides an interactive visual representation + * of the state architecture of an application using a tree graph. Users can click on nodes + * in the tree graph to view detailed state information for each component in a `DataTable`. + * + * This component uses Redux to access global state to determine which component is currently + * in focus and dynamically update the display based on user interactions with the tree graph. + * It manages local state for the current component's state details and its parent's props, + * which are passed down to child components for further interaction and display. + * + * @param {Object} props - Properties passed to the component including: + * @param {Array} props.data - The hierarchical data used to generate the tree structure, typically derived from the application's state. + * @param {Object} props.props - Additional props that might be necessary for child components or additional functionality. + * @returns {JSX.Element} A split view containing a `Tree` visualization on the left and a `DataTable` on the right + * that updates based on the node selected in the `Tree`. + * + * The `DisplayContainer` is designed to provide a comprehensive overview of the component structure of an application, + * allowing developers to navigate and manage the state and props relationships visually. This component is part of a larger + * state management feature within an application that may involve complex state logic and architecture. + */ +function DisplayContainer({ data, props }): JSX.Element { // "data" is referring to components from state - passed in from StateManagement // grabbing intialized state from App using UseContext const [currComponentState, setCurrComponentState] = useState([]); @@ -41,7 +61,7 @@ function DisplayContainer({ data, props }) { setClickedComp={setClickedComp} /> - + ref.current = value); + useEffect(() => (ref.current = value)); return ref.current; } -function Tree({ - data, setCurrComponentState, setParentProps, setClickedComp, -}) { - const state = useSelector((store:RootState) => store.appState) +/** + * `Tree` is a React component for visualizing the hierarchical structure of components in an application. + * It uses D3 to create a tree diagram that users can interact with to understand the relationship between components + * and to inspect the state and props of each component. Clicking on a node in the tree diagram updates the state + * and props information displayed elsewhere in the application. + * + * @param {Object} props - The props passed to the component. + * @param {Array} props.data - The array of component data from the application's state. Each element represents a component + * and includes details like the component's name, its child components, and its state and props. + * @param {Function} props.setCurrComponentState - Function to set the current component's state in the outer component's state. + * @param {Function} props.setParentProps - Function to set the parent props in the outer component's state. + * @param {Function} props.setClickedComp - Function to update the currently selected component in the outer component's state. + * + * This component is designed to be used in applications where understanding the component architecture visually can help + * in debugging and development. It leverages Redux to manage global state access and D3 for rendering the tree graph. + * + * Usage Example: + * ```jsx + * + * ``` + * + * Note: + * The component uses SVG to render the tree and implements interactive features such as clicking on nodes to trigger state updates. + * Ensure that the data passed to this component is correctly formatted to represent the hierarchical structure of components. + */ +function Tree({ data, setCurrComponentState, setParentProps, setClickedComp }) { + const state = useSelector((store: RootState) => store.appState); // Provide types for the refs. // In this case HTMLDivElement for the wrapperRef and SVGSVGElement for the svgRef. // create mutable ref objects with initial values of null @@ -40,7 +66,10 @@ function Tree({ // if element has a children array and that array has length, recursive call else if (arr[i].type === 'Component' && arr[i].children.length) { // if element is a component, replace it with deep clone of latest version (to update with new HTML elements) - if (arr[i].type === 'Component') arr[i] = cloneDeep(data.find((component) => component.name === arr[i].name)); + if (arr[i].type === 'Component') + arr[i] = cloneDeep( + data.find((component) => component.name === arr[i].name) + ); removeHTMLElements(arr[i].children); } } @@ -52,12 +81,15 @@ function Tree({ if (state.projectType === 'Next.js') { dataDeepClone.forEach((element) => { - element.children = sanitize(element.children).filter((element) => !Array.isArray(element)); + element.children = sanitize(element.children).filter( + (element) => !Array.isArray(element) + ); }); function sanitize(children) { return children.map((child) => { - if (child.name === 'Switch' || child.name === 'Route') return sanitize(child.children); + if (child.name === 'Switch' || child.name === 'Route') + return sanitize(child.children); return child; }); } @@ -72,8 +104,9 @@ function Tree({ // use dimensions from useResizeObserver, // but use getBoundingClientRect on initial render // (dimensions are null for the first render) - - const { width, height } = dimensions || wrapperRef.current.getBoundingClientRect(); + + const { width, height } = + dimensions || wrapperRef.current.getBoundingClientRect(); // transform hierarchical data let root; @@ -90,7 +123,7 @@ function Tree({ } } } else { - // if no, set root of tree to be app/index + // if no, set root of tree to be app/index root = hierarchy(dataDeepClone[0]); rootName = dataDeepClone[0].name; } @@ -181,7 +214,7 @@ function Tree({ width: '100%', margin: '10px 10px 10px 10px', overflow: 'auto', - alignItems: 'center', + alignItems: 'center' }; const wrapperStyles = { diff --git a/app/src/components/StateManagement/DisplayTab/useResizeObserver.ts b/app/src/components/StateManagement/DisplayTab/useResizeObserver.ts index d9adf16b1..37b1ea270 100644 --- a/app/src/components/StateManagement/DisplayTab/useResizeObserver.ts +++ b/app/src/components/StateManagement/DisplayTab/useResizeObserver.ts @@ -1,7 +1,45 @@ import { useEffect, useState } from 'react'; import ResizeObserver from 'resize-observer-polyfill'; -const useResizeObserver = (ref) => { +/** + * A custom React hook that allows you to monitor changes in the dimensions of an HTML element. + * It uses the `ResizeObserver` API to observe the specified element and updates the state + * with the new dimensions whenever a change is detected. + * + * This hook is useful for responsive components that need to adjust their layout or functionality + * based on their size, and it abstracts the setup and teardown logic associated with `ResizeObserver` + * to make it easy to use within a React component. + * + * @param {React.RefObject} ref - A React ref attached to the element whose dimensions you want to monitor. + * @returns {DOMRectReadOnly | null} The latest dimensions of the observed element, or `null` if the dimensions are not available. + * + * The returned object includes properties such as `width`, `height`, `top`, `right`, `bottom`, and `left`, + * providing a comprehensive overview of the element's size and position relative to its containing block. + * + * Usage: + * 1. Attach a ref to your component using `React.useRef()`. + * 2. Pass this ref to the `useResizeObserver` hook. + * 3. Use the returned dimensions to adjust your component's styling or behavior. + * + * Example: + * ```jsx + * const MyResponsiveComponent = () => { + * const myRef = useRef(); + * const dimensions = useResizeObserver(myRef); + * + * return
+ * {dimensions && {`Width: ${dimensions.width}, Height: ${dimensions.height}`}} + *
; + * }; + * ``` + * + * Note: + * Make sure to polyfill `ResizeObserver` in environments that do not support it, as this hook + * relies on that API to function properly. + */ +const useResizeObserver = ( + ref: React.RefObject +): DOMRectReadOnly | null => { const [dimensions, setDimensions] = useState(null); useEffect(() => { // the element being observed (div with green border) diff --git a/app/src/components/StateManagement/StateManagement.tsx b/app/src/components/StateManagement/StateManagement.tsx index c86aaad2f..3db8fd3e8 100644 --- a/app/src/components/StateManagement/StateManagement.tsx +++ b/app/src/components/StateManagement/StateManagement.tsx @@ -19,6 +19,26 @@ const useStyles = makeStyles({ } }); +/** + * `StateManager` is a React component that provides an interface for managing and displaying + * application components' state within a tabbed layout. This component utilizes Material-UI's + * tabs to offer two main functionalities: creating/editing components and displaying them. + * + * The component integrates with Redux to access the global application state, particularly focusing + * on the components' data to manage and render the state of individual components appropriately. + * + * @param {Object} props - Properties passed to the component. + * @returns {JSX.Element} A fragment containing a container with two tabs: one for creating/editing + * components and another for displaying them. + * + * The `StateManager` component uses `TabContext`, `TabList`, and `TabPanel` from Material-UI to + * render a tabbed interface where users can switch between different views: + * - **Create/Edit:** Allows users to modify the state or properties of components. + * - **Display:** Shows how components appear based on the current state or configuration. + * + * This setup is ideal for applications where components need to be dynamically created, configured, + * and previewed by the user, providing an intuitive interface for interaction with complex data structures. + */ const StateManager = (props): JSX.Element => { const state = useSelector((store: RootState) => store.appState); diff --git a/app/src/components/bottom/BottomPanel.tsx b/app/src/components/bottom/BottomPanel.tsx index efb3da428..f0c6e3468 100644 --- a/app/src/components/bottom/BottomPanel.tsx +++ b/app/src/components/bottom/BottomPanel.tsx @@ -2,6 +2,18 @@ import React, { useEffect, useRef, useState } from 'react'; import BottomTabs from './BottomTabs'; import { ExpandLess, ExpandMore } from '@mui/icons-material'; +/** + * A resizable bottom panel component that can be toggled and resized using mouse interactions. + * The component includes a draggable area that listens for mouse down, move, and up events to adjust the height of the panel. + * It also handles iframe messages to synchronize dragging events that occur over an iframe. + * + * @param {Object} props - Component props. + * @param {boolean} props.bottomShow - A boolean indicating if the bottom panel is visible. + * @param {Function} props.setBottomShow - Function to toggle the visibility of the bottom panel. + * @param {boolean} props.isThemeLight - Theme flag used by the BottomTabs component to determine the styling. + * + * @returns {JSX.Element} A React element representing the bottom panel which includes a draggable divider that can toggle the panel's visibility and a tabs component. + */ const BottomPanel = (props): JSX.Element => { let y: number = 0; let h: number = 0; @@ -35,11 +47,14 @@ const BottomPanel = (props): JSX.Element => { const dy = y - e.clientY; const newHeight = h + dy; - const styles = window.getComputedStyle(node.current); - const minHeight = parseInt(styles.minHeight, 10); - const maxHeight = window.innerHeight * 0.8; // Set a maximum height, e.g., 90% of window height + const styles = window.getComputedStyle(node.current); + const minHeight = parseInt(styles.minHeight, 10); + const maxHeight = window.innerHeight * 0.8; // Set a maximum height, e.g., 90% of window height - node.current.style.height = `${Math.max(minHeight, Math.min(maxHeight, newHeight))}px`; + node.current.style.height = `${Math.max( + minHeight, + Math.min(maxHeight, newHeight) + )}px`; }; const mouseUpHandler = function () { @@ -62,7 +77,7 @@ const BottomPanel = (props): JSX.Element => { return ( <> -
+
{ const { setBottomShow, isThemeLight } = props; const dispatch = useDispatch(); diff --git a/app/src/components/bottom/CodePreview.tsx b/app/src/components/bottom/CodePreview.tsx index 74edf7200..9092bbf33 100644 --- a/app/src/components/bottom/CodePreview.tsx +++ b/app/src/components/bottom/CodePreview.tsx @@ -19,6 +19,28 @@ import { unpkgPathPlugin } from '../../plugins/unpkg-path-plugin'; import useResizeObserver from '../../tree/useResizeObserver'; import { initializeEsbuild } from '../../helperFunctions/esbuildService'; +/** + * A React component that provides an interactive code editor using Ace Editor. It integrates a live code preview + * that updates the Redux store with the latest code. The editor can handle JavaScript code, with themes and plugins + * to enhance functionality. This component listens to size changes in its container and adjusts its height accordingly. + * It also integrates with esbuild to bundle the code with custom plugins for handling package imports and fetch operations. + * + * @param {Object} props - Component props. + * @param {string|null} props.theme - The theme for the Ace Editor. Null if no theme is set. + * @param {Function|null} props.setTheme - Function to set the theme of the Ace Editor. Null if not used. + * @param {number} [props.zoom] - Zoom level for the editor, affecting its scale. + * @param {React.RefObject} props.containerRef - Reference to the editor's container element. + * + * @returns {JSX.Element} The CodePreview component, which includes the Ace Editor configured for JavaScript editing. + * + * Redux State Dependencies: + * - `appState`: Uses information about the current component focus to load and manage code. + * + * Effects: + * - Initializes the esbuild service on component mount. + * - Updates the editor height and internal state when the container's dimensions change. + * - Updates Redux with the current code whenever the code in the editor changes. + */ const CodePreview: React.FC<{ theme: string | null; setTheme: any | null; @@ -90,7 +112,14 @@ const CodePreview: React.FC<{ transform: `scale(${zoom})` }} > -
+
{ const style = useSelector((store: RootState) => store.styleSlice); return ( diff --git a/app/src/components/bottom/StylesEditor.tsx b/app/src/components/bottom/StylesEditor.tsx index 2de9d1a11..113197c53 100644 --- a/app/src/components/bottom/StylesEditor.tsx +++ b/app/src/components/bottom/StylesEditor.tsx @@ -14,10 +14,29 @@ import SaveIcon from '@mui/icons-material/Save'; import { updateStylesheet } from '../../redux/reducers/slice/appStateSlice'; import { emitEvent } from '../../helperFunctions/socket'; +/** + * A component that provides a CSS editing interface using the Ace Editor. It is styled with a specific theme and + * allows for the live editing and saving of CSS styles. Changes are saved to the Redux state and can be broadcasted + * to other users in a collaborative environment using sockets. + * + * @param {Object} props - Component props. + * @param {string|null} props.theme - The theme for the Ace Editor; can be null indicating no specific theme set. + * @param {Function|null} props.setTheme - Function to change the theme of the Ace Editor; can be null if theme adjustment is not required. + * + * Redux State Dependencies: + * - `appState.stylesheet`: Holds the current CSS content being edited. + * - `roomSlice.roomCode`: The room identifier used for socket communications to broadcast CSS updates. + * + * @returns {JSX.Element} A styled div containing the Ace Editor configured for CSS editing and a floating action button to save changes. + * + * Functionalities: + * - Edits CSS within a fully featured text editor with syntax highlighting and autocompletion. + * - Saves the CSS to the Redux state and broadcasts changes to other users in the same room via socket events. + */ const StylesEditor: React.FC<{ theme: string | null; setTheme: any | null; -}> = ({ theme, setTheme }) => { +}> = ({ theme, setTheme }): JSX.Element => { const wrapper = useRef(); const stylesheet = useSelector( (state: RootState) => state.appState.stylesheet diff --git a/app/src/components/bottom/UseStateModal.tsx b/app/src/components/bottom/UseStateModal.tsx index adc7774d2..53a2e10b8 100644 --- a/app/src/components/bottom/UseStateModal.tsx +++ b/app/src/components/bottom/UseStateModal.tsx @@ -2,7 +2,27 @@ import React, { useState, useRef } from 'react'; import Modal from '@mui/material/Modal'; import TableStateProps from '../StateManagement/CreateTab/components/TableStateProps'; -function UseStateModal({ updateAttributeWithState, attributeToChange }) { +/** + * A React component that provides a modal interface for linking state properties to component attributes. + * It displays a list of available state properties in a table format and allows the user to select one to associate + * with a specified attribute of a component. The modal can be opened by clicking a button and closed using a dedicated close button. + * + * @param {Object} props - Component props. + * @param {Function} props.updateAttributeWithState - Function to update the component attribute with the selected state property. + * @param {string} props.attributeToChange - The name of the attribute in the component to which the state is to be linked. + * + * State: + * - `open`: Boolean to control the visibility of the modal. + * - `stateKey`: Stores the key part of the state property being linked. + * - `statePropsId`: Stores the ID of the state property being linked. + * - `componentProviderId`: Stores the ID of the component that provides the state. Set initially to 1 and can be changed as needed. + * + * @returns {JSX.Element} A React element that renders a button to open the modal and the modal itself containing the TableStateProps component. + */ +function UseStateModal({ + updateAttributeWithState, + attributeToChange +}): JSX.Element { const [open, setOpen] = useState(false); const [stateKey, setStateKey] = useState(''); const [statePropsId, setStatePropsId] = useState(-1); diff --git a/app/src/components/form/Selector.tsx b/app/src/components/form/Selector.tsx index f82d18267..155026531 100644 --- a/app/src/components/form/Selector.tsx +++ b/app/src/components/form/Selector.tsx @@ -21,6 +21,27 @@ type Props = { name: String; }; +/** + * Renders a form selector with a dynamically populated Select component from MUI. The selector allows + * the user to choose from a list of options provided via `items` prop. The appearance and behavior of the + * component can be customized with several styling options based on the current theme (light or dark). + * + * @param {Object} props - The properties passed to the component. + * @param {Array} props.items - An array of objects each containing the `value` and `text` for each menu item. + * @param {Object} props.classes - An object containing various styling classes: + * `configRow` for the layout of the selector, + * `configType` for the type label, + * `lightThemeFontColor` and `darkThemeFontColor` for theme-based text coloring, + * `formControl` for the control's style, + * `select` for the base style of the Select component, + * `selectInput` for the input part of the Select component. + * @param {boolean} props.isThemeLight - Indicates if the light theme is currently active. + * @param {string} props.title - The title for the selector, displayed above the Select component. + * @param {any} props.selectValue - The currently selected value. + * @param {Function} props.handleChange - The function to call when the selected value changes. + * @param {string} props.name - The name attribute for the Select component; used in form handling. + * @returns {JSX.Element} The rendered component with configured Select and theming. + */ const FormSelector = (props): JSX.Element => { const items = []; let key = 1; diff --git a/app/src/components/left/ComponentDrag.tsx b/app/src/components/left/ComponentDrag.tsx index 01f2f586e..dc3081311 100644 --- a/app/src/components/left/ComponentDrag.tsx +++ b/app/src/components/left/ComponentDrag.tsx @@ -24,6 +24,15 @@ const useStyles = makeStyles({ } }); +/** + * Displays a panel of components that can be dragged onto a canvas, typically used in designing UI frameworks like Next.js or Gatsby.js. + * This panel shows only "root" components which are typically top-level pages in these frameworks. + * + * @param {{ isVisible: boolean, isThemeLight: boolean }} props The props passed to the component. + * @param {boolean} props.isVisible Determines if the component panel should be displayed. + * @param {boolean} props.isThemeLight Indicates if the theme is light, affecting the text color styling. + * @returns {JSX.Element | null} A styled list of draggable component items if visible, otherwise null. + */ const ComponentDrag = ({ isVisible, isThemeLight }): JSX.Element | null => { const classes = useStyles(); const state = useSelector((store: RootState) => store.appState); diff --git a/app/src/components/left/ComponentsContainer.tsx b/app/src/components/left/ComponentsContainer.tsx index d4e3c5fd6..54668af64 100644 --- a/app/src/components/left/ComponentsContainer.tsx +++ b/app/src/components/left/ComponentsContainer.tsx @@ -5,7 +5,16 @@ import { RootState } from '../../redux/store'; import makeStyles from '@mui/styles/makeStyles'; import { useSelector } from 'react-redux'; -const ComponentsContainer = () => { +/** + * Displays a panel of reusable components that are not root components. + * This panel lists components that can be reused across different parts of the application. + * Each component item in the list is capable of being focused, which is visually indicated. + * + * @returns {JSX.Element} A container that holds a list of `ComponentPanelItem` elements, + * each representing a non-root component from the application state. These components are not + * designated as top-level pages or structures but are reusable UI elements. + */ +const ComponentsContainer = (): JSX.Element => { const classes = useStyles(); const state = useSelector((store: RootState) => store.appState); @@ -45,7 +54,7 @@ const useStyles = makeStyles({ flexDirection: 'column', alignItems: 'center', flexGrow: 1, - overflow: 'auto' + overflow: 'auto' }, panelWrapperList: { minHeight: 'auto' diff --git a/app/src/components/left/ContentArea.tsx b/app/src/components/left/ContentArea.tsx index 8590dfac7..284fbe1e8 100644 --- a/app/src/components/left/ContentArea.tsx +++ b/app/src/components/left/ContentArea.tsx @@ -11,6 +11,18 @@ interface ContentAreaProps { isVisible: boolean; } +/** + * TabPanel is a functional component that conditionally renders its children + * based on whether its index matches the currently active tab. This allows for + * efficient rendering of tabbed content only when the tab is active, improving + * performance and user experience by not rendering hidden tab content. + * + * @param {Object} props - Component props + * @param {React.ReactNode} props.children - The content to be rendered when this tab is active. + * @param {number | null} props.activeTab - The index of the currently active tab. + * @param {number} props.index - The index of this specific tab panel. + * @returns {JSX.Element} A Box component from MUI that renders children if it's the active tab. + */ const TabPanel: React.FC<{ children: React.ReactNode; activeTab: number | null; @@ -29,6 +41,19 @@ const panels = [ ]; +/** + * ContentArea component that renders different panels based on the active tab. + * This component acts as a dynamic container for switching between different sections + * of the application like elements, components, rooms, profile page, and settings + * depending on user interaction with the tab interface. + * + * The visibility of the entire container can be toggled, which is controlled by the `isVisible` prop. + * + * @param {Object} props - Component props + * @param {number | null} props.activeTab - The index of the currently active tab; controls which tab content is visible. + * @param {boolean} props.isVisible - Flag indicating whether the content area should be displayed. + * @returns {JSX.Element} The content area component with conditional rendering based on the active tab. + */ const ContentArea: React.FC = ({ activeTab, isVisible }) => { return (
{ const classes = useStyles(); const dispatch = useDispatch(); diff --git a/app/src/components/left/ElementsContainer.tsx b/app/src/components/left/ElementsContainer.tsx index b1fb2a433..d7ab77c9e 100644 --- a/app/src/components/left/ElementsContainer.tsx +++ b/app/src/components/left/ElementsContainer.tsx @@ -9,7 +9,20 @@ import { deleteChild } from '../../redux/reducers/slice/appStateSlice'; import { RootState } from '../../redux/store'; import { emitEvent } from '../../helperFunctions/socket'; -// Left-hand portion of the app, where predefined component options are displayed +/** + * ElementsContainer serves as the left-hand side container in the application, primarily responsible + * for hosting user interaction elements such as `DragDropPanel`. This component handles user inputs + * related to component selection and arrangement via drag-and-drop interfaces. + * + * It also includes key bindings to enhance user interaction, such as deleting a child component + * with the 'Backspace' key when focus is not on text input fields. + * + * Props: + * @param {Object} props - The properties passed down to this component. + * @param {boolean} props.isThemeLight - Indicates if the current theme is light, affecting the visual styling. + * + * @returns {JSX.Element} The container element that holds UI sections for component interaction. + */ const ElementsContainer = (props): JSX.Element => { const contextParam = useSelector((store: RootState) => store.contextSlice); const roomCode = useSelector((store: RootState) => store.roomSlice.roomCode); diff --git a/app/src/components/left/HTMLItem.tsx b/app/src/components/left/HTMLItem.tsx index cbe65eef2..cf5da3b61 100644 --- a/app/src/components/left/HTMLItem.tsx +++ b/app/src/components/left/HTMLItem.tsx @@ -34,12 +34,25 @@ const useStyles = makeStyles({ } }); +/** + * Represents a draggable HTML element item in the component panel. This component allows users + * to drag HTML element types into the canvas or delete instances of them from the project. + * It supports interaction through dragging to add and a button to trigger a deletion modal. + * + * Props: + * @param {string} name - The display name of the HTML element. + * @param {number} id - The unique identifier for the HTML element type. + * @param {string} icon - The name of the icon from Material-UI icons that represents the element. + * @param {Function} handleDelete - Function to handle the deletion of all instances of the element. + * + * @returns {JSX.Element} The rendered HTML item component. + */ const HTMLItem: React.FC<{ name: string; id: number; icon: any; handleDelete: (id: number) => void; -}> = ({ name, id, icon, handleDelete }) => { +}> = ({ name, id, icon, handleDelete }): JSX.Element => { const IconComponent = Icons[icon]; const roomCode = useSelector((store: RootState) => store.roomSlice.roomCode); // current roomCode diff --git a/app/src/components/left/HTMLPanel.tsx b/app/src/components/left/HTMLPanel.tsx index f1389214a..a9aef0675 100644 --- a/app/src/components/left/HTMLPanel.tsx +++ b/app/src/components/left/HTMLPanel.tsx @@ -10,21 +10,22 @@ import MuiAlert, { AlertProps } from '@mui/material/Alert'; import Snackbar from '@mui/material/Snackbar'; import { emitEvent } from '../../helperFunctions/socket'; -/* -DESCRIPTION: This is the bottom half of the left panel, starting from the 'HTML - Elements' header. The boxes containing each HTML element are rendered in - HTMLItem, which itself is rendered by this component. - - !!! TO NAME HTML ELEMENTS in the LEFT panel !!! - -Central state contains all available HTML elements (stored in the HTMLTypes property). - The data for HTMLTypes is stored in HTMLTypes.tsx and is added to central state in - initialState.tsx. - -Hook state: - -tag: -*/ - +/** + * Provides a user interface for creating custom HTML elements in the application. It includes + * input fields for the HTML tag and element name, validations for these inputs, and submission handling + * to add new elements to the Redux store. It also handles error messages and displays a snackbar for success notifications. + * + * @component + * @param {Object} props - Component props. + * @param {boolean} props.isThemeLight - Indicates if the theme is light or dark for styling purposes. + * + * @returns {JSX.Element} The HTMLPanel component. + * + * @example + * ```jsx + * + * ``` + */ const HTMLPanel = (props): JSX.Element => { const classes = useStyles(); const [tag, setTag] = useState(''); diff --git a/app/src/components/left/MUIDragDropPanel.tsx b/app/src/components/left/MUIDragDropPanel.tsx index b8eaff80a..5516fed34 100644 --- a/app/src/components/left/MUIDragDropPanel.tsx +++ b/app/src/components/left/MUIDragDropPanel.tsx @@ -24,6 +24,16 @@ const useStyles = makeStyles({ } }); +/** + * Provides a user interface for managing MUI components in the application. It features accordions for different categories + * of MUI components like Inputs, Data Display, Feedback, etc. Each category can be expanded to show respective MUI components + * that can be dragged onto a canvas or deleted. It uses the `MUIItem` component to render each item and supports deleting items through a centralized method. + * + * @component + * @param {Object} props - Component props, currently unused in the component's body and may be intended for future features or extensions. + * + * @returns {JSX.Element} The MUIDragDropPanel component, which renders an interactive list of MUI components categorized by function. + */ const MUIDragDropPanel = (props): JSX.Element => { const classes = useStyles(); const dispatch = useDispatch(); @@ -61,11 +71,10 @@ const MUIDragDropPanel = (props): JSX.Element => { const muiSurfacesToRender = state.MUITypes.filter( (type) => type.name !== 'separator' && type.id >= 50 && type.id <= 53 ); - + const muiNavigationToRender = state.MUITypes.filter( (type) => type.name !== 'separator' && type.id >= 54 && type.id <= 62 ); - const muiLayoutToRender = state.MUITypes.filter( (type) => type.name !== 'separator' && type.id >= 63 && type.id <= 70 @@ -78,7 +87,6 @@ const MUIDragDropPanel = (props): JSX.Element => { return (
- {/* Root Components */} ); -const ProfilePage = () => { +/** + * The ProfilePage component displays user-related information, such as the username and email, + * fetched from the local storage. It also provides links to resources like React documentation, + * MongoDB, and AWS SQL. The component is styled to fit a dark theme and includes several + * informational sections separated by dividers. + * + * This component is a part of a larger application that seems to provide users with resources + * to build and manage React applications, potentially integrating different databases and + * services. + * + * @returns {JSX.Element} A styled card containing user information, resource links, and + * descriptions of application capabilities. + */ +const ProfilePage = (): JSX.Element => { const classes = useStyles(); const [username, setUsername] = useState(''); const [email, setEmail] = useState(''); @@ -94,7 +107,7 @@ const ProfilePage = () => { textTransform: 'capitalize', fontSize: '14px' }} - href="https://legacy.reactjs.org/tutorial/tutorial.html" + href="https://react.dev/learn" > React docs diff --git a/app/src/components/left/RoomsContainer.tsx b/app/src/components/left/RoomsContainer.tsx index 6a562fd8f..d4d692df9 100644 --- a/app/src/components/left/RoomsContainer.tsx +++ b/app/src/components/left/RoomsContainer.tsx @@ -59,7 +59,26 @@ import { addComponentToContext } from '../../../src/redux/reducers/slice/contextReducer'; -const RoomsContainer = () => { +/** + * RoomsContainer handles the UI and logic for creating or joining collaboration rooms + * within the application. It allows users to enter or leave collaboration rooms, manage + * room settings, and handle real-time interactions via websockets. + * + * The component manages several states like room code, user name, and password, and + * displays different UIs based on whether the user is currently in a room or not. + * It handles socket connections, emits events to the server, and responds to events + * from the server. + * + * Features: + * - Create a new collaboration room. + * - Join an existing collaboration room. + * - Leave a collaboration room. + * - Display a list of current users in the room. + * - Handle errors like incorrect room name or password. + * + * @returns {JSX.Element} The component UI with interactive elements based on the user's state. + */ +const RoomsContainer = (): JSX.Element => { const [isJoinCallabRoom, setIsJoinCollabRoom] = useState(false); const [joinedPasswordAttempt, setJoinedPasswordAttempt] = useState(''); const [isPasswordAttemptIncorrect, setIsPasswordAttemptIncorrect] = @@ -75,7 +94,6 @@ const RoomsContainer = () => { (store: RootState) => store.roomSlice.password ); - const userJoinCollabRoom = useSelector( (store: RootState) => store.roomSlice.userJoinCollabRoom ); @@ -123,7 +141,6 @@ const RoomsContainer = () => { socket.on('room does not exist', () => { setIsRoomAvailable(false); - }); //If you are the host: send current state to server when a new user joins socket.on('requesting state from host', (callback) => { diff --git a/app/src/components/left/Settings.tsx b/app/src/components/left/Settings.tsx index b241b1406..59100542b 100644 --- a/app/src/components/left/Settings.tsx +++ b/app/src/components/left/Settings.tsx @@ -4,7 +4,17 @@ import makeStyles from '@mui/styles/makeStyles'; import { useSelector } from 'react-redux'; import MUIDragDropPanel from './MUIDragDropPanel'; -const ProfilePage = () => { +/** + * A functional component that renders the MUIDragDropPanel within a simple container. + * The ProfilePage currently acts as a wrapper for the MUIDragDropPanel. + * + * @returns {JSX.Element} + * @example + * return ( + * + * ) + */ +const ProfilePage = (): JSX.Element => { const classes = useStyles(); return ( diff --git a/app/src/components/left/Sidebar.tsx b/app/src/components/left/Sidebar.tsx index 1b4e6d8f1..99acfa49c 100644 --- a/app/src/components/left/Sidebar.tsx +++ b/app/src/components/left/Sidebar.tsx @@ -20,11 +20,35 @@ interface SidebarProps { let oldValue = 0; +/** + * Renders a vertical sidebar with navigational tabs. Each tab can activate a different view in the application. + * The `Sidebar` component manages which tab is currently active and can toggle the visibility of associated views. + * + * Props: + * @param {number | null} activeTab - The index of the currently active tab or null if no tab is selected. + * @param {(value: number | null) => void} setActiveTab - Function to set the active tab. + * @param {(state: boolean) => void} toggleVisibility - Function to toggle the visibility of the sidebar. + * + * The component uses MUI `Tabs` and `Tab` components to create a vertical sidebar where each tab corresponds + * to a section of the application. It handles tab changes and can conditionally render components based on + * the active tab. The `handleTabChange` function updates the active tab and ensures the sidebar is visible + * when a tab is clicked. + * + * @component + * @example + * return ( + * + * ) + */ const Sidebar: React.FC = ({ activeTab, setActiveTab, toggleVisibility -}) => { +}): JSX.Element => { const handleTabChange = (event: React.ChangeEvent<{}>, newValue: number) => { setActiveTab(newValue); toggleVisibility(true); diff --git a/app/src/components/main/AddLink.tsx b/app/src/components/main/AddLink.tsx index e11979667..806ed8bf1 100644 --- a/app/src/components/main/AddLink.tsx +++ b/app/src/components/main/AddLink.tsx @@ -7,7 +7,19 @@ import { useDispatch, useSelector } from 'react-redux'; import { updateAttributes } from '../../redux/reducers/slice/appStateSlice'; import { RootState } from '../../redux/store'; -function AddLink({ id, onClickHandler, linkDisplayed }) { +/** + * `AddLink` is a React component that renders a drop-down menu to select and link to different pages in the app. + * It helps manage internal routing by associating selected pages with specific elements within the application. + * The component is primarily used to assign navigation targets for components that act as links. + * + * @param {Object} props - The component props. + * @param {number} props.id - The unique identifier for the link. + * @param {Function} props.onClickHandler - Function to handle click events on the Select component. + * @param {string} props.linkDisplayed - The currently displayed link in the Select component. + * + * @returns {JSX.Element} A FormControl component containing a Select element with options derived from pages within the application. + */ +function AddLink({ id, onClickHandler, linkDisplayed }): JSX.Element { const { state, contextParam, isThemeLight } = useSelector( (store: RootState) => ({ state: store.appState, diff --git a/app/src/components/main/AddRoute.tsx b/app/src/components/main/AddRoute.tsx index b061cb8e2..2a7d7fd6e 100644 --- a/app/src/components/main/AddRoute.tsx +++ b/app/src/components/main/AddRoute.tsx @@ -5,6 +5,14 @@ import { addChild } from '../../redux/reducers/slice/appStateSlice'; import { RootState } from '../../redux/store'; import { emitEvent } from '../../helperFunctions/socket'; +/** + * Provides a button that when clicked, dispatches an action to add a new route as a child element in the application's state. + * This component is typically used in scenarios where dynamic route addition is needed within a React application managing routes state via Redux. + * + * @param {Object} props - Component props. + * @param {number} props.id - The ID of the parent element to which the new route should be added. + * @returns {JSX.Element} A React element representing a button that adds a route. + */ function AddRoute({ id }: AddRoutes): JSX.Element { const roomCode = useSelector((store: RootState) => store.roomSlice.roomCode); const dispatch = useDispatch(); diff --git a/app/src/components/main/Arrow.tsx b/app/src/components/main/Arrow.tsx index 91c3a8910..ce180a345 100644 --- a/app/src/components/main/Arrow.tsx +++ b/app/src/components/main/Arrow.tsx @@ -1,87 +1,122 @@ -import { Arrow } from '../../interfaces/Interfaces' +import { Arrow } from '../../interfaces/Interfaces'; + +/** + * The `Arrow` object provides functionalities to create and manage visual arrow elements on a web page. + * It is primarily used for drawing arrows between two points on the screen, which is useful for representing + * graphical connections or relationships in web-based applications. + * + * @property {Function} renderArrow - Renders an arrow from a source element to a destination element based on their IDs. + * @property {Function} createLineElement - Creates a line element styled as an arrow shaft. + * @property {Function} createHeadElement - Creates an arrow head element. + * @property {Function} lineDraw - Draws a line with an arrowhead between two points on the screen. + * @property {Function} deleteLines - Removes all existing lines and arrowheads from the screen. + */ const arrow: Arrow = { renderArrow: (id) => { - if(id != null) { - let canvasEle = document.getElementById(`canv${id}`) - let renderEle = document.getElementById(`rend${id}`) - if( canvasEle === null || renderEle === null) { + if (id != null) { + let canvasEle = document.getElementById(`canv${id}`); + let renderEle = document.getElementById(`rend${id}`); + if (canvasEle === null || renderEle === null) { return; } else { let canvasElePosition = canvasEle.getBoundingClientRect(); let renderElePosition = renderEle.getBoundingClientRect(); - const canvasEleX = canvasElePosition.left + canvasElePosition.width; - const canvasEleY = canvasElePosition.top + (canvasElePosition.height / 2); - const renderEleX = renderElePosition.left; - const renderEleY = renderElePosition.top; + const canvasEleX = canvasElePosition.left + canvasElePosition.width; + const canvasEleY = canvasElePosition.top + canvasElePosition.height / 2; + const renderEleX = renderElePosition.left; + const renderEleY = renderElePosition.top; arrow.lineDraw(canvasEleX, canvasEleY, renderEleX, renderEleY); } } }, createLineElement: (x, y, length, angle) => { - let styles = 'border: 1px solid black;' - + 'width: ' + length + 'px;' - + 'height: 0px;' - + 'z-index: 9999999999;' - + '-moz-transform: rotate(' + angle + 'rad);' - + '-webkit-transform: rotate(' + angle + 'rad);' - + '-o-transform: rotate(' + angle + 'rad);' - + '-ms-transform: rotate(' + angle + 'rad);' - + 'position: absolute;' - + 'top: ' + y + 'px;' - + 'left: ' + x + 'px;'; - let line = document.createElement("div"); - line.setAttribute("class", "line"); - line.setAttribute('style', styles); + let styles = + 'border: 1px solid black;' + + 'width: ' + + length + + 'px;' + + 'height: 0px;' + + 'z-index: 9999999999;' + + '-moz-transform: rotate(' + + angle + + 'rad);' + + '-webkit-transform: rotate(' + + angle + + 'rad);' + + '-o-transform: rotate(' + + angle + + 'rad);' + + '-ms-transform: rotate(' + + angle + + 'rad);' + + 'position: absolute;' + + 'top: ' + + y + + 'px;' + + 'left: ' + + x + + 'px;'; + let line = document.createElement('div'); + line.setAttribute('class', 'line'); + line.setAttribute('style', styles); return line; }, createHeadElement: (x, y, length, angle) => { - let styles = 'width: 13px;' - + 'height: 13px;' - + 'border: solid 2px;' - + 'border-radius: 50%;' - + 'z-index: 9999999999;' - + 'background-color: #00FFFF;' - + 'border-color: #bbb;' - + 'rotate: ' + angle + 'rad;' - + 'position: absolute;' - + 'top: ' + -6.5 + 'px;' - + 'left: ' + 0 + (length - 12) + 'px;'; - let head = document.createElement("div"); - head.setAttribute("class", "head"); + let styles = + 'width: 13px;' + + 'height: 13px;' + + 'border: solid 2px;' + + 'border-radius: 50%;' + + 'z-index: 9999999999;' + + 'background-color: #00FFFF;' + + 'border-color: #bbb;' + + 'rotate: ' + + angle + + 'rad;' + + 'position: absolute;' + + 'top: ' + + -6.5 + + 'px;' + + 'left: ' + + 0 + + (length - 12) + + 'px;'; + let head = document.createElement('div'); + head.setAttribute('class', 'head'); head.setAttribute('style', styles); return head; }, lineDraw: (x1, y1, x2, y2) => { let a = x1 - x2, - b = y1 - y2, - c = Math.sqrt(a * a + b * b); + b = y1 - y2, + c = Math.sqrt(a * a + b * b); let sx = (x1 + x2) / 2, - sy = (y1 + y2) / 2; + sy = (y1 + y2) / 2; let x = sx - c / 2, - y = sy; + y = sy; let alpha = Math.PI - Math.atan2(-b, a); let line = arrow.createLineElement(x, y, c, alpha); let head = arrow.createHeadElement(x, y, c, alpha); arrow.deleteLines(); - document.getElementsByClassName("main")[0].append(line); - document.getElementsByClassName("line")[0].append(head); + document.getElementsByClassName('main')[0].append(line); + document.getElementsByClassName('line')[0].append(head); }, deleteLines: () => { - let lineArray = document.getElementsByClassName("line"); + let lineArray = document.getElementsByClassName('line'); let lineArrayIterable = Array.from(lineArray); - let headArray = document.getElementsByClassName("head"); + let headArray = document.getElementsByClassName('head'); let headArrayIterable = Array.from(headArray); - for(const line of lineArrayIterable) { + for (const line of lineArrayIterable) { line.remove(); } - for(const head of headArrayIterable) { + for (const head of headArrayIterable) { head.remove(); } } -} +}; export default arrow; diff --git a/app/src/components/main/Canvas.tsx b/app/src/components/main/Canvas.tsx index c491dfd3f..181b11d7a 100644 --- a/app/src/components/main/Canvas.tsx +++ b/app/src/components/main/Canvas.tsx @@ -24,366 +24,384 @@ 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); +/** + * Canvas provides an interactive design surface within the application, capable of rendering child components + * and handling various user interactions like drag-and-drop, zooming, and clicking. It also supports collaborative features + * such as displaying remote user cursors when working in a shared session. + * + * @param {Object} props - The component props. + * @param {number} props.zoom - The current zoom level of the canvas, which scales the view of the contents. + * @param {React.Ref} ref - Ref forwarded to the main container div of the canvas for direct DOM manipulations. + * @returns {JSX.Element} A component that encapsulates the interactive canvas area where users can manipulate and view components. + */ +const Canvas = forwardRef( + ({ zoom }, ref): 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 userName = useSelector( + (store: RootState) => store.roomSlice.userName + ); + const userList = useSelector( + (store: RootState) => store.roomSlice.userList + ); - //remote cursor data - const [remoteCursors, setRemoteCursors] = useState([]); + //remote cursor data + const [remoteCursors, setRemoteCursors] = useState([]); - // Toggle switch for live cursor tracking - const [toggleSwitch, setToggleSwitch] = useState(true); + // 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'); - }; + // 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); + // 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 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; + 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 { - //return previous state if no change - return prevState; + //new user: add new user's cursor + return [ + ...prevState, + { + x: remoteData.x, + y: remoteData.y, + remoteUserName: remoteData.userName, + isVisible: true + } + ]; } - } 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) + // 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) + ) ); - } - - return () => { - if (socket) socket.off('remote cursor data from server'); }; - }, [socket]); - useEffect(() => { - handleCursorDeleteFromServer(); - }, [userList]); + // 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 + })) + ); + } + }; - // 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 - ); + //Function to handle the multiple click events of the toggle button. + const multipleClicks = () => { + handleToggleSwitch(); + toggleButton(); + }; - Arrow.deleteLines(); + 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) + ); + } - 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 - }); - } - }; + return () => { + if (socket) socket.off('remote cursor data from server'); + }; + }, [socket]); - // onClickHandler is responsible for changing the focused component and child component - function onClickHandler(event: React.MouseEvent) { - event.stopPropagation(); - changeFocusFunction(state.canvasFocus.componentId, null); - } + useEffect(() => { + handleCursorDeleteFromServer(); + }, [userList]); - // 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 - }) + // 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 ); - }; - // 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; + 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 + }); } - // 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') { - dispatch( - //update state - addChild({ - type: item.instanceType, - typeId: item.instanceTypeId, - childId: null, - contextParam: contextParam - }) - ); + }; + + // 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 + }) + ); + }; - //emit the socket event - if (roomCode) { - emitEvent('addChildAction', roomCode, { - type: item.instanceType, - typeId: item.instanceTypeId, - childId: null, - contextParam: contextParam - }); + // 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; } - } 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 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') { + 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 + }); + } } - // 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() - }) - }); + }, + 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' - }; + // 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 + // 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 - ); + 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' - ]; + // 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' - }; + 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 && ( -
+ {renderChildren(currentComponent.children)} + {remoteCursors.map( + (cursor, idx) => + cursor.isVisible && ( +
+ {} + {cursor.remoteUserName} +
+ ) + )} +
- ) - )} - -
- ); -}); + {toggleText === 'on' ? 'View Cursors' : 'Hide Cursors'} + + )} + +
+ ); + } +); export default Canvas; diff --git a/app/src/components/main/CanvasContainer.tsx b/app/src/components/main/CanvasContainer.tsx index dd59c01ff..e99bf93b9 100644 --- a/app/src/components/main/CanvasContainer.tsx +++ b/app/src/components/main/CanvasContainer.tsx @@ -19,7 +19,17 @@ interface CanvasContainerProps { setTheme: React.Dispatch>; } -// The CanvasContainer sets the boundaries on the width/height of the canvas +/** + * `CanvasContainer` manages the interface for a flexible workspace in an application that includes both a canvas for graphical elements and a code preview editor. + * It provides tools to zoom in and out of the canvas, toggle between the canvas view and the code editor, and scroll within the canvas. + * + * @param {Object} props - The component props. + * @param {number} props.zoom - The current zoom level of the canvas. + * @param {string} props.theme - The current theme of the code editor. + * @param {React.Dispatch>} props.setTheme - A function to update the theme of the code editor. + * + * @returns {JSX.Element} A component that provides a canvas and code editor environment with controls for navigation and viewing adjustments. + */ function CanvasContainer(props: CanvasContainerProps): JSX.Element { const [theme, setTheme] = useState('solarized_light'); // theme for ACE editor, taken from BottomTabs const state = useSelector((store: RootState) => store.appState); @@ -38,7 +48,7 @@ function CanvasContainer(props: CanvasContainerProps): JSX.Element { backgroundSize: '8px 8px', backgroundPosition: '-19px -19px', borderBottom: 'none', - overflow: 'auto', + overflow: 'auto' }; //containerRef references the container that will ultimately have the scroll functionality @@ -106,13 +116,16 @@ function CanvasContainer(props: CanvasContainerProps): JSX.Element { padding: '20px 20px 5px 20px' }} > - {!state.codePreview && (
-
diff --git a/app/src/components/main/DeleteButton.tsx b/app/src/components/main/DeleteButton.tsx index f9e5027ab..2ee7fc6d3 100644 --- a/app/src/components/main/DeleteButton.tsx +++ b/app/src/components/main/DeleteButton.tsx @@ -6,7 +6,22 @@ import { RootState } from '../../redux/store'; import { emitEvent } from '../../helperFunctions/socket'; import { Clear } from '@mui/icons-material'; -function DeleteButton({ id, name, onClickHandler }: DeleteButtons) { +/** + * `DeleteButton` is a React component that renders a button for deleting a specific HTML element or component within the application. + * It uses Redux actions to manage the state and potentially emits events over sockets for collaborative environments. + * + * @param {Object} props - The component props. + * @param {number} props.id - The unique identifier of the component or HTML element to be deleted. + * @param {string} props.name - The name of the component or HTML element, used for identification in the UI or logs. + * @param {Function} [props.onClickHandler] - An optional function to handle additional or preparatory actions on button click before the deletion occurs. + * + * @returns {JSX.Element} A button element that triggers the deletion of the specified component or HTML element when clicked. + */ +function DeleteButton({ + id, + name, + onClickHandler +}: DeleteButtons): JSX.Element { const contextParam = useSelector((store: RootState) => store.contextSlice); const roomCode = useSelector((store: RootState) => store.roomSlice.roomCode); diff --git a/app/src/components/main/DemoRender.tsx b/app/src/components/main/DemoRender.tsx index 53c145310..dd87f560f 100644 --- a/app/src/components/main/DemoRender.tsx +++ b/app/src/components/main/DemoRender.tsx @@ -8,16 +8,21 @@ import { import React, { useEffect, useRef } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import MUITypes from '../../redux/MUITypes'; -// import Box from '@mui/material/Box'; -// import { Component } from '../../interfaces/Interfaces'; +import Box from '@mui/material/Box'; +import { Component } from '../../interfaces/Interfaces'; import ReactDOMServer from 'react-dom/server'; import { RootState } from '../../redux/store'; import { changeFocus } from '../../redux/reducers/slice/appStateSlice'; // import { blue } from '@mui/material/colors'; import serverConfig from '../../serverConfig'; - -// DemoRender is the full sandbox demo of our user's custom built React components. DemoRender references the design specifications stored in state to construct -// real react components that utilize hot module reloading to depict the user's prototype application. +import componentBuilder from '../../helperFunctions/componentBuilder'; +import html from '../../helperFunctions/DemoRenderHTML'; + +/** + * DemoRender is a React component that renders a sandbox demo of custom-built React components. + * It constructs real React components based on design specifications stored in state and uses hot module reloading to depict the user's prototype application. + * @returns {JSX.Element} JSX Element + */ const DemoRender = (): JSX.Element => { const state = useSelector((store: RootState) => store.appState); const stylesheet = useSelector( @@ -35,632 +40,13 @@ const DemoRender = (): JSX.Element => { overflow: 'auto' }; - const html = ` - - - - - - - - - - - -
- - - - `; - - window.onmessage = (event) => { + /** + * Handles messages received from the iframe. + * Parses the data to identify the component and updates the focus accordingly. + * @param {MessageEvent} event - The event containing the message data. + * @returns {void} + */ + window.onmessage = (event: MessageEvent): void => { // If event.data or event.data.data is undefined, return early if (!event.data || typeof event.data.data !== 'string') return; @@ -678,7 +64,12 @@ const DemoRender = (): JSX.Element => { dispatch(changeFocus({ componentId: matchedComponent.id, childId: null })); }; - window.addEventListener('message', function (event) { + /** + * Listens for messages from the iframe and logs them if they're of type 'log'. + * @param {MessageEvent} event - The event containing the message data. + * @returns {void} + */ + window.addEventListener('message', function (event: MessageEvent): void { if (event.origin !== serverConfig.API_BASE_URL2) return; // Ensure you replace this with your actual iframe origin if (event.data.type === 'log') { @@ -695,113 +86,21 @@ const DemoRender = (): JSX.Element => { } }); - const componentBuilder2 = (array, key = 0) => { - const componentsToRender = []; - for (const element of array) { - if (element.name === 'separator') continue; - const elementType = element.name; - const childId = element.childId; - const elementStyle = element.style; - const innerText = element.attributes.compText; - const classRender = element.attributes.cssClasses; - const activeLink = element.attributes.compLink; - - let allChildren = []; - - if (element.componentData && element.componentData.children) { - allChildren = [...element.componentData.children]; - } - if (element.children && element.children.length > 0) { - allChildren = [...allChildren, ...element.children]; - } - - let renderedChildren = - allChildren.length > 0 - ? componentBuilder2(allChildren, ++key) - : undefined; - - if (element.type === 'MUI Component') { - const baseData = MUITypes.find( - (m) => m.tag === elementType - ).componentData; - // console.log('baseData', baseData); - if (!baseData) return null; - const componentData = { - ...baseData, - props: { - ...baseData.props, - key: ++key, - children: renderedChildren - } - }; - componentsToRender.push(JSON.stringify(componentData)); - - // const serializedHtmlContent = JSON.stringify(htmlContent); - } else { - let Component; - switch (elementType) { - case 'input': - Component = 'input'; - break; - case 'img': - case 'Image': - Component = 'img'; - break; - case 'a': - Component = 'a'; - break; - case 'Switch': - Component = 'Switch'; - break; - case 'Route': - Component = 'Route'; - break; - default: - Component = elementType; - break; - } - const childrenContent = []; - if (innerText) childrenContent.push(innerText); - if (renderedChildren) childrenContent.push(...renderedChildren); - const props = { - ...element.attributes, - className: classRender, - style: elementStyle, - key: ++key, - id: `rend${childId}`, - ...(elementType === 'img' || elementType === 'Image' - ? { src: activeLink } - : {}), - ...(elementType === 'a' || elementType === 'Link' - ? { href: activeLink } - : {}) - }; - - componentsToRender.push( - - {childrenContent.length > 0 - ? childrenContent.map((child, index) => ( - {child} - )) - : null} - - ); - } - key++; - } - // console.log('componentsToRender', componentsToRender); - return componentsToRender; - }; - //initializes our 'code' which will be whats actually in the iframe in the demo render - //this will reset every time we make a change + /** + * Initializes the code variable which represents the content to be displayed in the iframe. + */ let code = ''; + // Find the current component in focus const currComponent = state.components.find( (element) => element.id === state.canvasFocus.componentId ); - // console.log('currComponent', currComponent); + console.log('currComponent', currComponent); - componentBuilder2(currComponent.children).forEach((element) => { + /** + * Builds the code variable based on the current component's children. + */ + componentBuilder(currComponent.children).forEach((element) => { if (typeof element === 'string') { console.log('element', element); code += element; @@ -818,7 +117,10 @@ const DemoRender = (): JSX.Element => { // writes our stylesheet from state to the code code += ``; - // adds the code into the iframe + + /** + * Loads the code into the iframe when it is loaded or when the code changes. + */ useEffect(() => { //load the current state code when the iframe is loaded and when code changes iframe.current.addEventListener('load', () => { diff --git a/app/src/components/main/DirectChildComponent.tsx b/app/src/components/main/DirectChildComponent.tsx index 3b29b2b02..872689367 100644 --- a/app/src/components/main/DirectChildComponent.tsx +++ b/app/src/components/main/DirectChildComponent.tsx @@ -10,7 +10,23 @@ import { changeFocus } from '../../redux/reducers/slice/appStateSlice'; import { RootState } from '../../redux/store'; import { emitEvent } from '../../helperFunctions/socket'; -function DirectChildComponent({ childId, type, typeId, name }: ChildElement) { +/** + * Renders a direct child component within the canvas, allowing for user interaction such as dragging and focusing. + * The component displays the name and includes a delete button for removing the component from the canvas. + * + * @param {Object} props - Component props. + * @param {number} props.childId - Unique identifier for the child component. + * @param {string} props.type - The type of the component (e.g., HTML element, custom component). + * @param {number} props.typeId - The type identifier which corresponds to a specific instance or class of components. + * @param {string} props.name - The display name of the component. + * @returns {JSX.Element} A draggable and clickable area representing the component with its name and a delete button. + */ +function DirectChildComponent({ + childId, + type, + typeId, + name +}: ChildElement): JSX.Element { const state = useSelector((store: RootState) => store.appState); const roomCode = useSelector((store: RootState) => store.roomSlice.roomCode); diff --git a/app/src/components/main/DirectChildHTML.tsx b/app/src/components/main/DirectChildHTML.tsx index 5c1a3f98a..ec17c0fec 100644 --- a/app/src/components/main/DirectChildHTML.tsx +++ b/app/src/components/main/DirectChildHTML.tsx @@ -10,7 +10,25 @@ import { changeFocus } from '../../redux/reducers/slice/appStateSlice'; import { RootState } from '../../redux/store'; import { emitEvent } from '../../helperFunctions/socket'; -function DirectChildHTML({ childId, name, type, typeId, style }: ChildElement) { +/** + * Renders a direct child HTML element within the canvas. This component is draggable and clickable, allowing users to interact with it dynamically. + * It displays the placeholder or a short representation of the HTML type and includes a delete button for component management. + * + * @param {Object} props - Component props. + * @param {number} props.childId - Unique identifier for the child component. + * @param {string} props.name - The display name of the HTML element. + * @param {string} props.type - The type of the component (e.g., HTML element, custom component). + * @param {number} props.typeId - Identifier for the specific type of HTML element. + * @param {Object} props.style - Custom styles applied to the HTML element. + * @returns {JSX.Element} A styled, draggable, and interactive representation of the HTML element on the canvas. + */ +function DirectChildHTML({ + childId, + name, + type, + typeId, + style +}: ChildElement): JSX.Element { const state = useSelector((store: RootState) => store.appState); const roomCode = useSelector((store: RootState) => store.roomSlice.roomCode); @@ -71,7 +89,7 @@ function DirectChildHTML({ childId, name, type, typeId, style }: ChildElement) { return (
diff --git a/app/src/components/main/DirectChildHTMLNestable.tsx b/app/src/components/main/DirectChildHTMLNestable.tsx index 333ca87ac..5591678b0 100644 --- a/app/src/components/main/DirectChildHTMLNestable.tsx +++ b/app/src/components/main/DirectChildHTMLNestable.tsx @@ -21,6 +21,23 @@ import { snapShotAction } from '../../redux/reducers/slice/appStateSlice'; +/** + * Represents a draggable and nestable HTML element on a visual design canvas, capable of accepting other draggable items. + * This component uses React DnD to allow it to be dragged and to accept other draggable components. + * It can function as a container that other components can be dragged into. When components are dragged onto it, + * it checks if the nesting is valid and then potentially changes the parent of the dragged component. + * Additionally, it supports custom route additions and direct child links. + * + * @param {ChildElement} props - The properties passed to the component, including: + * @param {number} props.childId - Unique identifier for the component instance. + * @param {string} props.type - The type of the component (e.g., 'HTML Element'). + * @param {number} props.typeId - The type identifier, linking to the specific HTML type information. + * @param {React.CSSProperties} props.style - Custom styles applied to override or complement the default and type-specific styles. + * @param {React.ReactNode[]} props.children - Child components of this element. + * @param {string} props.name - The name of the component, used for display and reference. + * @param {Object} props.attributes - Additional attributes that might influence the rendering or behavior of the component. + * @returns {JSX.Element} A styled component that can interact within a drag-and-drop interface, reflecting changes in a collaborative environment via socket events. + */ function DirectChildHTMLNestable({ childId, type, @@ -213,14 +230,15 @@ function DirectChildHTMLNestable({ return (
- - {HTMLType.placeHolderShort} - + {HTMLType.placeHolderShort} {attributes && attributes.compLink ? ` ${attributes.compLink}` : ''} diff --git a/app/src/components/main/DirectChildMUI.tsx b/app/src/components/main/DirectChildMUI.tsx index 934ef48a4..3c728eaf6 100644 --- a/app/src/components/main/DirectChildMUI.tsx +++ b/app/src/components/main/DirectChildMUI.tsx @@ -10,7 +10,21 @@ 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) { +/** + * A draggable component representing an instance of a Material-UI element. This component allows for interaction + * within a design canvas environment, supporting actions such as focus change and element deletion. + * The component is styled according to both inherited and directly provided styles, and it communicates with a collaborative + * environment via socket connections when changes occur. + * + * @param {ChildElement} props - The properties passed to the component. + * @param {number} props.childId - The unique identifier for this instance of the component. + * @param {string} props.name - The display name of the component. + * @param {string} props.type - The type of the component as defined in the application's constants. + * @param {number} props.typeId - The unique identifier linking this instance to its base type information. + * @param {React.CSSProperties} props.style - Custom styles applied to the component to override or complement default and type-specific styles. + * @returns {JSX.Element} A draggable, interactable component displayed within the canvas. + */ +function DirectChildMUI({ childId, name, type, typeId, style }: ChildElement): JSX.Element { const state = useSelector((store: RootState) => store.appState); const roomCode = useSelector((store: RootState) => store.roomSlice.roomCode); diff --git a/app/src/components/main/DirectChildMUINestable.tsx b/app/src/components/main/DirectChildMUINestable.tsx index f30848cb3..913046e39 100644 --- a/app/src/components/main/DirectChildMUINestable.tsx +++ b/app/src/components/main/DirectChildMUINestable.tsx @@ -21,6 +21,22 @@ import { snapShotAction } from '../../redux/reducers/slice/appStateSlice'; +/** + * Renders a Material-UI nestable component within the application's canvas. This component can be dragged and dropped + * to reorder or nest within other components. It supports additional functionalities like adding routes or links + * if applicable. It integrates deeply with the application's state management and real-time collaboration features. + * + * @param {Object} props - The component properties. + * @param {number} props.childId - Unique identifier for the child component instance. + * @param {string} props.type - Type of the child element, typically related to UI framework components. + * @param {number} props.typeId - Identifier for the type, linking to specific functionality or styling. + * @param {Object} props.style - Custom styles applied to the component instance. + * @param {React.ReactNode} props.children - Nested child components or elements. + * @param {string} props.name - Display name of the component, used for identification within the UI. + * @param {Object} props.attributes - Additional attributes that might affect rendering or behavior, like links. + * @returns {JSX.Element} A styled, interactive component that can be focused, rearranged, or nested within other components. + */ + function DirectChildMUINestable({ childId, type, diff --git a/app/src/components/main/IndirectChild.tsx b/app/src/components/main/IndirectChild.tsx index 337c3359e..d91f0a17a 100644 --- a/app/src/components/main/IndirectChild.tsx +++ b/app/src/components/main/IndirectChild.tsx @@ -7,6 +7,19 @@ import { useDispatch, useSelector } from 'react-redux'; import { changeFocus } from '../../redux/reducers/slice/appStateSlice'; import { RootState } from '../../redux/store'; +/** + * A component representing an indirect child element that may contain a navigation link to another component. + * It can display either a placeholder or a direct link to another component based on its configuration. + * + * @param {Object} props - The properties passed to the component. + * @param {Object} props.style - Custom styles applied to the component instance. + * @param {React.ReactNode} props.children - Nested child components or elements. + * @param {string} props.placeHolder - Placeholder text displayed when no link is active. + * @param {number} props.linkId - Identifier for the linked component if available. + * @param {number} props.childId - Unique identifier for the child component instance. + * @param {string} props.name - Display name of the component, used for identification within the UI. + * @returns {JSX.Element} A styled component that optionally acts as a navigation link to another component. + */ function IndirectChild({ style, children, @@ -15,13 +28,13 @@ function IndirectChild({ childId, name }) { - const state = useSelector((store:RootState) => store.appState); + const state = useSelector((store: RootState) => store.appState); const dispatch = useDispatch(); let combinedStyle = combineStyles(globalDefaultStyle, style); // when a user clicks a link, the focus should change to that component function onClickHandlerRoute(event) { event.stopPropagation(); - dispatch(changeFocus({ componentId: linkId, childId: null})); + dispatch(changeFocus({ componentId: linkId, childId: null })); } let linkName: string; // if there's a link in this component, then include a link @@ -36,7 +49,11 @@ function IndirectChild({
{` ( ${childId} )`} - + {linkId ? (
{linkName}
diff --git a/app/src/components/main/RouteLink.tsx b/app/src/components/main/RouteLink.tsx index db9e1a120..33698431d 100644 --- a/app/src/components/main/RouteLink.tsx +++ b/app/src/components/main/RouteLink.tsx @@ -8,6 +8,19 @@ import { useDispatch, useSelector } from 'react-redux'; import { changeFocus } from '../../redux/reducers/slice/appStateSlice'; import { RootState } from '../../redux/store'; +/** + * Renders a RouteLink component that allows navigation within the application by changing the focus + * to the component associated with the route. The component is draggable and interactive, + * highlighting when focused. + * + * @param {Object} props - The component properties. + * @param {number} props.childId - Unique identifier for the child component. + * @param {string} props.type - The type of the component (always "RouteLink" here). + * @param {number} props.typeId - Identifier for the type of the linked component. + * @param {Object} props.style - Custom styles applied to the RouteLink. + * @returns {JSX.Element} A styled, interactive RouteLink that changes the focus in the application canvas. + */ + function RouteLink({ childId, type, diff --git a/app/src/components/main/SeparatorChild.tsx b/app/src/components/main/SeparatorChild.tsx index f2910ebb4..4fb736f56 100644 --- a/app/src/components/main/SeparatorChild.tsx +++ b/app/src/components/main/SeparatorChild.tsx @@ -21,6 +21,19 @@ import { } from '../../redux/reducers/slice/appStateSlice'; import { emitEvent } from '../../helperFunctions/socket'; +/** + * Renders a draggable and droppable separator child component within the canvas. + * This component is capable of being both a drag source and a drop target, allowing nested structures. + * It also displays a dynamic style change when being hovered over to indicate it can accept drop items. + * + * @param {Object} props - Component props. + * @param {number} props.childId - Unique identifier for the child component. + * @param {string} props.type - The type of the component (e.g., HTML element, custom component). + * @param {number} props.typeId - Identifier for the specific type of component. + * @param {Object} props.style - Custom styles applied to the separator. + * @param {Object[]} props.children - Child components to be rendered within this separator. + * @returns {JSX.Element} A styled, interactive separator that responds to drag and drop operations. + */ function SeparatorChild({ childId, type, diff --git a/app/src/components/marketplace/MarketplaceCard.tsx b/app/src/components/marketplace/MarketplaceCard.tsx index a83ea6dad..f0f311e95 100644 --- a/app/src/components/marketplace/MarketplaceCard.tsx +++ b/app/src/components/marketplace/MarketplaceCard.tsx @@ -44,7 +44,28 @@ interface Project { } const ITEM_HEIGHT = 48; -const MarketplaceCard = ({ proj }: { proj: Project }) => { + +/** + * `MarketplaceCard` is a React component that displays a project card in the marketplace. It includes functionality + * to clone a project from the marketplace and optionally open it immediately after cloning. The card displays project details, + * an image preview fetched from AWS S3, and offers menu options to clone or clone and open the project. + * + * @param {Object} props - The properties passed to the component. + * @param {Object} props.proj - The project data object containing details to display and operate upon. + * @param {string} props.proj.forked - Indicates if the project is forked. + * @param {string[]} props.proj.comments - Comments associated with the project. + * @param {Date} props.proj.createdAt - The creation date of the project. + * @param {number} props.proj.likes - The number of likes the project has received. + * @param {string} props.proj.name - The name of the project. + * @param {Object} props.proj.project - The project detail and configuration. + * @param {boolean} props.proj.published - Indicates if the project is published. + * @param {number} props.proj.userId - The user ID of the project's owner. + * @param {string} props.proj.username - The username of the project's owner. + * @param {number} props.proj._id - The unique identifier for the project. + * + * @returns {JSX.Element} - A card element that represents a project in the marketplace, with interactions for cloning and opening. + */ +const MarketplaceCard = ({ proj }: { proj: Project }): JSX.Element => { const dispatch = useDispatch(); const history = useHistory(); const [anchorEl, setAnchorEl] = React.useState(null); @@ -54,7 +75,6 @@ const MarketplaceCard = ({ proj }: { proj: Project }) => { const [guest, setGuest] = React.useState(null); const state = useSelector((store: RootState) => store.appState); - useEffect(() => { async function s3ImgFetch() { Amplify.configure(awsconfig); @@ -71,13 +91,11 @@ const MarketplaceCard = ({ proj }: { proj: Project }) => { s3ImgFetch(); }, []); - - const handleClick = (event: React.MouseEvent) => { setAnchorEl(event.currentTarget); }; const handleClone = async () => { - if(state.isLoggedIn){ + if (state.isLoggedIn) { // creates a copy of the project const docId = proj._id; const response = await axios.get(`/cloneProject/${docId}`); //passing in username as a query param is query params @@ -95,7 +113,7 @@ const MarketplaceCard = ({ proj }: { proj: Project }) => { }; const handleCloneOpen = async () => { - if(state.isLoggedIn){ + if (state.isLoggedIn) { const project = await handleClone(); history.push('/'); dispatch(openProject(project)); @@ -139,7 +157,7 @@ const MarketplaceCard = ({ proj }: { proj: Project }) => { - {proj.username.slice(0,1).toUpperCase()} + {proj.username.slice(0, 1).toUpperCase()} } action={ @@ -193,7 +211,7 @@ const MarketplaceCard = ({ proj }: { proj: Project }) => { { { +/** + * `MarketplaceCardContainer` is a React component that renders a container for `MarketplaceCard` components. + * It organizes project cards into a responsive grid layout. Each project is represented by a `MarketplaceCard` which + * displays the project's details and interactions such as cloning and opening. + * + * @param {Object} props - The properties passed to the component. + * @param {Array} props.displayProjects - An array of project objects to display. Each object contains details + * necessary for the `MarketplaceCard` component to function properly. + * + * @returns {JSX.Element} - A container that organizes project cards into a grid layout for display in the UI. + */ +const MarketplaceCardContainer = ({ displayProjects }): JSX.Element => { return ( <> diff --git a/app/src/components/marketplace/Searchbar.tsx b/app/src/components/marketplace/Searchbar.tsx index 441f73d48..f4728d20d 100644 --- a/app/src/components/marketplace/Searchbar.tsx +++ b/app/src/components/marketplace/Searchbar.tsx @@ -1,46 +1,57 @@ -import React, {useEffect, useState} from 'react'; +import React, { useEffect, useState } from 'react'; import TextField from '@mui/material/TextField'; -const SearchBar = ({marketplaceProjects, updateDisplayProjects }):JSX.Element => { +/** + * `SearchBar` is a React component that provides a text input for users to type search terms. It filters and updates the + * display of projects based on the user's input. It uses a debounce approach to minimize the number of updates + * and ensures the search operation is performed after the user has stopped typing for a specified duration. + * + * @param {Object} props - The properties passed to the component. + * @param {Array} props.marketplaceProjects - An array of all available projects in the marketplace. + * @param {Function} props.updateDisplayProjects - A function to update the state of displayed projects based on the search criteria. + * + * @returns {JSX.Element} A TextField component from MUI that handles user input to filter projects. + */ +const SearchBar = ({ + marketplaceProjects, + updateDisplayProjects +}): JSX.Element => { //passing down all marketplaceProjects const [searchText, setSearchText] = useState(''); const handleChange = (e) => { - const newText = e.target.value + const newText = e.target.value; - setSearchText(newText) - - } + setSearchText(newText); + }; - useEffect(()=>{ - if(searchText === ''){ - - updateDisplayProjects(marketplaceProjects) + useEffect(() => { + if (searchText === '') { + updateDisplayProjects(marketplaceProjects); return; } - function searching () { + function searching() { + //more strict pattern not currently used + //const patternString = '(?:^|[^a-zA-Z])' + searchText.toLowerCase() + '(?:$|[^a-zA-Z])'; + //(?: [non-capturing group] means to ignore the items inside the parens in terms of looking for that string order in the target + //^ refers to the literal beginning of a start of a line or string + //| pipe operator is an OR statement + //[^a-zA-Z] [] is grouping characters, the ^ at the beginning means to look for things that are not letters (lowercase or uppercase) + //a-zA-Z letters (lowercase and uppercase) and underscore symbol + //All together: match either the start/end of a line/string or any character that is not a letter. + //Looks for anything that has the searchText in between non-letter characters + const patternString2 = '(?:^|.)' + searchText.toLowerCase() + '(?:$|.)'; + //Only difference is that (?:^|.) (?:$|.) look for anything that matches the string in between any other characters for the username search + //test3 and 1test) would both match for string 'test' - //more strict pattern not currently used - //const patternString = '(?:^|[^a-zA-Z])' + searchText.toLowerCase() + '(?:$|[^a-zA-Z])'; - //(?: [non-capturing group] means to ignore the items inside the parens in terms of looking for that string order in the target - //^ refers to the literal beginning of a start of a line or string - //| pipe operator is an OR statement - //[^a-zA-Z] [] is grouping characters, the ^ at the beginning means to look for things that are not letters (lowercase or uppercase) - //a-zA-Z letters (lowercase and uppercase) and underscore symbol - //All together: match either the start/end of a line/string or any character that is not a letter. - //Looks for anything that has the searchText in between non-letter characters - const patternString2 = '(?:^|.)' + searchText.toLowerCase() + '(?:$|.)'; - //Only difference is that (?:^|.) (?:$|.) look for anything that matches the string in between any other characters for the username search - //test3 and 1test) would both match for string 'test' - - const searchResults = marketplaceProjects.reduce((results, curr) => { - const lowName = curr.name.toLowerCase() - const lowUsername = curr.username.toLowerCase() - if(lowName.match(patternString2) || lowUsername.match(patternString2)) - results.push(curr) - return results; - }, []) - updateDisplayProjects(searchResults) + const searchResults = marketplaceProjects.reduce((results, curr) => { + const lowName = curr.name.toLowerCase(); + const lowUsername = curr.username.toLowerCase(); + if (lowName.match(patternString2) || lowUsername.match(patternString2)) + results.push(curr); + return results; + }, []); + updateDisplayProjects(searchResults); } // simple debounce @@ -49,21 +60,17 @@ const SearchBar = ({marketplaceProjects, updateDisplayProjects }):JSX.Element => }, 800); // clears the timer if searchText is changed before the setTimeout duration is reached return () => clearTimeout(debounceTimer); - - }, [searchText]) - + }, [searchText]); return ( - - ); }; diff --git a/app/src/components/right/ComponentPanel.tsx b/app/src/components/right/ComponentPanel.tsx index f9ceb572c..9734f3b43 100644 --- a/app/src/components/right/ComponentPanel.tsx +++ b/app/src/components/right/ComponentPanel.tsx @@ -10,7 +10,16 @@ import MuiAlert, { AlertProps } from '@mui/material/Alert'; import Snackbar from '@mui/material/Snackbar'; import { emitEvent } from '../../helperFunctions/socket'; -// The component panel section of the left panel displays all components and has the ability to add new components +/** + * `ComponentPanel` is a React component that facilitates the creation and management of component entities + * within a user interface design tool. It allows users to add new components with specific characteristics, + * such as determining if the component is a root component, which affects its behavior and placement within the project. + * + * @param {Object} props - Properties passed to the component. + * @param {boolean} props.isThemeLight - Indicates if the light theme is currently active, influencing the visual styling of the panel. + * + * @returns {JSX.Element} A panel that allows users to input details for a new component, such as name and root status, and adds it to the project. + */ const ComponentPanel = ({ isThemeLight }): JSX.Element => { const classes = useStyles(); // const { state, contextParam } = useSelector((store: RootState) => ({ @@ -166,110 +175,109 @@ const ComponentPanel = ({ isThemeLight }): JSX.Element => { New Component {/* input for new component */} -
-
-
- - Name - -
- +
+
+ -
-
- -
- + Name + +
+ setIsRoot(!isRoot)} + // inputprops and helpertext must be lowercase + // inputProps={{ className: classes.input }} + value={compName} + // Doesn't accept boolean value needs to be a string + error={errorStatus} + // Updated + helperText={errorStatus ? errorMsg : ''} + onChange={handleNameInput} + style={{}} + InputProps={{ + style: { + color: isThemeLight ? 'white' : 'white' + } + }} + placeholder="name" /> - } - label={ - state.projectType === 'Next.js' || - state.projectType === 'Gatsby.js' - ? 'Page' - : 'Root' - } // name varies depending on mode +
+
+ +
+ setIsRoot(!isRoot)} + /> + } + label={ + state.projectType === 'Next.js' || + state.projectType === 'Gatsby.js' + ? 'Page' + : 'Root' + } // name varies depending on mode + className={ + isThemeLight + ? `${classes.rootCheckBoxLabel} ${classes.lightThemeFontColor}` + : `${classes.rootCheckBoxLabel} ${classes.darkThemeFontColor}` + } + labelPlacement="top" + /> +
+
+
+
+
-
-
- -
-
-
<> diff --git a/app/src/components/right/ComponentPanelItem.tsx b/app/src/components/right/ComponentPanelItem.tsx index a6fc640ee..a8d8bf086 100644 --- a/app/src/components/right/ComponentPanelItem.tsx +++ b/app/src/components/right/ComponentPanelItem.tsx @@ -8,22 +8,28 @@ 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 +/** + * `ComponentPanelItem` represents an individual component item within the ComponentPanel. It uses + * drag-and-drop functionality to allow the user to position components within the canvas. The component can + * display a focus state, and restricts dragging when marked as a root component or is currently focused. + * + * @param {Object} props - The component props. + * @param {string} props.name - The display name of the component, which correlates with an icon in the MUI icons library. + * @param {number} props.id - The unique identifier for the component. + * @param {boolean} props.root - Indicates if the component is a root component, affecting its draggability. + * @param {boolean} props.isFocus - Indicates if the component is currently focused in the UI. + * @param {boolean} props.isThemeLight - Indicates if the light theme is active, affecting the text color. + * + * @returns {JSX.Element} A draggable and clickable item that represents a component in the component panel. + */ const ComponentPanelItem: React.FC<{ name: string; id: number; root: boolean; isFocus: boolean; isThemeLight: boolean; -}> = ({ name, id, root, isFocus, isThemeLight }) => { +}> = ({ name, id, root, isFocus, isThemeLight }): JSX.Element => { const classes = useStyles({}); const state = useSelector((store: RootState) => store.appState); const roomCode = useSelector((store: RootState) => store.roomSlice.roomCode); diff --git a/app/src/components/right/ComponentPanelRoutingItem.tsx b/app/src/components/right/ComponentPanelRoutingItem.tsx index 006bfae75..c416c24bf 100644 --- a/app/src/components/right/ComponentPanelRoutingItem.tsx +++ b/app/src/components/right/ComponentPanelRoutingItem.tsx @@ -10,23 +10,17 @@ import makeStyles from '@mui/styles/makeStyles'; import { useDrag } from 'react-dnd'; import { useSelector } from 'react-redux'; -// ------------------------------------------------ - -/* -N.B.: RENDERED ONLY IN NEXT.JS MODE - -DESCRIPTION: This is the box beneath the "Navigation" heading. It allows insertion of links - ("routing items") between pages (which are listed in the "Pages" menu, located above in the app). -First, this component gathers all Pages (as listed in the Pages menu) and puts them in an - array of names of those Pages (navigableComponents). -Next, it sets route (hook state) to the first value in navigableComponents and checks whether - that value (referencedComponent) still exists in the app's central state (Redux). If it does, - the variable routeId is set to the id property of referencedComponent. If it doesn't, - referencedComponent is replaced by index (the only Page guaranteed to exist) in navigableComponents. -Dragging works in the same manner as in ComponentPanelItem.tsx -*/ -// a component panel routing item is a Next.js component that allows the user to navigate between pages -const ComponentPanelRoutingItem: React.FC<{}> = () => { +/** + * `ComponentPanelRoutingItem` represents a routing item in a component panel, specifically for Next.js mode. + * It facilitates the creation of navigational links between pages by providing a drag-and-drop interface. + * Users can select from a list of root components (pages) to set up navigation routes within the application. + * + * This component fetches root components from the Redux store, presents them in a dropdown for user selection, + * and enables dragging these as route links to be dropped into a design canvas. + * + * @returns {JSX.Element} A grid item that contains a dropdown of navigable components and supports drag-and-drop. + */ +const ComponentPanelRoutingItem: React.FC<{}> = (): JSX.Element => { const classes = useStyles(); ('s there, '); const state = useSelector((store: RootState) => store.appState); diff --git a/app/src/components/right/DeleteProjects.tsx b/app/src/components/right/DeleteProjects.tsx index 788f1b24e..1df5222bf 100644 --- a/app/src/components/right/DeleteProjects.tsx +++ b/app/src/components/right/DeleteProjects.tsx @@ -14,23 +14,30 @@ import { getProjects, deleteProject } from '../../helperFunctions/projectGetSaveDel'; -import localforage from 'localforage'; import { useSelector, useDispatch } from 'react-redux'; import { setInitialState, initialState } from '../../redux/reducers/slice/appStateSlice'; import { RootState } from '../../redux/store'; -import MuiAlert, { AlertProps } from '@mui/material/Alert'; -import Snackbar from '@mui/material/Snackbar'; export interface ProjectDialogProps { deleteAlert: () => void; open: boolean; projects: Array; onClose: () => void; } -// The options to be rendered when dialog is open -function ProjectsDialog(props: ProjectDialogProps) { + +/** + * Displays a dialog for deleting projects. It lists all user and marketplace projects with the option to delete them. + * + * @param {Object} props - The component props. + * @param {boolean} props.open - Controls if the dialog is open or not. + * @param {Array} props.projects - List of projects to display in the dialog. + * @param {Function} props.onClose - Function to call when the dialog needs to be closed. + * @param {Function} props.deleteAlert - Function to trigger an alert when a project is deleted. + * @returns {JSX.Element} The dialog component for project deletion. + */ +function ProjectsDialog(props: ProjectDialogProps): JSX.Element { const classes = useStyles(); const { onClose, open, projects, deleteAlert } = props; const state = useSelector((store: RootState) => store.appState); @@ -115,7 +122,12 @@ function ProjectsDialog(props: ProjectDialogProps) { ); } -export default function ProjectsFolder(props) { +/** + * Manages the state and behavior for deleting projects. It triggers fetching projects and opens a dialog for their deletion. + * + * @returns {JSX.Element} - A component that provides a button to open the delete projects dialog and the dialog itself. + */ +export default function ProjectsFolder(props): JSX.Element { const [open, setOpen] = useState(false); const [projects, setProjects] = useState([{ hello: 'cat' }]); diff --git a/app/src/components/right/ExportButton.tsx b/app/src/components/right/ExportButton.tsx index 88c647c35..2bb26d765 100644 --- a/app/src/components/right/ExportButton.tsx +++ b/app/src/components/right/ExportButton.tsx @@ -11,7 +11,13 @@ import { useSelector } from 'react-redux'; import { RootState } from '../../redux/store'; import zipFiles from '../../helperFunctions/zipFiles'; -export default function ExportButton() { +/** + * Renders an export button that triggers a modal for selecting export options. + * The button and modal allow users to export project components in various formats. + * + * @returns {JSX.Element} A button that opens a modal dialogue for exporting project components. + */ +export default function ExportButton(): JSX.Element { const [modal, setModal] = useState(null); const state = useSelector((store: RootState) => store.appState); diff --git a/app/src/components/right/LoginButton.tsx b/app/src/components/right/LoginButton.tsx index c2e83d39e..ebff8997b 100644 --- a/app/src/components/right/LoginButton.tsx +++ b/app/src/components/right/LoginButton.tsx @@ -16,7 +16,15 @@ if (isDev) { serverURL = `http://localhost:${DEV_PORT}`; } -export default function LoginButton() { +/** + * Renders a button that toggles between a login and logout state based on the user's authentication status. + * When clicked, it will either redirect to the login page or log the user out, clearing local storage and + * potentially triggering a re-fetch of certain data based on server response. The button displays + * dynamically based on the current authentication state stored in the Redux state. + * + * @returns {JSX.Element} A button element that allows the user to either log in or log out. + */ +export default function LoginButton(): JSX.Element { const state = useSelector((store: RootState) => store.appState); const dispatch = useDispatch(); diff --git a/app/src/components/right/OpenProjects.tsx b/app/src/components/right/OpenProjects.tsx index f2def7324..c20a4042c 100644 --- a/app/src/components/right/OpenProjects.tsx +++ b/app/src/components/right/OpenProjects.tsx @@ -21,8 +21,19 @@ export interface ProjectDialogProps { projects: Array; onClose: () => void; } -// The options to be rendered when dialog is open -function ProjectsDialog(props: ProjectDialogProps) { + +/** + * Displays a dialog listing all available user and marketplace projects with options to open them. + * Allows users to select a project to open, which triggers an update in the application's state and possibly an alert. + * + * @param {Object} props - The component props. + * @param {boolean} props.open - Controls if the dialog is open or not. + * @param {Array} props.projects - List of projects to display in the dialog. + * @param {Function} props.onClose - Function to call when the dialog needs to be closed. + * @param {Function} props.openAlert - Function to trigger an alert when a project is opened. + * @returns {JSX.Element} A Dialog component populated with list items representing each project. + */ +function ProjectsDialog(props: ProjectDialogProps): JSX.Element { const classes = useStyles(); const { onClose, open, projects, openAlert } = props; const state = useSelector((store: RootState) => store.appState); diff --git a/app/src/components/right/ProjectManager.tsx b/app/src/components/right/ProjectManager.tsx index c933f5134..0b58aa55a 100644 --- a/app/src/components/right/ProjectManager.tsx +++ b/app/src/components/right/ProjectManager.tsx @@ -10,7 +10,18 @@ import createModal from '../right/createModal'; import { useDispatch, useSelector } from 'react-redux'; import { RootState } from '../../redux/store'; import { resetState } from '../../redux/reducers/slice/appStateSlice'; -// ProjectManager function moved to NavBar.tsx + + +/** + * A component that manages project-related actions such as exporting and deleting projects. + * It allows users to export components alone or with additional application files, and provides + * an option to clear all project data. + * + * The component uses a modal to confirm user actions and to present export options. The export + * functionality is tightly coupled with Electron's main process to handle directory selection and file management. + * + * @returns {JSX.Element} A React component that conditionally renders modals based on user interaction. + */ const ProjectManager = () => { // state to keep track of whether a modal should display const [modal, setModal] = useState(null); diff --git a/app/src/components/right/SaveProjectButton.tsx b/app/src/components/right/SaveProjectButton.tsx index a27165b90..61d059fab 100644 --- a/app/src/components/right/SaveProjectButton.tsx +++ b/app/src/components/right/SaveProjectButton.tsx @@ -7,20 +7,30 @@ import DialogContent from '@mui/material/DialogContent'; import DialogTitle from '@mui/material/DialogTitle'; import SaveOutlinedIcon from '@mui/icons-material/SaveOutlined'; import { saveProject } from '../../helperFunctions/projectGetSaveDel'; -import {useDispatch, useSelector} from 'react-redux' -import {updateProjectName, updateProjectId} from '../../redux/reducers/slice/appStateSlice'; +import { useDispatch, useSelector } from 'react-redux'; +import { + updateProjectName, + updateProjectId +} from '../../redux/reducers/slice/appStateSlice'; import { RootState } from '../../redux/store'; import { State } from '../../interfaces/Interfaces'; -export default function FormDialog() { +/** + * A form dialog component that allows users to save projects under a specific name. + * It handles project name validation, interacts with the Redux store to update state, + * and communicates with the server to save the project. + * + * @returns {JSX.Element} A React component that provides a form inside a dialog for entering a project name, + * with validation feedback and options to either save or cancel the operation. + */ +export default function FormDialog(): JSX.Element { const [open, setOpen] = useState(false); -const state = useSelector((store:RootState) => store.appState); -const dispatch = useDispatch(); + const state = useSelector((store: RootState) => store.appState); + const dispatch = useDispatch(); const [projectName, setProjectName] = useState(''); const [invalidProjectName, setInvalidProjectName] = useState(false); - const [invalidProjectNameMessage, setInvalidProjectNameMessage] = useState( - '' - ); + const [invalidProjectNameMessage, setInvalidProjectNameMessage] = + useState(''); const handleClickOpen = () => { setInvalidProjectName(false); @@ -34,8 +44,10 @@ const dispatch = useDispatch(); // Switch to Thunk // If errors occur on the backend, the project name still gets updated - dispatch(updateProjectName(projectName)) - saveProject(projectName, state).then((project: State) => dispatch(updateProjectId(project._id)))//updates the slice with new _id from mongo + dispatch(updateProjectName(projectName)); + saveProject(projectName, state).then((project: State) => + dispatch(updateProjectId(project._id)) + ); //updates the slice with new _id from mongo setOpen(false); } else { setInvalidProjectName(true); @@ -52,35 +64,41 @@ const dispatch = useDispatch(); }; const saveKeyBind = useCallback((e) => { //Save Project As, the || is for Mac or Windows - (e.key === 's' && e.metaKey && !e.shiftKey || e.key === 's' && e.ctrlKey && !e.shiftKey) ? handleClickOpen() : ''; + (e.key === 's' && e.metaKey && !e.shiftKey) || + (e.key === 's' && e.ctrlKey && !e.shiftKey) + ? handleClickOpen() + : ''; }, []); useEffect(() => { document.addEventListener('keydown', saveKeyBind); return () => { - document.removeEventListener('keydown', saveKeyBind) - } + document.removeEventListener('keydown', saveKeyBind); + }; }, []); return (
- Save Project + + Save Project + - - @@ -105,4 +123,3 @@ const dispatch = useDispatch();
); } - diff --git a/app/src/components/right/SimpleModal.tsx b/app/src/components/right/SimpleModal.tsx index 5585fa2c1..05268c3a8 100644 --- a/app/src/components/right/SimpleModal.tsx +++ b/app/src/components/right/SimpleModal.tsx @@ -30,7 +30,24 @@ const styles = (theme: any): any => ({ } }); -const SimpleModal = (props) => { +/** + * `SimpleModal` is a customizable modal dialog component styled with Material-UI. + * It provides a flexible modal that can be used for messages, forms, or any custom content. + * + * @param {Object} props - The properties passed to the modal component. + * @param {boolean} props.open - Controls the visibility of the modal. + * @param {string} props.message - The primary text or content to be displayed in the modal's title area. + * @param {React.ReactNode} props.children - The content to be displayed in the body of the modal. + * @param {string} [props.primBtnLabel] - The label for the primary action button (optional). + * @param {string} [props.secBtnLabel] - The label for the secondary action button (optional). + * @param {Function} [props.primBtnAction] - The function to call when the primary button is clicked (optional). + * @param {Function} [props.secBtnAction] - The function to call when the secondary button is clicked (optional). + * @param {Function} props.closeModal - The function to call to close the modal. + * @param {Object} props.classes - The styling classes applied to various parts of the modal, provided by `withStyles`. + * + * @returns {JSX.Element} The `SimpleModal` component encapsulated in a React Fragment. + */ +const SimpleModal = (props): JSX.Element => { const { classes, open, diff --git a/app/src/components/right/createModal.tsx b/app/src/components/right/createModal.tsx index b3c01ecf6..8101c47a8 100644 --- a/app/src/components/right/createModal.tsx +++ b/app/src/components/right/createModal.tsx @@ -12,6 +12,21 @@ type Props = { closeModal: any; }; +/** + * Creates a customizable modal dialog using the `SimpleModal` component. + * + * @param {Object} props - The properties for configuring the modal. + * @param {boolean} [props.open=true] - Determines if the modal is open. + * @param {string} props.message - The message or content displayed in the modal. + * @param {any} props.primBtnLabel - Label for the primary button. + * @param {any} [props.secBtnLabel=null] - Label for the secondary button (optional). + * @param {any} props.primBtnAction - Handler for the primary button click event. + * @param {any} [props.secBtnAction=null] - Handler for the secondary button click event (optional). + * @param {any} [props.children=null] - Child components or elements to be displayed within the modal. + * @param {Function} props.closeModal - Function to call when closing the modal. + * + * @returns {JSX.Element} A `SimpleModal` component configured with the specified properties. + */ const createModal = ({ open = true, message, @@ -21,7 +36,7 @@ const createModal = ({ secBtnAction = null, children = null, closeModal -}: Props) => ( +}: Props): JSX.Element => ( { +/** + * The `NavBar` component serves as the main navigation bar for the application, providing links, + * buttons for various actions like publishing/unpublishing projects, and access to additional options + * through a dropdown menu. It manages several UI states for visibility of modal dialogs and alerts, + * reacting to user inputs and application state changes. + * + * The component uses Redux for state management to track application states such as whether the user is + * logged in, the current project's publication status, and its name. It also handles triggering alerts + * for different application events like successful publication or deletion, and errors such as when a + * publish action is attempted without being logged in. + * + * @returns {JSX.Element} A `div` element containing the navigation bar setup with links, buttons, and + * conditional rendering logic for showing modals and alerts based on the application state. + * + * This component integrates closely with the application's Redux store to fetch and manipulate the global + * state concerning the current project. It also utilizes local state to manage UI-specific concerns like + * dropdown menu visibility and modal dialogs for publishing projects. Additionally, it includes error handling + * and feedback mechanisms via MUI `Snackbar` components to enhance user interaction and experience. + */ +const NavBar: React.FC = (): JSX.Element => { const [dropMenu, setDropMenu] = useState(false); const state = useSelector((store: RootState) => store.appState); const [publishModalOpen, setPublishModalOpen] = useState(false); @@ -151,7 +170,7 @@ const NavBar: React.FC = () => {

reactype

- +
{isMarketplace ? null : state.published ? ( {modal}
); -} \ No newline at end of file +} diff --git a/app/src/components/top/PublishModal.tsx b/app/src/components/top/PublishModal.tsx index 1516751a5..80d7544ea 100644 --- a/app/src/components/top/PublishModal.tsx +++ b/app/src/components/top/PublishModal.tsx @@ -6,6 +6,27 @@ import DialogActions from '@mui/material/DialogActions'; import Button from '@mui/material/Button'; import TextField from '@mui/material/TextField'; +/** + * `PublishModal` is a React component that renders a dialog for entering a project name + * and publishing the project. This modal includes a text field for the project name and + * buttons to either cancel the action or save and publish the project. + * + * The modal is designed to provide feedback on the validity of the project name through + * text field validation, displaying an error message if the project name is considered invalid. + * + * @param {Object} props - The properties passed to the component. + * @param {boolean} props.open - Boolean to control the visibility of the modal. + * @param {Function} props.onClose - Function to be called when the modal is requested to be closed. + * @param {Function} props.onSave - Function to be called when the publish button is clicked. + * @param {string} props.projectName - Current value of the project name input field. + * @param {Function} props.onChange - Function to handle changes to the project name input field. + * @param {boolean} props.invalidProjectName - Boolean indicating whether the current project name is invalid. + * @param {string} props.invalidProjectNameMessage - Message to display when the project name is invalid. + * @returns {JSX.Element} A modal dialog with a form for publishing a project that includes a text input for the project name and action buttons. + * + * The `PublishModal` is used in scenarios where a user needs to provide a project name before publishing it to ensure + * that the project name meets certain criteria. It is part of the project's workflow where publishing is a key step. + */ const PublishModal = ({ open, onClose, @@ -13,24 +34,26 @@ const PublishModal = ({ projectName, onChange, invalidProjectName, - invalidProjectNameMessage, -}) => { + invalidProjectNameMessage +}): JSX.Element => { return ( - - Publish Project + + Publish Project + { //useHistory hook to grab the url, if it is /marketplace then selectively render MarketplaceContainer const urlAdd = useHistory(); diff --git a/app/src/containers/CustomizationPanel.tsx b/app/src/containers/CustomizationPanel.tsx index 53e040361..744b45174 100644 --- a/app/src/containers/CustomizationPanel.tsx +++ b/app/src/containers/CustomizationPanel.tsx @@ -42,9 +42,24 @@ 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 +/** + * `CustomizationPanel` is a complex React component designed to provide a user interface for modifying the attributes, + * styles, and event handlers of components or HTML elements within a visual editor. This panel allows users to + * adjust CSS properties, bind events, and manage component links and texts. It supports complex conditional rendering + * based on the application's state and user interactions, including support for undo/redo actions, deleting instances, + * and handling responsive design settings. + * + * @param {Object} props - Props for `CustomizationPanel`. + * @param {boolean} props.isThemeLight - Indicates whether the light theme is currently active. + * @returns {JSX.Element} The rendered JSX of the CustomizationPanel, which includes various interactive elements for customization. + * + * The component uses numerous hooks for managing local state (like `useState` for local variables and `useEffect` for lifecycle management), + * and Redux hooks (like `useDispatch` and `useSelector`) to interact with the global state. The component dynamically adjusts its layout and + * functionality based on the selected component or HTML element, facilitating extensive customization capabilities. It leverages custom hooks + * and functions to compute values dynamically, apply changes through dispatch actions, and handle complex conditions such as the presence + * or absence of specific attributes or styles. Additionally, the component includes accessibility features, error handling, and a modal system + * for user confirmations and actions. + */ const CustomizationPanel = ({ isThemeLight }): JSX.Element => { const classes = useStyles(isThemeLight); const dispatch = useDispatch(); diff --git a/app/src/containers/LeftContainer.tsx b/app/src/containers/LeftContainer.tsx index 7967e5766..5d32f1bca 100644 --- a/app/src/containers/LeftContainer.tsx +++ b/app/src/containers/LeftContainer.tsx @@ -2,6 +2,25 @@ import React, { useState, useEffect } from 'react'; import ContentArea from '../components/left/ContentArea'; import Sidebar from '../components/left/Sidebar'; +/** + * The `App` component serves as the main container for a simple application that includes a sidebar + * and a content area. The sidebar allows for tab selection and toggling the visibility of the content area. + * The content area displays content based on the selected tab and its visibility state. + * + * State management within the component includes: + * - `activeTab`: Tracks the currently active tab in the sidebar. + * - `isVisible`: Controls the visibility of the content area. + * + * The component uses the `useState` hook to manage these states and provides methods to update them, + * which are passed down to child components (`Sidebar` and `ContentArea`) as props. + * + * @returns {JSX.Element} The main layout of the application, including a sidebar and a content area, + * structured in a flexible display container. + * + * This structure facilitates a user interface where interactions in the sidebar affect the content displayed, + * making it dynamic based on user actions. This pattern is typical for applications requiring a navigation panel + * alongside content that updates based on user interaction. + */ const App = () => { const [activeTab, setActiveTab] = useState(0); const [isVisible, setIsVisible] = useState(true); diff --git a/app/src/containers/MainContainer.tsx b/app/src/containers/MainContainer.tsx index 8c4dc742d..8d468d3d0 100644 --- a/app/src/containers/MainContainer.tsx +++ b/app/src/containers/MainContainer.tsx @@ -10,6 +10,25 @@ import { Buffer } from 'buffer'; import { Amplify, Storage } from 'aws-amplify'; import awsconfig from '../../../src/custom-aws-exports'; +/** + * `MainContainer` is a React functional component that serves as the primary layout container for a UI application. + * It includes a canvas area managed by `CanvasContainer` and `DemoRender` components, and a bottom panel that + * can be shown or hidden based on user interactions. This component also handles screenshot capturing of the + * canvas area when triggered and uploads the screenshot to AWS S3. + * + * The component uses `useState` for local state management and `useRef` to reference DOM nodes. It integrates + * with Redux for state management across the app, particularly for managing screenshot triggers and application styles. + * It uses `html2canvas` for taking screenshots and AWS Amplify's Storage module to handle the upload to S3. + * + * @param {Object} props - Props passed to the component including `isThemeLight` to indicate the current theme. + * @returns {JSX.Element} - Renders the main container including the canvas area, demo renderer, and a dynamically + * visible bottom panel. + * + * The component listens for changes in the `screenshotTrigger` from the Redux store to initiate a screenshot, + * converts it to a Buffer, and uploads it to an AWS S3 bucket. It also includes an internal hook `useOutsideClick` + * to handle clicks outside the bottom panel to potentially close it. The layout style and transitions are managed + * based on the state of the `bottomShow` flag. + */ const MainContainer = (props): JSX.Element => { const [bottomShow, setBottomShow] = useState(false); const dispatch = useDispatch(); diff --git a/app/src/containers/MarketplaceContainer.tsx b/app/src/containers/MarketplaceContainer.tsx index 7c9378f96..aee367cb2 100644 --- a/app/src/containers/MarketplaceContainer.tsx +++ b/app/src/containers/MarketplaceContainer.tsx @@ -1,11 +1,27 @@ import MarketplaceCardContainer from '../components/marketplace/MarketplaceCardContainer'; import SearchBar from '../components/marketplace/Searchbar'; -import React, {useEffect, useState} from 'react'; +import React, { useEffect, useState } from 'react'; import axios from 'axios'; import { CircularProgress } from '@mui/material'; +/** + * `MarketplaceContainer` serves as the main container for the marketplace section of the application, + * where users can discover, browse, and interact with community-built components. It fetches a list + * of projects from a server endpoint and allows users to search through them using a `SearchBar` component. + * The search results are displayed using the `MarketplaceCardContainer`. + * + * The component makes an HTTP GET request to fetch marketplace projects using Axios and manages the fetched + * data with React state hooks. It also handles displaying a loading state while the data is being fetched + * and provides feedback when no projects match the search criteria or while projects are still loading. + * + * @returns {JSX.Element} A styled container that includes a search bar and a dynamic display area for + * marketplace projects or loading and no-results messages. + * + * The component uses CSS properties for styling and includes React state management to handle the data + * fetched from the server and the results filtered by the search bar. The component's internal state + * updates trigger re-renders to display the projects or relevant messages to the user. + */ const MarketplaceContainer = () => { - const [marketplaceProjects, setMarketplaceProjects] = useState([]); const [displayProjects, setDisplayProjects] = useState([]); useEffect(() => { @@ -13,9 +29,9 @@ const MarketplaceContainer = () => { try { const response = await axios.get('/getMarketplaceProjects', { headers: { - 'Content-Type': 'application/json', + 'Content-Type': 'application/json' }, - withCredentials: true, + withCredentials: true }); setMarketplaceProjects(response.data); setDisplayProjects(response.data); @@ -24,37 +40,37 @@ const MarketplaceContainer = () => { } } marketplaceFetch(); - }, []); - const updateDisplayProjects = (searchResults) => { - setDisplayProjects(searchResults);//have to pass this down as a prop so that the setting is done outside of Rendering otherwise callstack issues + setDisplayProjects(searchResults); //have to pass this down as a prop so that the setting is done outside of Rendering otherwise callstack issues }; - + return (

Discover components built with ReacType

- Browse, save, and customize the latest components built by the - community + Browse, save, and customize the latest components built by the + community

- +
- {displayProjects.length ? : - - (/*while marketplaceProjects is length 0 means it is not done fetching. Add loading...*/ - marketplaceProjects.length ? -

- No Results Found! -

- : -

{/*added a circular progress bar*/} - Loading... -

- ) - } + {displayProjects.length ? ( + + ) : /*while marketplaceProjects is length 0 means it is not done fetching. Add loading...*/ + marketplaceProjects.length ? ( +

No Results Found!

+ ) : ( +

+ {' '} + {/*added a circular progress bar*/} + Loading... +

+ )}
); }; @@ -65,8 +81,7 @@ const containerStyles: React.CSSProperties = { width: '100vw', color: 'white', paddingBottom: '15vh', - overflow: 'auto', - + overflow: 'auto' }; const contentStyles: React.CSSProperties = { diff --git a/app/src/helperFunctions/DemoRenderHTML.ts b/app/src/helperFunctions/DemoRenderHTML.ts new file mode 100644 index 000000000..7a2785f6a --- /dev/null +++ b/app/src/helperFunctions/DemoRenderHTML.ts @@ -0,0 +1,838 @@ +/** + * HTML content for an iframe. + * This HTML code is designed to be loaded within an iframe and handles various interactions such as message passing, dependency loading, and component rendering based on incoming data. + * @typedef {string} IframeHTML + */ +const html = ` + + + + + + + + + + + +
+ + + + `; + +export default html; diff --git a/app/src/helperFunctions/changePositionValidation.ts b/app/src/helperFunctions/changePositionValidation.ts index e86c803d3..28a11ac22 100644 --- a/app/src/helperFunctions/changePositionValidation.ts +++ b/app/src/helperFunctions/changePositionValidation.ts @@ -1,9 +1,13 @@ -// This function will evaluate the target destination when moving an element on the canvas -// If the target destination is actually a nested component within its own children array -// the new target parent is not a valid parent to change position - import { State, ChildElement } from '../interfaces/Interfaces'; +/** + * Validates whether the new parent is a valid target destination for moving an element on the canvas. + * This function checks if the target destination is a nested component within its own children array, preventing nesting inside its children. + * @param {State} state - The state object containing canvas components and focus information. + * @param {number} currentChildId - The ID of the child element being moved. + * @param {number} toTargetParentId - The ID of the potential new parent element. + * @returns {boolean} Returns true if the new parent is a valid target, otherwise false. + */ const validateNewParent = ( state: State, currentChildId: number, diff --git a/app/src/helperFunctions/cloneDeep.ts b/app/src/helperFunctions/cloneDeep.ts index 656f41866..84c4ec716 100644 --- a/app/src/helperFunctions/cloneDeep.ts +++ b/app/src/helperFunctions/cloneDeep.ts @@ -1,10 +1,14 @@ -// index signatures +/** + * Deeply clones an object or an array, including nested objects and arrays. + * @param {object | any[]} value - The object or array to be cloned. + * @returns {object | any[]} Returns a deep clone of the input value. + */ function cloneDeep( value: { [key: string]: any } | any[] ): { [key: string]: any } | any { if (Array.isArray(value)) { const result: any[] = []; - value.forEach(el => { + value.forEach((el) => { if (typeof el === 'object') { result.push(cloneDeep(el)); } else { @@ -15,7 +19,7 @@ function cloneDeep( } if (typeof value === 'object' && value !== null) { const result: { [key: string]: any } = {}; - Object.keys(value).forEach(key => { + Object.keys(value).forEach((key) => { if (typeof value[key] === 'object') { result[key] = cloneDeep(value[key]); } else { diff --git a/app/src/helperFunctions/combineStyles.ts b/app/src/helperFunctions/combineStyles.ts index dea047ee9..22863c226 100644 --- a/app/src/helperFunctions/combineStyles.ts +++ b/app/src/helperFunctions/combineStyles.ts @@ -1,3 +1,9 @@ +/** + * Combines default styles with priority styles, prioritizing the properties from the priority styles. + * @param {Object} defaultStyle - The default styles. + * @param {Object} priorityStyle - The priority styles. + * @returns {Object} Returns the combined styles object. + */ export const combineStyles = ( defaultStyle: Object, priorityStyle: Object diff --git a/app/src/helperFunctions/componentBuilder.tsx b/app/src/helperFunctions/componentBuilder.tsx new file mode 100644 index 000000000..5f6f64c12 --- /dev/null +++ b/app/src/helperFunctions/componentBuilder.tsx @@ -0,0 +1,103 @@ +import React from 'react'; +import MUITypes from '../redux/MUITypes'; +import { ChildElement, MUIComponent } from '../interfaces/Interfaces'; + +/** + * Function to build React components recursively from an array of child elements. + * @param {Array} array - Array of child elements or MUI components (created by MUIItem.tsx). + * @param {number} key - Key to assign to the components being rendered. + * @returns {React.ReactNode[]} - Array of React components to render. + */ +const componentBuilder = ( + array: Array, + key: number = 0 +): React.ReactNode[] => { + const componentsToRender = []; + for (const element of array) { + console.log('componentBuilder element', element); + if (element.name === 'separator') continue; + const elementType = element.name; + const childId = element.childId; + const elementStyle = element.style; + const innerText = element.attributes.compText; + const classRender = element.attributes.cssClasses; + const activeLink = element.attributes.compLink; + + let renderedChildren = + element.children.length > 0 + ? componentBuilder(element.children, ++key) + : undefined; + + if (element.type === 'MUI Component') { + const baseData = MUITypes.find( + (m) => m.tag === elementType + ).componentData; + console.log('baseData', baseData); + if (!baseData) return null; + const componentData = { + ...baseData, + props: { + ...baseData.props, + key: ++key, + children: renderedChildren + } + }; + componentsToRender.push(JSON.stringify(componentData)); + + // const serializedHtmlContent = JSON.stringify(htmlContent); + } else { + let Component; + switch (elementType) { + case 'input': + Component = 'input'; + break; + case 'img': + case 'Image': + Component = 'img'; + break; + case 'a': + Component = 'a'; + break; + case 'Switch': + Component = 'Switch'; + break; + case 'Route': + Component = 'Route'; + break; + default: + Component = elementType; + break; + } + const childrenContent = []; + if (innerText) childrenContent.push(innerText); + if (renderedChildren) childrenContent.push(...renderedChildren); + const props = { + ...element.attributes, + className: classRender, + style: elementStyle, + key: ++key, + id: `rend${childId}`, + ...(elementType === 'img' || elementType === 'Image' + ? { src: activeLink } + : {}), + ...(elementType === 'a' || elementType === 'Link' + ? { href: activeLink } + : {}) + }; + + componentsToRender.push( + + {childrenContent.length > 0 + ? childrenContent.map((child, index) => ( + {child} + )) + : null} + + ); + } + key++; + } + return componentsToRender; +}; + +export default componentBuilder; diff --git a/app/src/helperFunctions/componentNestValidation.ts b/app/src/helperFunctions/componentNestValidation.ts index d18830ba9..3114f424b 100644 --- a/app/src/helperFunctions/componentNestValidation.ts +++ b/app/src/helperFunctions/componentNestValidation.ts @@ -1,8 +1,13 @@ -// This function is used in both DirectChildHTMLNestable and SeparatorChild to ensure that user created components do not nest within themselves. -// To allow such nesting would be a certain paradox and locks the application. -// This check is done right after the drag functionality resolves and releases. Nothing is done if a component is found trying to nest within itself. import { ChildElement } from '../interfaces/Interfaces'; +/** + * Used in both DirectChildHTMLNestable and SeparatorChild to ensure that user-created components do not nest within themselves. + * To allow such nesting would be a certain paradox and locks the application. + * This check is performed after the drag functionality resolves and releases. + * @param {ChildElement[]} children - The array of child elements to check. + * @param {number} nestId - The ID of the component to check against nesting. + * @returns {boolean} Returns true if the component is not nested within itself, otherwise returns false. + */ const componentNest = (children: ChildElement[], nestId: Number) => { let notNested = true; for (const element of children) { diff --git a/app/src/helperFunctions/cssRefresh.tsx b/app/src/helperFunctions/cssRefresh.tsx index 0043e30b2..87d4b89f3 100644 --- a/app/src/helperFunctions/cssRefresh.tsx +++ b/app/src/helperFunctions/cssRefresh.tsx @@ -1,5 +1,7 @@ -// Removes old link to css and creates a new stylesheet link on demo render -// this is not currently being used for the website version +/** + * Removes the old link to CSS and creates a new stylesheet link on demo render. + * This function is not currently being used for the website version. + */ const cssRefresher = (): void => { const oldStylesheet = document.getElementById('stylesheet'); if (oldStylesheet !== null) oldStylesheet.remove(); @@ -14,5 +16,3 @@ const cssRefresher = (): void => { } }; export default cssRefresher; - - diff --git a/app/src/helperFunctions/esbuildService.ts b/app/src/helperFunctions/esbuildService.ts index b2ede9007..ce6c6606e 100644 --- a/app/src/helperFunctions/esbuildService.ts +++ b/app/src/helperFunctions/esbuildService.ts @@ -1,9 +1,11 @@ import * as esbuild from 'esbuild-wasm'; -/* Singleton pattern for initializing esbuild */ -/* This ensures that esbuild is only initialized once, regardless of how many times CodePreview is mounted and unmounted */ let isEsbuildInitialized = false; +/** + * Singleton pattern for initializing esbuild. + * Ensures that esbuild is only initialized once, regardless of how many times CodePreview is mounted and unmounted. + */ export const initializeEsbuild = async () => { if (!isEsbuildInitialized) { await esbuild.initialize({ diff --git a/app/src/helperFunctions/generateCode.ts b/app/src/helperFunctions/generateCode.ts index 8dc56cf1a..6758da8f1 100644 --- a/app/src/helperFunctions/generateCode.ts +++ b/app/src/helperFunctions/generateCode.ts @@ -3,6 +3,7 @@ import { ChildElement, HTMLType, MUIType, + MUIComponent, ChildStyle, StateProp } from '../interfaces/Interfaces'; @@ -12,7 +13,18 @@ declare global { } } -// generate code based on component hierarchy and then return the rendered code +/** + * Generates code based on the component hierarchy and returns the rendered code. + * @param {Component[]} components - Array of components in the hierarchy. + * @param {number} componentId - The ID of the component to generate code for. + * @param {number[]} rootComponents - Array of root component IDs. + * @param {string} projectType - The type of project (e.g., 'Classic React', 'Next.js', 'Gatsby.js'). + * @param {HTMLType[]} HTMLTypes - Array of HTML types. + * @param {MUIType[]} MUITypes - Array of Material UI types. + * @param {boolean} tailwind - Indicates whether Tailwind CSS is used. + * @param {any} contextParam - Additional parameters related to context (if any). + * @returns {string} - The formatted code. + */ const generateCode = ( components: Component[], componentId: number, @@ -22,7 +34,7 @@ const generateCode = ( MUITypes: MUIType[], tailwind: boolean, contextParam: any -) => { +): string => { const code = generateUnformattedCode( components, componentId, @@ -36,7 +48,18 @@ const generateCode = ( return formatCode(code); }; -// generate code based on the component hierarchy +/** + * Generates unformatted code based on the component hierarchy and returns the rendered code. + * @param {Component[]} comps - Array of components in the hierarchy. + * @param {number} componentId - The ID of the component to generate code for. + * @param {number[]} rootComponents - Array of root component IDs. + * @param {string} projectType - The type of project (e.g., 'Classic React', 'Next.js', 'Gatsby.js'). + * @param {HTMLType[]} HTMLTypes - Array of HTML types. + * @param {MUIType[]} MUITypes - Array of Material UI types. + * @param {boolean} tailwind - Indicates whether Tailwind CSS is used. + * @param {any} contextParam - Additional parameters related to context (if any). + * @returns {string} - The unformatted code. + */ const generateUnformattedCode = ( comps: Component[], componentId: number, @@ -46,14 +69,15 @@ const generateUnformattedCode = ( MUITypes: MUIType[], tailwind: boolean, contextParam: any -) => { +): string => { const components = [...comps]; // find the component that we're going to generate code for - const currComponent = components.find((elem) => elem.id === componentId); + const currComponent: Component | ChildElement | MUIComponent = + components.find((elem) => elem.id === componentId); // find the unique components that we need to import into this component file let imports: any = []; - let muiImports = new Set(); - let muiStateAndEventHandlers = new Set(); + let muiImports: Set = new Set(); + let muiStateAndEventHandlers: Set = new Set(); let providers: string = ''; let context: string = ''; let links: boolean = false; @@ -61,8 +85,16 @@ const generateUnformattedCode = ( const isRoot = rootComponents.includes(componentId); let importReactRouter = false; - // returns an array of objects which may include components, html elements, MaterialUI components, and/or route links - const getEnrichedChildren = (currentComponent) => { + /** + * Recursively processes the children of a component, enriching them with additional information. + * Differentiates between regular components, HTML elements, Material UI components, and route links. + * @param {Component | ChildElement | MUIComponent} currentComponent - The component whose children need to be processed. + * @returns {Array} - An array of objects representing enriched children, including components, HTML elements, + * Material UI components, and route links. + */ + const getEnrichedChildren = ( + currentComponent: Component | ChildElement | MUIComponent + ): Array => { const enrichedChildren = []; currentComponent.children?.forEach((child) => { @@ -101,9 +133,7 @@ const generateUnformattedCode = ( 'Route' ].includes(htmlElement.tag) ) { - newChild.children = getEnrichedChildren({ - children: child.children - }); + newChild.children = getEnrichedChildren(child.children); } // Additional flags for special types @@ -170,16 +200,14 @@ const generateUnformattedCode = ( 'container', 'grid', 'stack', - 'image-list', + 'imageList', 'modal', 'popover', 'popper', 'transition' ].includes(muiComponent.tag) ) { - newChild.children = getEnrichedChildren({ - children: child.children - }); + newChild.children = getEnrichedChildren(child.children); collectMUIImports(child, MUITypes, muiImports); collectStateAndEventHandlers( child, @@ -205,27 +233,37 @@ const generateUnformattedCode = ( 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) => { + /** + * Convert styles stored in an object to React inline style format. + * @param {object} styleObj - The object containing styles to format. + * @returns {string} - The formatted styles in React inline style format. + */ + const formatStyles = (styleObj): string => { + // check if the style object is empty if (Object.keys(styleObj).length === 0) return ''; + // Convert the style object to a string in React inline style format return `style={{${Object.entries(styleObj) .map(([key, value]) => `${key}: '${value}'`) - .join(', ')}}}`; + .join(', ')}}}`; // Join key-value pairs with commas and enclose in double curly braces }; - // 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) => { + /** + * Generates details such as classes, ids, styles, and event handlers for a given child element. + * @param {ChildElement} childElement - The child element to generate details for. + * @returns {string} - The generated details as a string. + */ + const elementTagDetails = (childElement): string => { const details = []; - + // Add id attribute if childId exists and the element is not a Route if (childElement.childId && childElement.tag !== 'Route') { details.push(`id="${childElement.childId}"`); } - + // Add className attribute if cssClasses exist if (childElement.attributes && childElement.attributes.cssClasses) { details.push(`className="${childElement.attributes.cssClasses}"`); } - + // Add styles if they exist if (childElement.style && Object.keys(childElement.style).length > 0) { if (tailwind) { // Assuming 'tailwind' variable is globally available @@ -297,22 +335,40 @@ const generateUnformattedCode = ( details.push(formatStyles(childElement.style)); } } - + // Add event handlers if they exist if (childElement.events) { Object.entries(childElement.events).forEach(([event, funcName]) => { details.push(`${event}={${funcName}}`); }); } - + // Join details into a single string and return return details.join(' '); }; - // function to fix the spacing of the ace editor for new lines of added content. This was breaking on nested components, leaving everything right justified. - const tabSpacer = (level) => ' '.repeat(level); - // function to dynamically generate the appropriate levels for the code preview - const levelSpacer = (level) => `\n${tabSpacer(level)}`; - // function to dynamically generate a complete html (& also other library type) elements - const elementGenerator = (childElement, level = 0) => { + /** + * Generates a string consisting of spaces to represent indentation for a given level. + * @param {number} level - The level of indentation. + * @returns {string} - The string representing the indentation. + */ + const tabSpacer = (level: number): string => ' '.repeat(level); + + /** + * Generates a string consisting of new lines and indentation to represent spacing for a given level. + * @param {number} level - The level of spacing. + * @returns {string} - The string representing the spacing. + */ + const levelSpacer = (level: number): string => `\n${tabSpacer(level)}`; + + /** + * Generates JSX elements based on the provided child element. + * @param {object} childElement - The child element for which JSX is to be generated. + * @param {number} [level=0] - The nesting level of the element. Default is 0. + * @returns {string[]} - An array of strings representing JSX elements. + */ + const elementGenerator = ( + childElement: ChildElement, + level: number = 0 + ): string[] => { const jsxArray = []; const indentation = ' '.repeat(level); @@ -375,11 +431,18 @@ const generateUnformattedCode = ( return jsxArray; }; - function insertNestedJsxBeforeClosingTag( - parentJsx, - nestedJsx, - indentationLevel - ) { + /** + * Inserts nested JSX before the closing tag of a parent JSX element. + * @param {string} parentJsx The JSX of the parent element. + * @param {string[]} nestedJsx The nested JSX elements to insert. + * @param {number} indentationLevel The level of indentation for the nested JSX. + * @returns {string} The updated JSX string with nested elements inserted. + */ + const insertNestedJsxBeforeClosingTag = ( + parentJsx: string, + nestedJsx: string[], + indentationLevel: number + ): string => { // Find the index of the closing tag of the parent component const closingTagIndex = parentJsx.lastIndexOf(' { // Define a regular expression to match the start tag of the specified child element const tagRegExp = new RegExp(`^<${name}(\\s|>)`); @@ -468,38 +546,55 @@ const generateUnformattedCode = ( return originalIndent + line; // Avoid trimming here as line may already include necessary spaces }); return modifiedJsx; - } + }; - const muiGenerator = (child, level = 0) => { + /** + * Generates JSX for a Material UI component. + * @param {ChildElement} child - The child element representing the Material UI component. + * @param {number} [level=0] - The indentation level. + * @returns {string} - The generated JSX for the Material UI component. + */ + const muiGenerator = (child: ChildElement, level: number = 0): string => { let childId = ''; let passedInPropsString = ''; - let key = 0; + let key = ''; + + const MUIComp: MUIType | undefined = MUITypes.find( + (el) => el.tag === child.name + ); + const MUIName: string | undefined = MUIComp?.name; - const MUIComp = MUITypes.find((el) => el.tag === child.name); - const MUIName = MUIComp.name; - // 'passedInProps' will be where the props from the MUI Props panel will saved + if (!MUIComp) { + console.error(`MUI component ${child.name} not found.`); + return ''; // Return empty string if MUI component not found + } + + // Build string for passed-in props child.passedInProps.forEach((prop) => { passedInPropsString += `${prop.key}={${prop.key}} `; }); + // Append default props to the passedInPropsString MUIComp.defaultProps.forEach((prop) => { passedInPropsString += `${prop}`; }); if (child.childId) { childId = `id="${+child.childId}"`; - key = +child.childId; + key = `${+child.childId}`; } // Indent the JSX generated for MUI components based on the provided level const indentedJSX = MUIComp.jsx.map( (line) => `${' '.repeat(level)}${line}` ); + + // Modify and indent JSX let modifiedJSx = modifyAndIndentJsx( indentedJSX, passedInPropsString, childId, - MUIName, + MUIName!, key ); @@ -512,9 +607,16 @@ const generateUnformattedCode = ( level + 1 ).split('\n'); } + return modifiedJSx.join('\n'); }; + /** + * Generates JSX for a route link based on the project type. + * @param {Object} child - The child object representing the route link. + * @param {number} level - The indentation level. + * @returns {string} - The generated JSX for the route link. + */ const handleRouteLink = (child, level) => { const jsxArray = []; const indentation = ' '.repeat(level); @@ -555,8 +657,17 @@ const generateUnformattedCode = ( return jsxArray.map((line) => `${indentation}${line}`).join('\n'); }; - // Function to collect MUI imports as components are processed - function collectMUIImports(component, MUITypes, muiImports) { + /** + * Collects Material UI imports as components are processed. + * @param {ChildElement | MUIType} component - The component being processed. + * @param {MUIType[]} MUITypes - The array of Material UI component types. + * @param {Set} muiImports - The set to store collected Material UI imports. + */ + const collectMUIImports = ( + component: ChildElement | MUIComponent, + MUITypes: MUIType[], + muiImports: Set + ): void => { if (component.type === 'MUI Component') { const muiComponent = MUITypes.find((m) => m.id === component.typeId); if ( @@ -578,31 +689,30 @@ const generateUnformattedCode = ( ); } } - } - // Generate MUI import strings from a set of import statements - function generateMUIImportStatements(muiImports) { - return Array.from(muiImports).join('\n'); - } + }; - // Function to collect state and event handler snippets from components - function collectStateAndEventHandlers( - component, - MUITypes, - handlersCollection - ) { - console.log('collectStateAndEventHandlers invoked'); + /** + * Generates Material UI import statements from a set of import statements. + * @param {Set} muiImports - The set of Material UI import statements. + * @returns {string} - The generated import statements as a single string. + */ + const generateMUIImportStatements = (muiImports: Set): string => + Array.from(muiImports).join('\n'); + + /** + * Collects state and event handler snippets from components. + * @param {ChildElement | MUIType} component - The component being processed. + * @param {MUIType[]} MUITypes - The array of Material UI component types. + * @param {Set} handlersCollection - The set to store collected state and event handler snippets. + */ + const collectStateAndEventHandlers = ( + component: ChildElement | MUIComponent, + MUITypes: MUIType[], + handlersCollection: Set + ): void => { if (component.type === 'MUI Component') { - console.log('collectStateAndEventHandlers MUI check'); const muiComponent = MUITypes.find((m) => m.id === component.typeId); - - console.log('muiComponent found:', JSON.stringify(muiComponent)); // Check what muiComponent is found - console.log( - 'StateAndEventHandlers:', - muiComponent?.stateAndEventHandlers - ); // Direct check - if (muiComponent && Array.isArray(muiComponent.stateAndEventHandlers)) { - console.log('collectStateAndEventHandlers hasState'); muiComponent.stateAndEventHandlers.forEach((handlerSnippet) => { handlersCollection.add(handlerSnippet); }); @@ -617,13 +727,23 @@ const generateUnformattedCode = ( collectStateAndEventHandlers(child, MUITypes, handlersCollection) ); } - } - - // Function to generate code for state and event handlers - function generateStateAndEventHandlerCode(handlersCollection) { - return Array.from(handlersCollection).join('\n'); - } + }; + /** + * Generates Material UI import statements from a set of import statements. + * @param {Set} handlersCollection - The set of Material UI import statements. + * @returns {string} - The generated import statements as a single string. + */ + const generateStateAndEventHandlerCode = ( + handlersCollection: Set + ): string => Array.from(handlersCollection).join('\n'); + + /** + * Writes nested elements based on the provided enriched children. + * @param {Array} enrichedChildren - The array of enriched children to process. + * @param {number} [level=0] - The nesting level of the elements. 0 by default. + * @returns {string[]} - An array of strings representing the nested elements. + */ const writeNestedElements = (enrichedChildren, level = 0) => { return enrichedChildren.flatMap((child) => { if (child.type === 'Component') { @@ -639,15 +759,20 @@ const generateUnformattedCode = ( }); }; - // function to properly incorporate the user created state that is stored in the application state - const writeStateProps = (stateArray: String[]) => { - let stateToRender: String = ''; + /** + * Generates code to properly incorporate the user-created state stored in the application state. + * @param {string[]} stateArray - Array of strings representing the user-created state. + * @returns {string} - A string containing code to incorporate the user-created state. + */ + const writeStateProps = (stateArray: string[]): string => { + let stateToRender: string = ''; for (const element of stateArray) { stateToRender += levelSpacer(2) + element + ';'; } return stateToRender; }; - const enrichedChildren: ChildElement[] = getEnrichedChildren(currComponent); + + const enrichedChildren = getEnrichedChildren(currComponent); // import statements differ between root (pages) and regular components (components) const importsMapped = @@ -685,7 +810,11 @@ const generateUnformattedCode = ( return acc; }, {}); - const createRender = () => { + /** + * Creates a JSX string representing the rendered output of enriched children. + * @returns {string} - The JSX string representing the rendered output. + */ + const createRender = (): string => { const jsxElements = writeNestedElements(enrichedChildren); let jsxString = jsxElements.join('\n'); @@ -717,7 +846,13 @@ const generateUnformattedCode = ( return jsxString; }; - const indentLinesExceptFirst = (text, level) => { + /** + * Indents all lines in a multi-line text except the first line by a specified level of indentation. + * @param {string} text - The text to be indented. + * @param {number} level - The level of indentation (number of spaces). + * @returns {string} - The indented text. + */ + const indentLinesExceptFirst = (text: string, level: number): string => { const lines = text.split('\n'); const firstLine = lines.shift(); // Remove the first line const indentation = ' '.repeat(level); @@ -725,8 +860,11 @@ const generateUnformattedCode = ( return `${firstLine}\n${indentedLines.join('\n')}`; }; - //decide which imports statements to use for which components - const createContextImport = () => { + /** + * Generates import statements for the components based on their contexts. + * @returns {string} - The import statements as a string. + */ + const createContextImport = (): string => { if (!(currComponent.name in componentContext)) return ''; let importStr = ''; @@ -737,7 +875,12 @@ const generateUnformattedCode = ( return importStr; }; - const createEventHandler = (children: ChildElement[]) => { + /** + * Creates event handler functions based on the events defined in the children elements. + * @param {ChildElement[]} children - The array of children elements. + * @returns {string}- The event handler functions as a string. + */ + const createEventHandler = (children: ChildElement[]): string => { let importStr = ''; children.map((child) => { if (child.type === 'HTML Element') { @@ -762,11 +905,12 @@ const generateUnformattedCode = ( return importStr; }; + const muiImportStatements = generateMUIImportStatements(muiImports); const stateAndEventHandlers = generateStateAndEventHandlerCode( muiStateAndEventHandlers ); - console.log('stateAndEventHandlers', stateAndEventHandlers); + let generatedCode = "import React, { useState, useEffect, useContext} from 'react';\n\n"; generatedCode += currComponent.name === 'App' ? contextImports : ''; @@ -849,8 +993,13 @@ const generateUnformattedCode = ( `; } }; -// formats code with prettier linter -const formatCode = (code: string) => { + +/** + * Formats code using the Prettier linter. + * @param {string} code The code string to be formatted. + * @returns {string} - The formatted code string. + */ +const formatCode = (code: string): string => { if (import.meta.env.NODE_ENV === 'test') { const { format } = require('prettier'); return format(code, { diff --git a/app/src/helperFunctions/manageSeparators.ts b/app/src/helperFunctions/manageSeparators.ts index bb1ba88b6..08c6600a3 100644 --- a/app/src/helperFunctions/manageSeparators.ts +++ b/app/src/helperFunctions/manageSeparators.ts @@ -11,9 +11,17 @@ const separator = { framework: '', nestable: true }; +/** + * Manages separators within an array of child elements. + */ const manageSeparators: ManageSeparators = { nextTopSeparatorId: 1000, - // this function checks for two separators in a row or missing separators and adds/removes as needed + /** + * Checks for two separators in a row or missing separators and adds/removes as needed. + * @param arr - Array of child elements. + * @param str - String indicating the action performed ('delete' or 'change position'). + * @returns The ID of the next top separator. + */ handleSeparators: (arr, str) => { if ( (str === 'delete' || str === 'change position') && diff --git a/app/src/helperFunctions/projectGetSaveDel.ts b/app/src/helperFunctions/projectGetSaveDel.ts index fcfd4aed2..3aababf8a 100644 --- a/app/src/helperFunctions/projectGetSaveDel.ts +++ b/app/src/helperFunctions/projectGetSaveDel.ts @@ -13,6 +13,10 @@ if (isDev) { serverURL = `http://localhost:${DEV_PORT}`; } +/** + * Retrieves projects from the server. + * @returns {Promise} - A promise that resolves to an array of projects. + */ export const getProjects = (): Promise => { const projects = fetch(`${serverURL}/getProjects`, { method: 'POST', @@ -30,6 +34,12 @@ export const getProjects = (): Promise => { return projects; //returns an array of projects with _id, name, project }; +/** + * Saves a project to the server. + * @param {string} name - The name of the project. + * @param {State} workspace - The state of the workspace to be saved. + * @returns {Promise} - A promise that resolves to the saved project object. + */ export const saveProject = ( name: string, workspace: State @@ -63,6 +73,12 @@ export const saveProject = ( return project; //returns _id in addition to the project object from the document }; +/** + * Publishes a project on the server. + * @param {string} name - The name of the project. + * @param {State} workspace - The state of the workspace to be published. + * @returns {Promise} - A promise that resolves to the published project object. + */ export const publishProject = ( name: string, workspace: State @@ -103,6 +119,11 @@ export const publishProject = ( return publishedProject; }; +/** + * Unpublishes a project on the server. + * @param {State} projectData - The data of the project to be unpublished. + * @returns {Promise} - A promise that resolves to the unpublished project object. + */ export const unpublishProject = (projectData: State): Promise => { const body = JSON.stringify({ _id: projectData._id @@ -135,6 +156,11 @@ export const unpublishProject = (projectData: State): Promise => { return unpublishedProject; }; +/** + * Deletes a project from the server. + * @param {State} project - The project to be deleted. + * @returns {Promise} - A promise that resolves to the deleted project object. + */ export const deleteProject = (project: any): Promise => { const body = JSON.stringify({ _id: project._id diff --git a/app/src/helperFunctions/randomPassword.ts b/app/src/helperFunctions/randomPassword.ts index fefbba33c..13e53bc36 100644 --- a/app/src/helperFunctions/randomPassword.ts +++ b/app/src/helperFunctions/randomPassword.ts @@ -1,12 +1,28 @@ +/** + * Generates a random password. + * @returns {string} - A randomly generated password string. + */ const randomPassword = () => { + /** + * Generates a random special character. + * @returns {string} - A random special character. + */ function getRandomSpecialChar() { const code = Math.round(Math.random() * (38 - 37) + 37); return String.fromCharCode(code); } + /** + * Generates a random digit. + * @returns {string} - A random digit. + */ function getRandomDigit() { const code = Math.round(Math.random() * (57 - 48) + 48); return String.fromCharCode(code); } + /** + * Generates a random letter. + * @returns {string} - A random letter. + */ function getRandomLetter() { const code = Math.round(Math.random() * (90 - 65) + 65); return String.fromCharCode(code); diff --git a/app/src/helperFunctions/renderChildren.tsx b/app/src/helperFunctions/renderChildren.tsx index 4259b805e..6762b1ef9 100644 --- a/app/src/helperFunctions/renderChildren.tsx +++ b/app/src/helperFunctions/renderChildren.tsx @@ -10,10 +10,14 @@ 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 + +/** + * Renders 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. + * @param {ChildElement[]} children - The array of child elements to render. + * @returns {React.ReactElement[]} - An array of React elements representing the rendered children. + */ const renderChildren = (children: ChildElement[]) => { const state = useSelector((store: RootState) => store.appState); diff --git a/app/src/helperFunctions/socket.ts b/app/src/helperFunctions/socket.ts index f03029425..a29909cb0 100644 --- a/app/src/helperFunctions/socket.ts +++ b/app/src/helperFunctions/socket.ts @@ -4,6 +4,9 @@ import serverConfig from '../serverConfig'; const { API_BASE_URL } = serverConfig; let socket = null; +/** + * Initializes the socket connection. + */ export const initializeSocket = () => { socket = io(API_BASE_URL, { transports: ['websocket'], @@ -12,17 +15,29 @@ export const initializeSocket = () => { }); }; -// export socket to ensure a single socket instance across the entire app +/** + * Returns the socket instance. + * @returns {Socket | null} The socket instance, or null if not initialized. + */ export const getSocket = () => { return socket; }; +/** + * Disconnects the socket if it's currently connected. + */ export const disconnectSocket = () => { if (socket) { socket.disconnect(); } }; +/** + * Emits an event through the socket. + * @param {string} event - The name of the event to emit. + * @param {string} roomCode - The room code to emit the event to. + * @param {any} data - The data to send with the event. + */ export const emitEvent = (event, roomCode, data) => { if (socket) { socket.emit(event, roomCode, data); diff --git a/app/src/helperFunctions/zipFiles.ts b/app/src/helperFunctions/zipFiles.ts index 7b15eb9e4..9b559fbfc 100644 --- a/app/src/helperFunctions/zipFiles.ts +++ b/app/src/helperFunctions/zipFiles.ts @@ -1,9 +1,12 @@ import { saveAs } from 'file-saver'; -import JSZip from'jszip'; +import JSZip from 'jszip'; import { State } from '../interfaces/Interfaces'; -//function to create a zip file for export in web app -const zipFiles = (state: State) => { +/** + * Creates a zip file containing the project files for export. + * @param {State} state - The current state of the project. + */ +const zipFiles = (state: State): void => { //initializes zip var zip = new JSZip(); let reacTypeApp = zip.folder('ReacTypeApp'); diff --git a/app/src/index.tsx b/app/src/index.tsx index d7b42c362..6772beeff 100644 --- a/app/src/index.tsx +++ b/app/src/index.tsx @@ -21,12 +21,23 @@ import Tutorial from './tutorial/Tutorial'; import TutorialPage from './tutorial/TutorialPage'; import store from './redux/store'; +/** + * Initializes an Apollo Client for interacting with a GraphQL API. + * The client is configured to use an in-memory cache. + * @type {ApolloClient} + */ const client = new ApolloClient({ uri: 'https://reactype-caret.herokuapp.com/graphql', cache: new InMemoryCache() }); const isDev = import.meta.env.NODE_ENV === 'development'; + +/** + * Configuration for server URLs, importing development port and API base URL from a server config file. + * Adjusts the server URL based on whether the application is running in development mode. + * @type {string} + */ import serverConfig from './serverConfig.js'; const { DEV_PORT, API_BASE_URL } = serverConfig; @@ -36,7 +47,15 @@ if (isDev) { serverURL = `http://localhost:${DEV_PORT}`; } -const PrivateRoute = ({ component: Component, ...rest }) => { +/** + * A private route component that only renders the specified component if the user is logged in. + * If not logged in, it redirects the user to the login page. It checks both a server session and a guest login state + * stored in localStorage. + * @param {{component: React.ComponentType, [x:string]: any}} props - The component to render and any additional route props. + * @returns {React.ReactNode} - Either the specified component if the user is authenticated, a redirect component to the login page, + * or null while waiting to verify user authentication. + */ +const PrivateRoute = ({ component: Component, ...rest }): React.ReactNode => { const [isLoggedIn, setIsLoggedIn] = useState(null); useEffect(() => { @@ -71,6 +90,12 @@ const PrivateRoute = ({ component: Component, ...rest }) => { ); }; +/** + * Initializes and renders the root component of the React application. It sets up the Apollo client with GraphQL, + * the Redux store for state management, and the React Router for handling routing. It includes both protected and unprotected routes. + * The application is wrapped with necessary providers for state management and data fetching. + * @returns {void} + */ ReactDOM.render( diff --git a/app/src/interfaces/Interfaces.ts b/app/src/interfaces/Interfaces.ts index d01261d72..ab8db327a 100644 --- a/app/src/interfaces/Interfaces.ts +++ b/app/src/interfaces/Interfaces.ts @@ -51,11 +51,14 @@ export interface ChildStyle { } export interface Component { + type?: string; + typeId?: number; + childId?: number; id: number; name: string; style: {}; icon?: any; - attributes?: object; + attributes?: Attributes; events: object; code: string; children: ChildElement[]; @@ -120,7 +123,20 @@ export interface MUIType { defaultProps: string[]; jsx: string[]; componentData: object; - children: []; + children?: MUIType[]; + attributes?: Attributes; +} +export interface MUIComponent { + type: string; + typeId: number; + name: string; + childId: number | null; + style: Record; // Styles can vary, so using a generic Record type + attributes: Record; // Similarly, attributes can vary + events: Record; // Events can vary + children: MUIComponent[]; // Recursive definition for children + stateProps: any[]; // Type for stateProps is not defined in your example, adjust as needed + passedInProps: any[]; // Type for passedInProps is not defined in your example, adjust as needed } export interface DragItem extends DragObjectWithType { newInstance: boolean; diff --git a/app/src/plugins/fetch-plugin.ts b/app/src/plugins/fetch-plugin.ts index e1636d671..9c89bd283 100644 --- a/app/src/plugins/fetch-plugin.ts +++ b/app/src/plugins/fetch-plugin.ts @@ -1,22 +1,38 @@ import * as esbuild from 'esbuild-wasm'; import axios from 'axios'; import localForage from 'localforage'; + +/** + * Creates a file cache instance using localForage. + */ const fileCache = localForage.createInstance({ name: 'filecache' }); -export const fetchPlugin = (inputCode: string) => { + +/** + * A plugin for esbuild to fetch and load code from external sources. + * @param {string} inputCode - The input code to load as the entry file. + * @returns {esbuild.Plugin} - The esbuild plugin for fetching and loading code. + */ +export const fetchPlugin = (inputCode: string): esbuild.Plugin => { return { name: 'fetch-plugin', + /** + * Setup function for the fetch plugin. + * @param {esbuild.PluginBuild} build - The esbuild PluginBuild object. + */ setup(build: esbuild.PluginBuild) { build.onLoad({ filter: /(^index\.js$)/ }, () => { return { loader: 'jsx', - contents: inputCode, + contents: inputCode }; }); - build.onLoad({ filter: /.*/}, async (args: any) => { - const cachedResult = await fileCache.getItem(args.path); - if(cachedResult) { + build.onLoad({ filter: /.*/ }, async (args: any) => { + const cachedResult = await fileCache.getItem( + args.path + ); + if (cachedResult) { return cachedResult; } }); @@ -25,9 +41,8 @@ export const fetchPlugin = (inputCode: string) => { const escaped = data .replace(/\n/g, '') .replace(/"/g, '\\"') - .replace(/'/g, "\\'") - const contents = - ` + .replace(/'/g, "\\'"); + const contents = ` const style = document.createElement('style'); style.innerText = '${escaped}'; document.head.appendChild(style) @@ -36,16 +51,16 @@ export const fetchPlugin = (inputCode: string) => { loader: 'jsx', contents, resolveDir: new URL('./', request.responseURL).pathname - } + }; await fileCache.setItem(args.path, result); return result; }); - build.onLoad({ filter: /.*/ }, async (args: any) => { - - const cachedResult = await fileCache.getItem(args.path); - if(cachedResult) { + const cachedResult = await fileCache.getItem( + args.path + ); + if (cachedResult) { return cachedResult; } const { data, request } = await axios.get(args.path); @@ -54,10 +69,10 @@ export const fetchPlugin = (inputCode: string) => { loader: 'jsx', contents: data, resolveDir: new URL('./', request.responseURL).pathname - } + }; await fileCache.setItem(args.path, result); return result; }); } }; -} \ No newline at end of file +}; diff --git a/app/src/plugins/unpkg-path-plugin.ts b/app/src/plugins/unpkg-path-plugin.ts index 697b44eb1..bd2f253be 100644 --- a/app/src/plugins/unpkg-path-plugin.ts +++ b/app/src/plugins/unpkg-path-plugin.ts @@ -1,7 +1,15 @@ import * as esbuild from 'esbuild-wasm'; -export const unpkgPathPlugin = () => { +/** + * Plugin for esbuild to resolve paths for UNPKG. + * @returns {esbuild.Plugin} - The esbuild plugin for resolving UNPKG paths. + */ +export const unpkgPathPlugin = (): esbuild.Plugin => { return { name: 'unpkg-path-plugin', + /** + * Setup function for the UNPKG path plugin. + * @param {esbuild.PluginBuild} build - The esbuild PluginBuild object. + */ setup(build: esbuild.PluginBuild) { // Handle root entry file of index.js build.onResolve({ filter: /(^index\.js$)/ }, () => { @@ -9,10 +17,14 @@ export const unpkgPathPlugin = () => { }); // Handle relative paths in a module build.onResolve({ filter: /^\.+\// }, (args: any) => { - const seeURL = new URL(args.path, 'https://unpkg.com' + args.resolveDir + '/').href; + const seeURL = new URL( + args.path, + 'https://unpkg.com' + args.resolveDir + '/' + ).href; return { namespace: 'a', - path: new URL(args.path, 'https://unpkg.com' + args.resolveDir + '/').href + path: new URL(args.path, 'https://unpkg.com' + args.resolveDir + '/') + .href }; }); // Handle main file of a module @@ -20,8 +32,8 @@ export const unpkgPathPlugin = () => { return { namespace: 'a', path: `https://unpkg.com/${args.path}` - } + }; }); - }, + } }; -}; \ No newline at end of file +}; diff --git a/app/src/public/styles/style.css b/app/src/public/styles/style.css index cf9b4931c..e8f90b673 100644 --- a/app/src/public/styles/style.css +++ b/app/src/public/styles/style.css @@ -773,6 +773,16 @@ CANVAS WINDOW height: 100%; } +#renderFocus { + position: relative; + min-width: 50px; + max-width: 1200px; + width: calc(100% - 280px); + height: 100%; + resize: horizontal; + overflow: auto; +} + /* Material-UI */ /* Sortable tree sorting */ .sortable-tree { diff --git a/app/src/redux/MUITypes.ts b/app/src/redux/MUITypes.ts index 7749b5163..62a890e74 100644 --- a/app/src/redux/MUITypes.ts +++ b/app/src/redux/MUITypes.ts @@ -8,12 +8,12 @@ import { MUIType } from '../interfaces/Interfaces'; 24. Checkbox 25. Floating Action Button 26. Radio Group -27. Rating +27. Rating // not working 28. Select 29. Slider 30. Switch 31. Text Field -32. Transfer List //not working +32. Transfer List // not working 33. Toggle Button DATA DISPLAY @@ -24,17 +24,17 @@ DATA DISPLAY 38. Icons 39. Material Icons 40. List -41. Table // is working now. how? +41. Table 42. Tooltip // not working 43. Typography FEEDBACK -44. Alert //working but icon needed +44. Alert 45. Backdrop 46. Dialog -47. Progress //not working -48. Skeleton //not working -49. Snackbar //not working +47. Progress +48. Skeleton +49. Snackbar SURFACES 50. Accordion @@ -51,16 +51,16 @@ NAVIGATION 59. Pagination 60. Speed Dial 61. Stepper //not working -62. Tabs //not working (no state) +62. Tabs LAYOUT 63. Box -64. Container //not working (odd visual) -65. Grid //not included -66. Grid v2 //not working +64. Container +65. Grid - not included +66. Grid v2 68. Stack -69. Image List //not working -70. Hidden - deprecated not working +69. Image List +70. Hidden - deprecated UTILS 71. Click-Away Listener - not included @@ -68,10 +68,10 @@ UTILS 73. Modal 74. No SSR - not included 75. Popover -76. Popper //not working (no state) +76. Popper 77. Portal - not included 78. Textarea Autosize - not included -79. Transitions // not working +79. Transitions 80. useMediaQuery - not included */ const MUITypes: MUIType[] = [ @@ -2021,7 +2021,8 @@ const MUITypes: MUIType[] = [ type: 'Alert', props: { icon: '', - severity: 'success' + severity: 'success', + sx: { m: 1 } }, children: 'Here is a gentle confirmation that your action was successful.' }, @@ -2078,7 +2079,7 @@ const MUITypes: MUIType[] = [ componentData: { type: 'div', - props: {}, + props: { sx: { m: 1 } }, children: [ { type: 'Button', @@ -2185,6 +2186,7 @@ const MUITypes: MUIType[] = [ { type: 'Button', props: { + sx: { m: 1 }, variant: 'outlined', onClick: '{handleClickOpen}', role: 'modalTrigger' //used 'modalTrigger' here because it is requires the same handleOpen function already created @@ -2255,67 +2257,29 @@ const MUITypes: MUIType[] = [ framework: 'reactClassic', nestable: false, imports: [ - "import CircularProgress, { CircularProgressProps } from '@mui/material/CircularProgress'", - "import Typography from '@mui/material/Typography'", + "import CircularProgress from '@mui/material/CircularProgress';", "import Box from '@mui/material/Box'" ], stateAndEventHandlers: [], - defaultProps: ['value={progress}'], - propOptions: ['value'], - jsx: [ - '', - ' ', - ' ', - ' ', - ' {`${Math.round(progress)}%`}', - ' ', - ' ', - '' + defaultProps: [], + propOptions: [ + 'classes', + 'color', + 'disableShrink', + 'size', + 'sx', + 'thickness', + 'value', + 'variant' ], + jsx: ['', ' ', ''], componentData: { type: 'Box', - props: { sx: { position: 'relative', display: 'inline-flex' } }, + props: { sx: { display: 'flex', m: 1 } }, children: [ { type: 'CircularProgress', - props: { variant: 'determinate', value: '{progress}' } - }, - { - type: 'Box', - props: { - sx: { - top: 0, - left: 0, - bottom: 0, - right: 0, - position: 'absolute', - display: 'flex', - alignItems: 'center', - justifyContent: 'center' - } - }, - children: [ - { - type: 'Typography', - props: { - variant: 'caption', - component: 'div', - color: 'text.secondary', - children: '{`${Math.round(progress)}%`}' - } - } - ] + props: {} } ] }, @@ -2332,9 +2296,8 @@ const MUITypes: MUIType[] = [ framework: 'reactClassic', nestable: true, imports: [ - "import Typography, { TypographyProps } from '@mui/material/Typography'", "import Skeleton from '@mui/material/Skeleton'", - "import Grid from '@mui/material/Grid'" + "import Stack from '@mui/material/Stack'" ], stateAndEventHandlers: [], defaultProps: ['loading'], @@ -2349,41 +2312,38 @@ const MUITypes: MUIType[] = [ 'width' ], jsx: [ - '', - ' ', - ' ', - ' ', - ' ', - ' ', - ' ', - '' + '', + ' {/* For variant="text", adjust the height via font-size */}', + ' ', + ' {/* For other variants, adjust the size with `width` and `height` */}', + ' ', + ' ', + ' ', + '' ], componentData: { type: 'SkeletonTypography', props: {}, children: [ { - type: 'Grid', - props: { container: true, spacing: 8 }, + type: 'Stack', + props: { spacing: 1, m: 1 }, children: [ { - type: 'Grid', - props: { item: true, xs: true }, - children: [ - { - type: 'TypographyDemo', - props: { loading: true } - } - ] + type: 'Skeleton', + props: { variant: 'text', sx: { fontSize: '1rem' } } }, { - type: 'Grid', - props: { item: true, xs: true }, - children: [ - { - type: 'TypographyDemo' - } - ] + type: 'Skeleton', + props: { variant: 'circular', width: 40, height: 40 } + }, + { + type: 'Skeleton', + props: { variant: 'rectangular', width: 210, height: 60 } + }, + { + type: 'Skeleton', + props: { variant: 'rounded', width: 210, height: 60 } } ] } @@ -2407,8 +2367,36 @@ const MUITypes: MUIType[] = [ "import IconButton from '@mui/material/IconButton'", "import CloseIcon from '@mui/icons-material/Close'" ], - stateAndEventHandlers: [], - defaultProps: [], + stateAndEventHandlers: [ + 'const [open, setOpen] = React.useState(false);', + '\nconst handleClick = () => {', + ' setOpen(true);', + '};', + '\nconst handleClose = (event, reason) => {', + " if (reason === 'clickaway') {", + ' return;', + ' }', + ' setOpen(false);', + '};', + '\nconst action = (', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' );' + ], + defaultProps: [ + 'open={open} autoHideDuration={6000} onClose={handleClose} message="Note archived" action={action}' + ], propOptions: [ 'action', 'anchorOrigin', @@ -2431,22 +2419,16 @@ const MUITypes: MUIType[] = [ jsx: [ '
', ' ', - ' ', + ' ', '
' ], componentData: { type: 'SimpleSnackbar', - props: {}, + props: { sx: { m: 1 } }, children: [ { type: 'Button', - props: { onClick: '{handleClick}' }, + props: { onClick: '{handleClick}', role: 'modalTrigger' }, children: 'Open Snackbar' }, { @@ -3041,7 +3023,7 @@ const MUITypes: MUIType[] = [ ' ))}', ' ', ' ', - ')\n' + ');\n' ], defaultProps: ['open={open} onClose={toggleDrawer(false)}'], propOptions: [ @@ -3730,9 +3712,11 @@ const MUITypes: MUIType[] = [ ], stateAndEventHandlers: [ 'const [value, setValue] = React.useState(0);', - 'const handleChange = (event, newValue) => { setValue(newValue); };\n' + '\nconst handleChange = (event, newValue) => { setValue(newValue); };\n' + ], + defaultProps: [ + 'value={value} onChange={handleChange} aria-label="basic tabs example"' ], - defaultProps: [], propOptions: [ 'children', 'classes', @@ -3791,21 +3775,21 @@ const MUITypes: MUIType[] = [ type: 'Tab', props: { label: 'Item One', - '...': 'a11yProps(0)' + index: 0 } }, { type: 'Tab', props: { label: 'Item Two', - '...': 'a11yProps(1)' + index: 1 } }, { type: 'Tab', props: { label: 'Item Three', - '...': 'a11yProps(2)' + index: 2 } } ] @@ -3926,7 +3910,14 @@ const MUITypes: MUIType[] = [ "import { styled } from '@mui/material/styles'", "import Box from '@mui/material/Box'", "import Paper from '@mui/material/Paper'", - "import Grid from '@mui/material/Grid'" + "import Grid from '@mui/material/Grid'", + '\nconst Item = styled(Paper)(({ theme }) => ({', + ' backgroundColor: theme.palette.mode === "dark" ? "#1A2027" : "#fff",', + ' ...theme.typography.body2,', + ' padding: theme.spacing(1),', + ' textAlign: "center",', + ' color: theme.palette.text.secondary,', + '}));' ], stateAndEventHandlers: [], defaultProps: ['container spacing={2}'], @@ -3969,28 +3960,58 @@ const MUITypes: MUIType[] = [ '' ], componentData: { - type: 'Grid', - props: { container: true, spacing: 2 }, + type: 'Box', + props: { sx: { flexGrow: 1, m: 1 } }, children: [ { type: 'Grid', - props: { item: true, xs: 8 }, - children: [{ type: 'Item', props: {}, children: 'xs=8' }] - }, - { - type: 'Grid', - props: { item: true, xs: 4 }, - children: [{ type: 'Item', props: {}, children: 'xs=4' }] - }, - { - type: 'Grid', - props: { item: true, xs: 4 }, - children: [{ type: 'Item', props: {}, children: 'xs=4' }] - }, - { - type: 'Grid', - props: { item: true, xs: 8 }, - children: [{ type: 'Item', props: {}, children: 'xs=8' }] + props: { container: true }, + children: [ + { + type: 'Grid', + props: { item: true, xs: 7, sx: { m: 1 } }, + children: [ + { + type: 'Item', + props: { backgroundColor: '#1A2027', textColor: '#FFFFFF' }, + children: 'xs=7' + } + ] + }, + { + type: 'Grid', + props: { item: true, xs: 4, role: 'grid-item', sx: { m: 1 } }, + children: [ + { + type: 'Item', + props: { backgroundColor: '#1A2027', textColor: '#FFFFFF' }, + children: 'xs=4' + } + ] + }, + { + type: 'Grid', + props: { item: true, xs: 4, role: 'grid-item', sx: { m: 1 } }, + children: [ + { + type: 'Item', + props: { backgroundColor: '#1A2027', textColor: '#FFFFFF' }, + children: 'xs=4' + } + ] + }, + { + type: 'Grid', + props: { item: true, xs: 7, role: 'grid-item', sx: { m: 1 } }, + children: [ + { + type: 'Item', + props: { backgroundColor: '#1A2027', textColor: '#FFFFFF' }, + children: 'xs=7' + } + ] + } + ] } ] }, @@ -4034,7 +4055,7 @@ const MUITypes: MUIType[] = [ ], componentData: { type: 'Stack', - props: { spacing: 2 }, + props: { spacing: 2, sx: { mt: 1, mb: 1 } }, children: [ { type: 'Item', props: {}, children: 'Item 1' }, { type: 'Item', props: {}, children: 'Item 2' }, @@ -4045,49 +4066,99 @@ const MUITypes: MUIType[] = [ }, { id: 69, - tag: 'image-list', - name: 'ImageList', + tag: 'imageList', + name: 'Image List', + style: {}, + placeHolderShort: 'imageList', + placeHolderLong: 'Material UI Image List Component', icon: 'ArtTrack', + framework: 'reactClassic', nestable: false, - props: { - children: { - type: 'node', - description: 'The content of the component, normally ImageListItems.' - }, - classes: { - type: 'object', - description: 'Override or extend the styles applied to the component.' - }, - cols: { - type: 'integer', - default: 2, - description: 'Number of columns.' - }, - component: { - type: 'elementType', - description: 'The component used for the root node.' - }, - gap: { - type: 'number', - default: 4, - description: 'The gap between items in px.' - }, - rowHeight: { - type: 'string | number', - default: 'auto', - description: 'The height of one row in px.' - }, - sx: { - type: 'array | func | object | bool', - description: - 'The system prop that allows defining system overrides as well as additional CSS styles.' - }, - variant: { - type: "'masonry' | 'quilted' | 'standard' | 'woven' | string", - default: 'standard', - description: 'The variant to use.' - } - } + imports: [ + "import ImageList from '@mui/material/ImageList'", + "import ImageListItem from '@mui/material/ImageListItem'", + '\nconst itemData = [', + ' {', + " img: 'https://images.unsplash.com/photo-1551963831-b3b1ca40c98e',", + " title: 'Breakfast',", + ' },', + ' {', + " img: 'https://images.unsplash.com/photo-1551782450-a2132b4ba21d',", + " title: 'Burger',", + ' },', + ' {', + " img: 'https://images.unsplash.com/photo-1522770179533-24471fcdba45',", + " title: 'Camera',", + ' },', + ' {', + " img: 'https://images.unsplash.com/photo-1444418776041-9c7e33cc5a9c',", + " title: 'Coffee',", + ' },', + ' {', + " img: 'https://images.unsplash.com/photo-1533827432537-70133748f5c8',", + " title: 'Hats',", + ' },', + ' {', + " img: 'https://images.unsplash.com/photo-1558642452-9d2a7deb7f62',", + " title: 'Honey',", + ' },', + ' {', + " img: 'https://images.unsplash.com/photo-1516802273409-68526ee1bdd6',", + " title: 'Basketball',", + ' },', + ' {', + " img: 'https://images.unsplash.com/photo-1518756131217-31eb79b20e8f',", + " title: 'Fern',", + ' },', + ' {', + " img: 'https://images.unsplash.com/photo-1597645587822-e99fa5d45d25',", + " title: 'Mushrooms',", + ' },', + ' {', + " img: 'https://images.unsplash.com/photo-1567306301408-9b74779a11af',", + " title: 'Tomato basil',", + ' },', + ' {', + " img: 'https://images.unsplash.com/photo-1471357674240-e1a485acb3e1',", + " title: 'Sea star',", + ' },', + ' {', + " img: 'https://images.unsplash.com/photo-1589118949245-7d38baf380d6',", + " title: 'Bike',", + ' },' + ], + stateAndEventHandlers: [], + defaultProps: ['sx={{ width: 500, height: 450 }} cols={3} rowHeight={164}'], + propOptions: [ + 'children', + 'classes', + 'cols', + 'component', + 'gap', + 'rowHeight', + 'sx', + 'variant' + ], + jsx: [ + '', + ' {itemData.map((item) => (', + ' ', + ' ', + ' ', + ' ))}', + '' + ], + componentData: { + type: 'ImageList', + props: { sx: { m: 1 } }, + children: [] + }, + children: [] }, { id: 73, diff --git a/app/src/tree/TreeChart.tsx b/app/src/tree/TreeChart.tsx index 8ba481ef2..2921aa2b2 100644 --- a/app/src/tree/TreeChart.tsx +++ b/app/src/tree/TreeChart.tsx @@ -12,7 +12,26 @@ function usePrevious(value) { return ref.current; } -function TreeChart({ data }) { +/** + * `TreeChart` is a React component that visualizes a tree structure using D3.js. It is designed to display + * hierarchical data related to the application's state components, such as UI elements or nested components. + * The component dynamically updates to reflect changes in the data or the component's dimensions. + * + * The visualization includes nodes represented by circles, connected by lines (links), with labels displaying + * the name of each node. It uses the `useResizeObserver` hook to react to changes in the container's size, + * ensuring the tree layout adjusts accordingly. + * + * @param {Object} props - Properties passed to the component. + * @param {Array} props.data - The hierarchical data used to generate the tree structure, typically derived from the application's state. + * @returns {JSX.Element} A responsive SVG element that renders the tree visualization inside a styled container. + * + * The component processes the incoming data to remove any unwanted items (like separators) and ensures that + * the structure is suitable for visualization. It uses D3's tree layout to calculate the positions of nodes + * and links based on the available space. This layout is responsive, adapting to changes in the container's size. + * The component integrates with Redux to access relevant parts of the application state and uses local state + * to manage modal dialogs and other UI elements. + */ +function TreeChart({ data }): JSX.Element { // data is components from state - passed in from BottomTabs const state = useSelector((store) => store.appState); diff --git a/app/src/tree/useResizeObserver.ts b/app/src/tree/useResizeObserver.ts index d9adf16b1..cc0459702 100644 --- a/app/src/tree/useResizeObserver.ts +++ b/app/src/tree/useResizeObserver.ts @@ -1,7 +1,14 @@ -import { useEffect, useState } from 'react'; +import { RefObject, useEffect, useState } from 'react'; import ResizeObserver from 'resize-observer-polyfill'; -const useResizeObserver = (ref) => { +/** + * A custom React hook that observes the resize of a specified DOM element. + * @param {RefObject} ref - The reference to the DOM element to observe. + * @returns {DOMRectReadOnly | null} - The dimensions of the observed element, or null if not available. + */ +const useResizeObserver = ( + ref: RefObject +): DOMRectReadOnly | null => { const [dimensions, setDimensions] = useState(null); useEffect(() => { // the element being observed (div with green border) diff --git a/app/src/tutorial/CSSEditor.tsx b/app/src/tutorial/CSSEditor.tsx index 4dd6c5565..2ec9f5555 100644 --- a/app/src/tutorial/CSSEditor.tsx +++ b/app/src/tutorial/CSSEditor.tsx @@ -3,35 +3,55 @@ import cssEditorTab from '../../../resources/csseditor_tutorial_images/CSSEditor import cssEdit from '../../../resources/csseditor_tutorial_images/DirectCSSEdit.gif'; import copyPaste from '../../../resources/csseditor_tutorial_images/CopyPasteCSS.gif'; +/** + * CSSEditor component displays the tutorial for the CSS editor tab, allowing users to edit CSS code directly and see the changes reflected immediately. + * + * @param {object} props - Component props. + * @param {object} props.classes - CSS classes for styling. + * @param {Function} props.setPage - Function to set the current page. + * @returns {JSX.Element} CSSEditor component JSX. + */ const CSSEditor: React.FC<{ classes: any; setPage: Function; -}> = ({ classes, setPage }) => { +}> = ({ classes, setPage }): JSX.Element => { return (

CSS Editor


-

The CSS editor tab is located in the bottom panel of the application and is where the CSS file's - code is located.

- You are able to freely edit the CSS code directly and have the changes reflected immediately in the demo render panel. - You can also copy and paste your own CSS code into the editor to take full control of custom CSS classes.
+

+ The CSS editor tab is located in the bottom panel of the application and + is where the CSS file's code is located. +
+
+ You are able to freely edit the CSS code directly and have the changes + reflected immediately in the demo render panel. You can also copy and + paste your own CSS code into the editor to take full control of custom + CSS classes. +

- +
-
+

Edit CSS code

-

To edit the CSS code in the CSS editor, make the desired changes directly within the editor.
+

+ To edit the CSS code in the CSS editor, make the desired changes + directly within the editor. +

- -

-

Or copy and paste your own CSS code directly into the editor. -


+ +
+
+

+ Or copy and paste your own CSS code directly into the editor. +

+
- +
-
+
); }; diff --git a/app/src/tutorial/Canvas.tsx b/app/src/tutorial/Canvas.tsx index 3ef4d2160..a001e6ddd 100644 --- a/app/src/tutorial/Canvas.tsx +++ b/app/src/tutorial/Canvas.tsx @@ -3,10 +3,18 @@ import canvas1 from '../../../resources/canvas_tutorial_images/canvas1.png'; import drag1 from '../../../resources/canvas_tutorial_images/drag1.gif'; import undoRedo from '../../../resources/canvas_tutorial_images/undoRedo.gif'; +/** + * Canvas component displays tutorial for HTML elements, components, MUI Components of the prototype application. + * + * @param {object} props - Component props. + * @param {object} props.classes - CSS classes for styling. + * @param {Function} props.setPage - Function to set the current page. + * @returns {JSX.Element} Canvas component JSX. + */ const Canvas: React.FC<{ classes: any; setPage: Function; -}> = ({ classes, setPage }) => { +}> = ({ classes, setPage }): JSX.Element => { return (

Canvas

diff --git a/app/src/tutorial/CodePreview.tsx b/app/src/tutorial/CodePreview.tsx index 35ddb59f7..80177963a 100644 --- a/app/src/tutorial/CodePreview.tsx +++ b/app/src/tutorial/CodePreview.tsx @@ -1,10 +1,18 @@ import React from 'react'; import codePreview from '../../../resources/code_preview_images/CodePreview.png'; +/** + * CodePreview component displays tutorial for the code preview at the bottom center panel of the page. + * + * @param {object} props - Component props. + * @param {object} props.classes - CSS classes for styling. + * @param {Function} props.setPage - Function to set the current page. + * @returns {JSX.Element} CodePreview component JSX. + */ const CodePreview: React.FC<{ classes: any; setPage: Function; -}> = ({ classes, setPage }) => { +}> = ({ classes, setPage }): JSX.Element => { return (

Code Preview

diff --git a/app/src/tutorial/ComponentTree.tsx b/app/src/tutorial/ComponentTree.tsx index 57fe1cbc6..edfc54a2b 100644 --- a/app/src/tutorial/ComponentTree.tsx +++ b/app/src/tutorial/ComponentTree.tsx @@ -5,39 +5,87 @@ import tree3 from '../../../resources/tree_tutorial_images/tree3.png'; import tree4 from '../../../resources/tree_tutorial_images/tree4.png'; import tree5 from '../../../resources/tree_tutorial_images/tree5.png'; +/** + * ComponentTree component displays the tutorial for the React component tree, providing a visual representation of the component hierarchy. + * + * @param {object} props - Component props. + * @param {object} props.classes - CSS classes for styling. + * @param {Function} props.setPage - Function to set the current page. + * @returns {JSX.Element} ComponentTree component JSX. + */ const ComponentTree: React.FC<{ classes: any; setPage: Function; -}> = ({ classes, setPage }) => { +}> = ({ classes, setPage }): JSX.Element => { return (

React Component Tree

-
-

The component tree provides the developer with a visual representation of the component hierarchy. The tree updates in real time as the developer adds or deletes components and HTML elements.

+
+

+ The component tree provides the developer with a visual representation + of the component hierarchy. The tree updates in real time as the + developer adds or deletes components and HTML elements. +

-
-

Each tree begins with a root node. The current page that is selected represents the root node.

+
+

+ Each tree begins with a root node. The current page that is selected + represents the root node. +

-
-

setPage('Reusable Components')} >Reusable components are shown attached to the current page along with their subtrees of components and setPage('HTML Elements')} >HTML elements.

+
+

+ setPage('Reusable Components')} + > + Reusable components + {' '} + are shown attached to the current page along with their subtrees of + components and{' '} + setPage('HTML Elements')} + > + HTML elements + + . +

-
-

setPage('HTML Elements')} >HTML elements are shown by their tag name.

+
+

+ setPage('HTML Elements')} + > + HTML elements + {' '} + are shown by their tag name. +

-
-

You can also view the tree for each setPage('Reusable Components')} >reusable component.

+
+

+ You can also view the tree for each{' '} + setPage('Reusable Components')} + > + reusable component + + . +

-
+
); }; diff --git a/app/src/tutorial/Customization.tsx b/app/src/tutorial/Customization.tsx index acb76f0ce..262ffa0f3 100644 --- a/app/src/tutorial/Customization.tsx +++ b/app/src/tutorial/Customization.tsx @@ -8,26 +8,51 @@ import link from '../../../resources/customizing_elements_images/linkState.png'; import cssClasses from '../../../resources/customizing_elements_images/CSS.png'; import textGif from '../../../resources/customizing_elements_images/text.gif'; +/** + * Customization component displays the tutorial for the features to customize HTML elements on the canvas. + * + * @param {object} props - Component props. + * @param {object} props.classes - CSS classes for styling. + * @param {Function} props.setPage - Function to set the current page. + * @returns {JSX.Element} Customization component JSX. + */ const Customization: React.FC<{ classes: any; setPage: Function; -}> = ({ classes, setPage }) => { +}> = ({ classes, setPage }): JSX.Element => { return (

Customization


- Customize your HTML elements on the canvas with the below features. Any changes
- made in the setPage('Customization')}> - customization panel will be reflected immediately in the setPage('Code Preview')}> - code preview and demo render panel. See your changes in real time to decide what's best!

- To customize an HTML element, drag it onto the canvas and select it on the canvas. Then use the desired customization feature. - Once done, press the save button to save your customization changes. + Customize your HTML elements on the canvas with the below features. Any + changes
+ made in the{' '} + setPage('Customization')} + > + customization + {' '} + panel will be reflected immediately in the{' '} + setPage('Code Preview')} + > + code preview{' '} + {' '} + and demo render panel. See your changes in real time to decide what's + best! +
+
+ To customize an HTML element, drag it onto the canvas and select it on + the canvas. Then use the desired customization feature. Once done, press + the save button to save your customization changes.


Display

- +

After having moved a{' '} @@ -65,7 +90,7 @@ const Customization: React.FC<{


Width

- +

Change the width of each{' '} @@ -94,7 +119,7 @@ const Customization: React.FC<{


Height

- +

Change the height of each{' '} @@ -123,46 +148,60 @@ const Customization: React.FC<{


Background Color

- +

- Select an element and type in the color you wish to change the background - color to and then click save. + Select an element and type in the color you wish to change the + background color to and then click save.


Text

- +

- Add HTML text to a selected element on the canvas by typing in the desired text. + Add HTML text to a selected element on the canvas by typing in the + desired text.



- You can also add state to the text of your element by clicking the "Use State" button. As shown in the example below, when you click "Use State", a window will pop up, showing all state available in the current component. You can click on any of these state variables and it will applied to the HTML text. When you click "Save", you can see a live Demo Render of your customization changes. + You can also add state to the text of your element by clicking the "Use + State" button. As shown in the example below, when you click "Use + State", a window will pop up, showing all state available in the current + component. You can click on any of these state variables and it will + applied to the HTML text. When you click "Save", you can see a live Demo + Render of your customization changes.



- -
+ +


Link

- +

- Add a hyperlink to a selected element on the canvas by typing in the url. + Add a hyperlink to a selected element on the canvas by typing in the + url.


CSS Classes

- +

- Change the CSS class of a selected element on the canvas by typing in the class name.
- ReactType also comes with a default CSS file that is shown in the setPage('CSS Editor')} >CSS editor. - Add your own CSS classes to the setPage('CSS Editor')} >CSS editor or make changes to it to use - custom CSS classes. + Change the CSS class of a selected element on the canvas by typing in + the class name.
+ ReactType also comes with a default CSS file that is shown in the{' '} + setPage('CSS Editor')}> + CSS editor + + . Add your own CSS classes to the{' '} + setPage('CSS Editor')}> + CSS editor + {' '} + or make changes to it to use custom CSS classes.


diff --git a/app/src/tutorial/HtmlElements.tsx b/app/src/tutorial/HtmlElements.tsx index 962c740ae..54c85daf1 100644 --- a/app/src/tutorial/HtmlElements.tsx +++ b/app/src/tutorial/HtmlElements.tsx @@ -4,43 +4,108 @@ import createNew from '../../../resources/html_elements_tutorial_images/createNe import newTag from '../../../resources/html_elements_tutorial_images/newTag.png'; import codeSnippet from '../../../resources/html_elements_tutorial_images/codeSnippet.png'; +/** + * HtmlElements component displays the tutorial for the information about HTML elements in ReacType. + * + * @param {object} props - Component props. + * @param {object} props.classes - CSS classes for styling. + * @param {Function} props.setPage - Function to set the current page. + * @returns {JSX.Element} HtmlElements component JSX. + */ const HtmlElements: React.FC<{ classes: any; setPage: Function; -}> = ({ classes, setPage }) => { +}> = ({ classes, setPage }): JSX.Element => { return (

HTML Elements

-
-

ReacType has default elements to help get you started.
- You can drag and drop elements into the setPage('Canvas')} >Canvas to create a React Component with HTML Elements.

-
+
+

+ ReacType has default elements to help get you started. +
+ You can drag and drop elements into the{' '} + setPage('Canvas')}> + Canvas + {' '} + to create a React Component with HTML Elements. +

+
-
+

Custom Elements

-

You can create new custom elements to better suit your needs.
- Click here for a link to more HTML tags that you can add.
- "Tag" should be the HTML tag you are creating and "Tag Name" should be something that makes it easy to remember what this tag is/does.
- You can also create your own custom elements besides the standard HTML Elements. For example you can create an element <hello><hello> and it will work! You can add functionality to these elements once you export your project. Just be sure to import them into the files where you are using them! For more information on how to create custom tags check out these resources from HTML5Rocks and smashing magazine. +

+ You can create new custom elements to better suit your needs. +
+ Click{' '} + + here + {' '} + for a link to more HTML tags that you can add. +
+ "Tag" should be the HTML tag you are creating and "Tag Name" should be + something that makes it easy to remember what this tag is/does. +
+ You can also create your own custom elements besides the standard HTML + Elements. For example you can create an element + <hello><hello> and it will work! You can add functionality + to these elements once you export your project. Just be sure to import + them into the files where you are using them! For more information on + how to create custom tags check out these resources from{' '} + + HTML5Rocks + {' '} + and{' '} + + smashing magazine + + .

-
+
-
+

Persisting Elements

-

Saving the project (available only to users) will allow you to save custom elements that you created. However, when opening a new project, only the tags saved for each specific project will show up again.
- In order to save custom tags across multiple projects, we recommend creating custom tags first, then saving multiple projects with the custom tags. This will allow access to custom tags across multiple projects.

-
+

+ Saving the project (available only to users) will allow you to save + custom elements that you created. However, when opening a new project, + only the tags saved for each specific project will show up again. +
+ In order to save custom tags across multiple projects, we recommend + creating custom tags first, then saving multiple projects with the + custom tags. This will allow access to custom tags across multiple + projects. +

+

Customization

-

Add attributes to elements in the generated code in the code preview. When making changes/editing the code, please make sure not to add anymore components/elements to the canvas. This should be the final step before exporting your project. Please see setPage('Code Preview')}>Code Preview for more details on when/how to make changes to your code in ReacType.

-
+

+ Add attributes to elements in the generated code in the code preview. + When making changes/editing the code, please make sure not to add + anymore components/elements to the canvas. This should be the final step + before exporting your project. Please see{' '} + setPage('Code Preview')} + > + Code Preview + {' '} + for more details on when/how to make changes to your code in ReacType. +

+
-
+
); }; export default HtmlElements; - diff --git a/app/src/tutorial/KeyboardShortcuts.tsx b/app/src/tutorial/KeyboardShortcuts.tsx index 4acd6d4c8..ea7721090 100644 --- a/app/src/tutorial/KeyboardShortcuts.tsx +++ b/app/src/tutorial/KeyboardShortcuts.tsx @@ -1,9 +1,17 @@ import React from 'react'; +/** + * KeyboardShortcuts component displays tutorial for the keyboard shortcuts for Mac and Windows. + * + * @param {object} props - Component props. + * @param {object} props.classes - CSS classes for styling. + * @param {Function} props.setPage - Function to set the current page. + * @returns {JSX.Element} KeyboardShortcuts component JSX. + */ const KeyboardShortcuts: React.FC<{ classes: any; setPage: Function; -}> = ({ classes, setPage }) => { +}> = ({ classes, setPage }): JSX.Element => { return (

Keyboard Shortcuts

diff --git a/app/src/tutorial/Pages.tsx b/app/src/tutorial/Pages.tsx index df9a1747d..de757a316 100644 --- a/app/src/tutorial/Pages.tsx +++ b/app/src/tutorial/Pages.tsx @@ -5,10 +5,18 @@ import deletePage from '../../../resources/pages_images/DeletePage.png'; import pagesPanel from '../../../resources/pages_images/PagesPanel.png'; import pageSwapping from '../../../resources/pages_images/PagesSwapping.gif'; +/** + * Pages component displays tutorial for the functionality related to managing pages. + * + * @param {object} props - Component props. + * @param {object} props.classes - CSS classes for styling. + * @param {Function} props.setPage - Function to set the current page. + * @returns {JSX.Element} Pages component JSX. + */ const Pages: React.FC<{ classes: any; setPage: Function; -}> = ({ classes, setPage }) => { +}> = ({ classes, setPage }): JSX.Element => { return (

Pages

diff --git a/app/src/tutorial/ReusableComponents.tsx b/app/src/tutorial/ReusableComponents.tsx index a5b086e90..9f7fd45ad 100644 --- a/app/src/tutorial/ReusableComponents.tsx +++ b/app/src/tutorial/ReusableComponents.tsx @@ -3,37 +3,72 @@ import reusableComponents1 from '../../../resources/reusable_components_tutorial import reusableComponents2 from '../../../resources/reusable_components_tutorial_images/reusablecomponents2.png'; import reusableComponents3 from '../../../resources/reusable_components_tutorial_images/reusablecomponents3.png'; +/** + * ReusableComponents component displays tutorial for the functionality related to reusable components. + * + * @param {object} props - Component props. + * @param {object} props.classes - CSS classes for styling. + * @param {Function} props.setPage - Function to set the current page. + * @returns {JSX.Element} ReusableComponents component JSX. + */ const ReusableComponents: React.FC<{ classes: any; setPage: Function; -}> = ({ classes, setPage }) => { +}> = ({ classes, setPage }): JSX.Element => { return (

Reusable Components

-
-

NOTE: As of version 13.0, each reusable component can only have one parent. Otherwise, you will be prompted to generate a new component to nest. -

-

To add a Reusable Component, use the New Component input form in the Creation Panel to name a Component. Leave the Root/Page checkbox unchecked. Then click Create to add a new Reusable Component.

+
+

+ NOTE: As of version 13.0, each reusable component can only have one + parent. Otherwise, you will be prompted to generate a new component to + nest. +

+

To add a Reusable Component, use the New Component input form + in the Creation Panel to name a Component. Leave the Root/Page checkbox + unchecked. Then click Create to add a new Reusable Component. +

-
-

The Components you create will populate on the left panel under the section 'Reusable Components'.

+
+

+ The Components you create will populate on the left panel under the + section 'Reusable Components'. +

-
-

After creating the desired Component, you can now drag-n-drop to the Canvas. - If you'd like to know about about the drag-n-drop functionality, please locate the setPage('Canvas')} >Canvas Tutorial for more information on how it works. +


+

+ After creating the desired Component, you can now drag-n-drop to the + Canvas. If you'd like to know about about the drag-n-drop functionality, + please locate the{' '} + setPage('Canvas')}> + Canvas Tutorial + {' '} + for more information on how it works.

-

You can place a reusable component inside setPage('Pages')} >Pages and populate the component itself with other setPage('HTML_Elements')} >HTML Elements.

-
+

+ You can place a reusable component inside{' '} + setPage('Pages')}> + Pages + {' '} + and populate the component itself with other{' '} + setPage('HTML_Elements')} + > + HTML Elements + + . +

+
); }; export default ReusableComponents; - diff --git a/app/src/tutorial/RouteLinks.tsx b/app/src/tutorial/RouteLinks.tsx index 786d09564..270652d6f 100644 --- a/app/src/tutorial/RouteLinks.tsx +++ b/app/src/tutorial/RouteLinks.tsx @@ -5,38 +5,87 @@ import links4 from '../../../resources/route_links_tutorial_images/links4.PNG'; import links6 from '../../../resources/route_links_tutorial_images/links6.PNG'; import linksCanvas from '../../../resources/route_links_tutorial_images/links-canvas.PNG'; +/** + * RouteLinks component displays information about Next.js Route Links. + * + * @param {object} props - Component props. + * @param {object} props.classes - CSS classes for styling. + * @param {Function} props.setPage - Function to set the current page. + * @returns {JSX.Element} RouteLinks component JSX. + */ const RouteLinks: React.FC<{ classes: any; setPage: Function; -}> = ({ classes, setPage }) => { +}> = ({ classes, setPage }): JSX.Element => { return (

Next.js Route Links

-
-

Route Links are only available for Next.js and Gatsby.js projects.

-

Users are able to drag-and-drop 'Link' components onto the canvas which allow navigation to different setPage('Pages')} >pages.

+
+

+ Route Links are only available for Next.js and Gatsby.js projects. +

+

+ Users are able to drag-and-drop 'Link' components onto the canvas which + allow navigation to different{' '} + setPage('Pages')}> + pages + + . +

-
-

Each page found in the ' setPage('Pages')} >Pages' section can be navigated to via a 'Link'. Clicking on the 'Route Link' dropdown will display all the created pages in your application.

+
+

+ Each page found in the ' + setPage('Pages')}> + Pages + + ' section can be navigated to via a 'Link'. Clicking on the 'Route Link' + dropdown will display all the created pages in your application. +

-
-

The code generator will automatically import Link from 'next/link' and create your Link component in the bottom panel.

+
+

+ The code generator will automatically{' '} + import Link from 'next/link' and create your Link component + in the bottom panel. +

-
-

Clicking on a Link component on the setPage('Canvas')} >canvas will navigate to the corresponding page.

+
+

+ Clicking on a Link component on the{' '} + setPage('Canvas')}> + canvas + {' '} + will navigate to the corresponding page. +

-
-

For more information on 'Link' for Next.js, please visit the official documentation section at nextjs.org. For more information on 'Link' for Gatsby.js, please visit the official documentation section at www.gatsbyjs.com.

-
+
+

+ For more information on 'Link' for Next.js, please{' '} + + visit the official documentation section at nextjs.org. + {' '} + For more information on 'Link' for Gatsby.js, please{' '} + + visit the official documentation section at www.gatsbyjs.com. + +

+
); }; diff --git a/app/src/tutorial/States.tsx b/app/src/tutorial/States.tsx index fd25d2b88..8360d89a9 100644 --- a/app/src/tutorial/States.tsx +++ b/app/src/tutorial/States.tsx @@ -8,10 +8,18 @@ import deleteState2 from '../../../resources/state_tutorial_images/delete.gif'; import display from '../../../resources/state_tutorial_images/display.gif'; import codePreview2 from '../../../resources/state_tutorial_images/CodePreview2.png'; +/** + * States component provides a tutorial on state management in ReacType. + * + * @param {object} props - Component props. + * @param {object} props.classes - CSS classes for styling. + * @param {Function} props.setPage - Function to set the current page. + * @returns {JSX.Element} States component JSX. + */ const States: React.FC<{ classes: any; setPage: Function; -}> = ({ classes, setPage }) => { +}> = ({ classes, setPage }): JSX.Element => { return (

State Management

diff --git a/app/src/tutorial/Styling.tsx b/app/src/tutorial/Styling.tsx index 55f59a520..c93c1d5a9 100644 --- a/app/src/tutorial/Styling.tsx +++ b/app/src/tutorial/Styling.tsx @@ -3,10 +3,18 @@ import lighting from '../../../resources/customizing_elements_images/Lighting.pn import resize from '../../../resources/customizing_elements_images/Resize.gif'; import codeChange from '../../../resources/customizing_elements_images/CodeChange.png'; +/** + * Styling component provides information about styling features in ReacType. + * + * @param {object} props - Component props. + * @param {object} props.classes - CSS classes for styling. + * @param {Function} props.setPage - Function to set the current page. + * @returns {JSX.Element} Styling component JSX. + */ const Styling: React.FC<{ classes: any; setPage: Function; -}> = ({ classes, setPage }) => { +}> = ({ classes, setPage }): JSX.Element => { return (

Styling Features

diff --git a/app/src/tutorial/Tutorial.tsx b/app/src/tutorial/Tutorial.tsx index 391219289..e1c104e13 100644 --- a/app/src/tutorial/Tutorial.tsx +++ b/app/src/tutorial/Tutorial.tsx @@ -86,7 +86,14 @@ const useStyles = makeStyles({ } }); -const Tutorial: React.FC = () => { +/** + * Tutorial component displaying a list of topics with associated icons. + * Each topic is rendered as a card, and clicking on a card navigates to a specific tutorial page. + * Includes a close button to close the tutorial window. + * @param {RouteComponentProps} props - RouteComponentProps object containing match, location, and history props + * @returns {JSX.Element} - JSX element representing the Tutorial component + */ +const Tutorial: React.FC = (): JSX.Element => { const classes = useStyles(); const topics = [ 'Pages', diff --git a/app/src/tutorial/TutorialPage.tsx b/app/src/tutorial/TutorialPage.tsx index be9365016..215dc5eea 100644 --- a/app/src/tutorial/TutorialPage.tsx +++ b/app/src/tutorial/TutorialPage.tsx @@ -89,7 +89,13 @@ const tutorialPageStyle = { } }; -const TutorialPage: React.FC = (props) => { +/** + * TutorialPage component displaying specific tutorial pages based on the selected topic. + * Provides a sidebar with links to navigate between tutorial pages. + * @param {RouteComponentProps} props - RouteComponentProps object containing match, location, and history props + * @returns {JSX.Element} - JSX element representing the TutorialPage component + */ +const TutorialPage: React.FC = (props): JSX.Element => { const classes = useStyles(); const [page, setPage] = useState(props.match.params.learn); diff --git a/app/src/utils/createApplication.util.ts b/app/src/utils/createApplication.util.ts index 6aa7a03b0..86e1cdf04 100644 --- a/app/src/utils/createApplication.util.ts +++ b/app/src/utils/createApplication.util.ts @@ -4,10 +4,21 @@ import createFiles from './createFiles.util'; import createTestSuiteClassic from './createTestSuiteClassic.util'; import store from '../redux/store'; -const camelToKebab = (camel: string) => { +/** + * Converts camelCase strings to kebab-case. + * @param {string} camel - The string in camelCase format. + * @returns {string} - The string converted to kebab-case. + */ +const camelToKebab = (camel: string): string => { return camel.replace(/([a-z0-9]|(?=[A-Z]))([A-Z])/g, '$1-$2').toLowerCase(); }; -const compToCSS = (component: Component) => { + +/** + * Converts a component object into CSS string format. + * @param {Component} component - The component object with name and style properties. + * @returns {string} - The CSS string for the component. + */ +const compToCSS = (component: Component): string => { const name = component.name; const styleObj = component.style; let cssClass = ` @@ -22,7 +33,16 @@ const compToCSS = (component: Component) => { `; return cssClass; }; -function createIndexHtml(path, appName) { + +/** + * Creates an index.html file for the React application, along with necessary directories if they do not exist. + * The function ensures that all required directories (src, server, components, contexts) exist, then writes + * an index.html file with a basic HTML structure to serve the React app. + * @param {string} path - The base directory path where the application is to be created. + * @param {string} appName - The name of the application, used to create a specific directory for the app. + * @returns {void} - This function has no return value but will log errors or success messages to the console. + */ +function createIndexHtml(path, appName): void { let dir = path; let dirSrc; let dirServer; @@ -64,7 +84,17 @@ function createIndexHtml(path, appName) { } }); } -export const createIndexTsx = (path, appName) => { + +/** + * Creates an `index.tsx` file at the specified path within the application directory. This file is the main + * entry point for the React application, setting up React DOM rendering for the main app component. + * It imports the main App component and attaches it to the root DOM element. This function also handles + * writing the file and logging the outcome of the operation. + * @param {string} path - The base directory path where the application is to be created. + * @param {string} appName - The name of the application, used to determine the directory structure for the file. + * @returns {void} - This function does not return a value but outputs through the console. + */ +export const createIndexTsx = (path, appName): void => { const filePath = `${path}/${appName}/src/index.tsx`; const data = `import React from 'react'; import ReactDOM from 'react-dom'; @@ -80,7 +110,17 @@ ReactDOM.render(, document.getElementById('root')); } }); }; -export const createDefaultCSS = (path, appName, components) => { + +/** + * Generates a default CSS file (`default.css`) for the React application at the specified path. + * This file includes basic styles for the root div and adds component-specific styles as defined in the + * components array. Each component's styles are generated using the `compToCSS` function. + * @param {string} path - The base directory path where the application is to be created. + * @param {string} appName - The name of the application, used to determine the directory structure for the CSS file. + * @param {Component[]} components - An array of component objects which include name and style properties for CSS generation. + * @returns {void} - This function does not return a value but will log the outcome of the file writing operation to the console. + */ +export const createDefaultCSS = (path, appName, components): void => { const filePath = `${path}/${appName}/src/default.css`; let data = `#root div { box-sizing: border-box; @@ -102,7 +142,19 @@ export const createDefaultCSS = (path, appName, components) => { } }); }; -export const createPackage = (path, appName, test) => { + +/** + * Creates a `package.json` file for the React application at the specified path. This file configures the project, + * setting up scripts for starting the server, building the application, and running development servers. + * It conditionally adds testing scripts and dependencies if specified. The function writes the configuration + * to the `package.json` file and logs the outcome of this operation. + * + * @param {string} path - The base directory path where the application is to be created. + * @param {string} appName - The name of the application, used to determine the directory structure for the `package.json` file. + * @param {boolean} test - Indicates whether to include testing scripts and dependencies in the `package.json`. + * @returns {void} - This function does not return a value but will log the outcome of the file writing operation to the console. + */ +export const createPackage = (path, appName, test): void => { const filePath = `${path}/${appName}/package.json`; let tsjest = `, "@types/enzyme": "^3.10.9", @@ -180,7 +232,17 @@ export const createPackage = (path, appName, test) => { } }); }; -export const createWebpack = (path, appName) => { + +/** + * Creates a `webpack.config.js` file for the React application at the specified path. This configuration file sets + * up webpack for building the application, including rules for handling TypeScript, JavaScript, CSS, and SCSS files. + * It dynamically sets the webpack mode based on the environment variable and ensures source maps are generated for + * debugging. The function also handles the writing of this configuration file and logs the outcome. + * @param {string} path - The base directory path where the application is to be created. + * @param {string} appName - The name of the application, used to determine the directory structure for the webpack configuration file. + * @returns {void} - This function does not return a value but will log the outcome of the file writing operation to the console. + */ +export const createWebpack = (path, appName): void => { const filePath = `${path}/${appName}/webpack.config.js`; const data = `var status = import.meta.env.NODE_ENV; //taken from script so we don't have to flip mode when using development/production var path = require('path'); @@ -234,7 +296,16 @@ module.exports = { } }); }; -export const createBabel = (path, appName) => { + +/** + * Creates a `.babelrc` file for the React application at the specified path. This configuration file sets up Babel + * presets for handling JavaScript according to ECMAScript standards, React JSX syntax, and TypeScript. It writes this + * configuration to the `.babelrc` file and logs whether the operation was successful or if an error occurred. + * @param {string} path - The base directory path where the application is to be created. + * @param {string} appName - The name of the application, used to determine the directory structure for the Babel configuration file. + * @returns {void} - This function does not return a value but will log the outcome of the file writing operation to the console. + */ +export const createBabel = (path, appName): void => { const filePath = `${path}/${appName}/.babelrc`; const data = `{ "presets": ["@babel/env", "@babel/react", "@babel/typescript"] @@ -248,7 +319,18 @@ export const createBabel = (path, appName) => { } }); }; -export const createTsConfig = (path, appName) => { + +/** + * Creates a `tsconfig.json` file for the React application at the specified path. This configuration file sets up + * TypeScript compiler options such as the output directory, module system, ECMAScript target version, JSX pragma, + * library inclusions, and more to ensure proper compilation of TypeScript files in the project. It also specifies + * which files to include in the compilation process. The function writes this configuration to the `tsconfig.json` + * file and logs whether the operation was successful or if an error occurred. + * @param {string} path - The base directory path where the application is to be created. + * @param {string} appName - The name of the application, used to determine the directory structure for the TypeScript configuration file. + * @returns {void} - This function does not return a value but will log the outcome of the file writing operation to the console. + */ +export const createTsConfig = (path, appName): void => { const filePath = `${path}/${appName}/tsconfig.json`; const data = `{ "compilerOptions": { @@ -273,7 +355,18 @@ export const createTsConfig = (path, appName) => { } }); }; -export const createTsLint = (path, appName) => { + +/** + * Creates a `tslint.json` file for the React application at the specified path. This configuration file sets up + * TypeScript linting rules and extends from recommended, React-specific, and Prettier configurations. The file + * specifies linting rules, such as quotation marks, JSX boolean values, and others, to ensure code quality and + * consistency. The function also configures the linter to auto-fix certain issues on save. It writes this + * configuration to the `tslint.json` file and logs whether the operation was successful or if an error occurred. + * @param {string} path - The base directory path where the application is to be created. + * @param {string} appName - The name of the application, used to determine the directory structure for the TypeScript linting configuration file. + * @returns {void} - This function does not return a value but will log the outcome of the file writing operation to the console. + */ +export const createTsLint = (path, appName): void => { const filePath = `${path}/${appName}/tslint.json`; const data = `{ "extends": ["tslint:recommended", "tslint-react", "tslint-config-prettier"], @@ -302,7 +395,20 @@ export const createTsLint = (path, appName) => { } }); }; -export const createServer = (path, appName) => { + +/** + * Creates a server configuration file (`server.js`) for the React application at the specified path. + * This server file uses Express.js to serve the build directory and the main `index.html` file. + * It includes a test endpoint and configuration to listen on port 8080. The function writes + * this server setup to the `server.js` file within a designated server directory and logs + * the outcome of this operation, indicating success or reporting any errors. + * @param {string} path - The base directory path where the application is to be created. + * @param {string} appName - The name of the application, used to determine the directory structure + * for the server configuration file. + * @returns {void} - This function does not return a value but will log the outcome of the file writing + * operation to the console. + */ +export const createServer = (path, appName): void => { const filePath = `${path}/${appName}/server/server.js`; const data = `const express = require('express'); const path = require('path'); @@ -329,8 +435,17 @@ app.listen(8080, () => { }); }; -//Generate files for all existing contexts in the current application -export const createContext = (path, appName) => { +/** + * Generates context files for all existing contexts in the current application. Each context file includes + * a React context provider that initializes with a predefined state derived from the application's redux store. + * This setup allows different parts of the React application to access and utilize shared state through context. + * The function writes each context file into a specified directory structure and logs the outcome of these + * operations, reporting success or any errors encountered. + * @param {string} path - The base directory path where the application contexts are to be created. + * @param {string} appName - The name of the application, used to determine the directory structure for the context files. + * @returns {void} - This function does not return a value but will log the outcome of the file writing operation to the console. + */ +export const createContext = (path, appName): void => { // const store = useStore(); const { allContext } = store.getState().contextSlice; @@ -365,6 +480,21 @@ export default ${context.name}Provider }); } }; + +/** + * Orchestrates the creation of all necessary files and configurations for a React application. + * This utility function systematically sets up the core structure of the application by creating + * essential files such as index.html, index.tsx, default CSS, package.json, webpack configuration, + * babel configuration, TypeScript configuration, tslint configuration, and server setup files. + * It also generates context providers and optionally sets up a classic test suite if specified. + * Each step awaits the completion of the previous file creation to ensure proper setup order. + * @param {Object} params - The parameters needed for setting up the application. + * @param {string} params.path - The base directory path where the application is to be created. + * @param {string} params.appName - The name of the application, used for directory structure and naming files. + * @param {Component[]} params.components - An array of component objects that might be needed for CSS generation or other file setups. + * @param {boolean} params.testchecked - Flag to determine whether to include test suite setup in the application. + * @returns {Promise} - Asynchronous function that completes once all files are created. + */ async function createApplicationUtil({ path, appName, @@ -375,7 +505,7 @@ async function createApplicationUtil({ appName: string; components: Component[]; testchecked: boolean; -}) { +}): Promise { await createIndexHtml(path, appName); await createIndexTsx(path, appName); await createDefaultCSS(path, appName, components); diff --git a/app/src/utils/createFiles.util.ts b/app/src/utils/createFiles.util.ts index 0c40757bf..1b1033c2f 100644 --- a/app/src/utils/createFiles.util.ts +++ b/app/src/utils/createFiles.util.ts @@ -1,10 +1,23 @@ -// Creates all component files (but not the full application files) and places them in a "components" directory +/** + * Creates TypeScript component files for each component provided in the components array. These files + * are placed in a "components" directory within the specified path. The function can differentiate + * between exporting files for a full application or just locally within a specified directory, based + * on the `exportAppBool` flag. It handles file creation asynchronously and resolves once all files + * are successfully written, or rejects upon any error. + * + * @param {any[]} components - An array of objects representing components, each with a `name` and `code` property. + * @param {string} path - The base directory path where component files are to be created. + * @param {string} appName - The name of the application, used to structure the directory if exporting as part of an application setup. + * @param {boolean} exportAppBool - A boolean flag that indicates whether the components are being exported as part of a full application setup. + * @returns {Promise>} - A promise that resolves with an array of paths where files have been created, or rejects with an error message. + */ + const createFiles = ( components: any, path: string, appName: string, exportAppBool: boolean -) => { +): Promise> => { let dir = path; if (exportAppBool === false) { if (!dir.match(/components|\*$/)) { diff --git a/app/src/utils/createGatsbyApp.util.ts b/app/src/utils/createGatsbyApp.util.ts index bad733d5e..61bba2f55 100644 --- a/app/src/utils/createGatsbyApp.util.ts +++ b/app/src/utils/createGatsbyApp.util.ts @@ -2,27 +2,44 @@ import createGatsbyFiles from './createGatsbyFiles.util'; import createTestSuite from './createTestSuite.util'; import { Component } from '../interfaces/Interfaces'; -const camelToKebab= (camel:string) => { +/** + * Converts camelCase string to kebab-case. + * @param {string} camel - The string in camelCase format. + * @returns {string} - The string in kebab-case format. + */ +const camelToKebab = (camel: string): string => { return camel.replace(/([a-z0-9]|(?=[A-Z]))([A-Z])/g, '$1-$2').toLowerCase(); }; -const compToCSS = (component: Component) => { +/** + * Generates CSS class definitions based on the component's style object. + * @param {Component} component - The component object with name and style properties. + * @returns {string} - CSS class definitions for the component. + */ +const compToCSS = (component: Component): string => { const name = component.name; const styleObj = component.style; let cssClass = ` .${name} { `; - Object.keys(styleObj).forEach(property => { + Object.keys(styleObj).forEach((property) => { let cssStyle = `${camelToKebab(property)}: ${styleObj[property]}; `; cssClass += cssStyle; - }) + }); cssClass += `} `; return cssClass; -} -//createPackage -export const createPackage = (path, appName, test) => { +}; + +/** + * Creates a package.json file with or without test configurations based on input. + * @param {string} path - The path where the file should be created. + * @param {string} appName - The application name. + * @param {boolean} test - Indicates whether to include test configurations. + * @returns {void} - This function writes to a file and does not return any value. + */ +export const createPackage = (path, appName, test): void => { const filePath = `${path}/${appName}/package.json`; let tsjest = `, "@types/enzyme": "^3.10.9", @@ -46,8 +63,11 @@ export const createPackage = (path, appName, test) => { "dev": "gatsby develop", "build": "gatsby build", "start": "npm run dev"${ - test ? `, - "test": "jest"`: '' } + test + ? `, + "test": "jest"` + : '' + } }, "dependencies": { "gatsby": "^2.26.1", @@ -57,12 +77,11 @@ export const createPackage = (path, appName, test) => { "devDependencies": { "@types/node": "^14.0.20", "@types/react": "^16.9.41", - "typescript": "^3.9.6"${ - test ? tsjest : '' } + "typescript": "^3.9.6"${test ? tsjest : ''} } } `; - window.api.writeFile(filePath, data, err => { + window.api.writeFile(filePath, data, (err) => { if (err) { console.log('package.json error:', err.message); } else { @@ -70,11 +89,17 @@ export const createPackage = (path, appName, test) => { } }); }; -//createTSConfig (empty) -export const createTsConfig = (path:string, appName:string) => { - const filePath:string = `${path}/${appName}/tsconfig.json`; + +/** + * Creates a default tsconfig.json file for a Gatsby project. + * @param {string} path - The base directory path. + * @param {string} appName - The name of the app. + * @returns {void} - This function writes to a file and does not return any value. + */ +export const createTsConfig = (path: string, appName: string): void => { + const filePath: string = `${path}/${appName}/tsconfig.json`; //running 'gatsby dev' will autopopulate this with default values - const data:string = `{ + const data: string = `{ "compilerOptions": { "outDir": "./dist/", "sourceMap": true, @@ -89,7 +114,7 @@ export const createTsConfig = (path:string, appName:string) => { "include": ["./src/**/*"] } `; - window.api.writeFile(filePath, data, err => { + window.api.writeFile(filePath, data, (err) => { if (err) { console.log('TSConfig error:', err.message); } else { @@ -98,8 +123,18 @@ export const createTsConfig = (path:string, appName:string) => { }); }; -//createDefaultCSS -export const createDefaultCSS = (path:string, appName:string, components) => { +/** + * Creates a global CSS file for the application that includes default styles and styles for provided components. + * @param {string} path - The path where the CSS file should be created. + * @param {string} appName - The name of the application for which the CSS is being created. + * @param {Component[]} components - An array of component objects which will have their styles converted to CSS and included in the file. + * @returns {void} - This function writes to a file and does not return any value. + */ +export const createDefaultCSS = ( + path: string, + appName: string, + components +): void => { const filePath = `${path}/${appName}/global.css`; let data = ` #__gatsby div { @@ -112,26 +147,33 @@ export const createDefaultCSS = (path:string, appName:string, components) => { } `; console.log(components); - components.forEach(comp => { + components.forEach((comp) => { data += compToCSS(comp); - }) - window.api.writeFile(filePath, data, err => { + }); + window.api.writeFile(filePath, data, (err) => { if (err) { console.log('global.css error:', err.message); } else { console.log('global.css written successfully'); } }); -} +}; -export const initFolders = (path:string, appName: string) => { +/** + * Initializes the necessary directory structure for a new application within a specified path. + * This function creates the main application directory, along with subdirectories for 'src', 'pages', and 'components'. + * @param {string} path - The base path where the application directories will be created. + * @param {string} appName - The name of the application, which will be used as the root directory name. + * @returns {void} - This function writes to a file and does not return any value. + */ +export const initFolders = (path: string, appName: string): void => { let dir = path; let dirPages; let dirComponents; dir = `${dir}/${appName}`; if (!window.api.existsSync(dir)) { window.api.mkdirSync(dir); - window.api.mkdirSync(`${dir}/src`) + window.api.mkdirSync(`${dir}/src`); dirPages = `${dir}/src/pages`; window.api.mkdirSync(dirPages); dirComponents = `${dir}/src/components`; @@ -139,10 +181,16 @@ export const initFolders = (path:string, appName: string) => { } }; -//createBaseTsx -export const createBaseTsx = (path: string, appName: string) => { - const filePath:string = `${path}/${appName}/src/pages/_app.tsx`; - const data:string = ` +/** + * Creates a base TypeScript file (_app.tsx) for a Gatsby application, including a basic React component setup. + * This function writes the file with an import of React, a global CSS import, and a default export of a base component. + * @param {string} path - The path where the application resides. + * @param {string} appName - The name of the application; used to build the path to the TypeScript file. + * @returns {void} - This function writes to a file and does not return any value. + */ +export const createBaseTsx = (path: string, appName: string): void => { + const filePath: string = `${path}/${appName}/src/pages/_app.tsx`; + const data: string = ` import React from 'react'; import '../global.css'; const Base = ({ Component }):JSX.Element => { @@ -154,7 +202,7 @@ export const createBaseTsx = (path: string, appName: string) => { } export default Base; `; - window.api.writeFile(filePath, data, err => { + window.api.writeFile(filePath, data, (err) => { if (err) { console.log('_app.tsx error:', err.message); } else { @@ -163,26 +211,39 @@ export const createBaseTsx = (path: string, appName: string) => { }); }; +/** + * Creates a base TypeScript file (_app.tsx) for a Gatsby application which setups up the main React component structure. + * This file will include necessary imports and define a basic React component structure. + * @param {string} path - The base directory path where the file will be created. + * @param {string} appName - The name of the application, used to specify the directory. + * @returns {Promise} - This function writes to a file and does not return any value. + */ async function createGatsbyAppUtil({ path, appName, components, rootComponents, - testchecked, + testchecked }: { path: string; appName: string; components: Component[]; rootComponents: number[]; testchecked: boolean; -}) { +}): Promise { await initFolders(path, appName); await createBaseTsx(path, appName); await createDefaultCSS(path, appName, components); await createPackage(path, appName, testchecked); await createTsConfig(path, appName); if (testchecked) { - await createTestSuite({path, appName, components, rootComponents, testchecked}); + await createTestSuite({ + path, + appName, + components, + rootComponents, + testchecked + }); } await createGatsbyFiles(components, path, appName, rootComponents); } diff --git a/app/src/utils/createGatsbyFiles.util.ts b/app/src/utils/createGatsbyFiles.util.ts index 19e52ca49..35ba1b5ee 100644 --- a/app/src/utils/createGatsbyFiles.util.ts +++ b/app/src/utils/createGatsbyFiles.util.ts @@ -1,11 +1,28 @@ -// Create all component files for a Gatsby.js application -// all components are stored in a src folder -// "Root" level components are stored in a pages directory -// all other components will be in a components directory import { Component } from '../interfaces/Interfaces'; -const isRoot = (component: Component, rootArray: number[]) => { + +/** + * Determines whether a given component is a root component. + * Root components are identified by their IDs being included in the provided rootArray. + * @param {Component} component The component to check. + * @param {number[]} rootArray An array of IDs that identify root components. + * @returns {boolean} Returns true if the component is a root component, false otherwise. + */ +const isRoot = (component: Component, rootArray: number[]): boolean => { return rootArray.includes(component.id) ? true : false; }; + +/** + * Asynchronously creates TypeScript component files for a Gatsby.js application, organizing them into 'pages' and 'components' directories. + * This function categorizes components as root or non-root based on their IDs. Root components are stored in the 'pages' directory, + * with the first root component specifically stored as 'index.tsx'. All other root components are named according to their component name. + * Non-root components are stored in the 'components' directory. + * @param {Component[]} components An array of component objects that contain the necessary data to generate files. + * @param {string} path The base directory path for the Gatsby application. + * @param {string} appName The name of the Gatsby application. + * @param {number[]} rootComponents An array of component IDs that should be treated as root components. + * @returns {Promise} A promise that resolves when all component files have been successfully written to disk. + * The promise returns an array of the paths where the files were created. + */ const createGatsbyFiles = ( components: Component[], path: string, diff --git a/app/src/utils/createNextApp.util.ts b/app/src/utils/createNextApp.util.ts index e66a8b0e7..9f941eccb 100644 --- a/app/src/utils/createNextApp.util.ts +++ b/app/src/utils/createNextApp.util.ts @@ -2,26 +2,47 @@ import createNextFiles from './createNextFiles.util'; import { Component } from '../interfaces/Interfaces'; import createTestSuiteNext from './createTestSuiteNext.util'; -const camelToKebab= (camel:string) => { + +/** + * Converts camelCase string to kebab-case. + * @param {string} camel - The string in camelCase format. + * @returns {string} - The string in kebab-case format. + */ +const camelToKebab = (camel: string): string => { return camel.replace(/([a-z0-9]|(?=[A-Z]))([A-Z])/g, '$1-$2').toLowerCase(); }; -const compToCSS = (component: Component) => { + +/** + * Generates CSS class definitions based on the component's style object. + * @param {Component} component - The component object with name and style properties. + * @returns {string} - CSS class definitions for the component. + */ +const compToCSS = (component: Component): string => { const name = component.name; const styleObj = component.style; let cssClass = ` .${name} { `; - Object.keys(styleObj).forEach(property => { + Object.keys(styleObj).forEach((property) => { let cssStyle = `${camelToKebab(property)}: ${styleObj[property]}; `; cssClass += cssStyle; - }) + }); cssClass += `} `; return cssClass; -} -//createPackage -export const createPackage = (path, appName, test) => { +}; + +/** + * Creates a package.json file for a Next.js application at the specified path. The package.json file includes + * scripts for development, build, and start processes, and it conditionally adds testing scripts if specified. + * Dependencies for both the application and development (including optional testing dependencies) are also defined. + * @param {string} path - The base directory path where the application files will be written. + * @param {string} appName - The name of the application, used to define the path where the package.json will be located. + * @param {boolean} test - Flag to determine whether test-related packages and scripts should be included. + * @returns {void} - Does not return a value; it performs file writing operations directly. + */ +export const createPackage = (path, appName, test): void => { const filePath = `${path}/${appName}/package.json`; let testpackages = `, "@types/enzyme": "^3.10.9", @@ -42,8 +63,11 @@ export const createPackage = (path, appName, test) => { "dev": "next dev", "build": "next build", "start": "next start"${ - test ? `, - "test": "jest"`: '' } + test + ? `, + "test": "jest"` + : '' + } }, "dependencies": { "next": "9.3.5", @@ -54,13 +78,11 @@ export const createPackage = (path, appName, test) => { "@types/node": "^14.0.20", "@types/react": "^16.9.41", "@types/react-dom": "^17.0.9", - "typescript": "^3.9.6"${ - test ? testpackages : '' - } + "typescript": "^3.9.6"${test ? testpackages : ''} } } `; - window.api.writeFile(filePath, data, err => { + window.api.writeFile(filePath, data, (err) => { if (err) { console.log('createNextApp.util package.json error:', err.message); } else { @@ -68,11 +90,20 @@ export const createPackage = (path, appName, test) => { } }); }; -//createTSConfig (empty) -export const createTsConfig = (path, appName) => { + +/** + * Creates a tsconfig.json file at the specified path for a given application. The tsconfig.json is pre-populated + * with a set of TypeScript compiler options tailored for a Next.js application, including settings to support + * JSX, module resolution, and other strict checks. This function writes the config file and logs success or error messages. + * Note that running 'next dev' will autopopulate this file with default values later if needed. + * @param {string} path - The directory path where the application files will be written. + * @param {string} appName - The name of the application, used to determine the specific directory for the tsconfig.json file. + * @returns {void} - Does not return a value; it performs file writing operations directly. + */ +export const createTsConfig = (path, appName): void => { const filePath = `${path}/${appName}/tsconfig.json`; //running 'next dev' will autopopulate this with default values - const data:string = `{ + const data: string = `{ "compileOnSave": false, "compilerOptions": { "target": "esnext", @@ -109,7 +140,7 @@ export const createTsConfig = (path, appName) => { ] } `; - window.api.writeFile(filePath, data, err => { + window.api.writeFile(filePath, data, (err) => { if (err) { console.log('TSConfig error:', err.message); } else { @@ -118,8 +149,16 @@ export const createTsConfig = (path, appName) => { }); }; -//createDefaultCSS -export const createDefaultCSS = (path, appName, components) => { +/** + * Creates a global CSS file for the specified application. This file includes default styles for div elements + * within the #__next container and appends custo- m styles for each component provided. The CSS rules are aimed + * at ensuring consistent styling across the application. + * @param {string} path - The base directory path where the CSS file will be created. + * @param {string} appName - The name of the application, used to construct the path to the global.css file. + * @param {Component[]} components - An array of component objects from which to generate additional CSS rules. + * @returns {void} - Does not return a value; it performs file writing operations directly. + */ +export const createDefaultCSS = (path, appName, components): void => { const filePath = `${path}/${appName}/global.css`; let data = ` #__next div { @@ -131,35 +170,51 @@ export const createDefaultCSS = (path, appName, components) => { font-family: Helvetica, Arial; } `; - components.forEach(comp => { + components.forEach((comp) => { data += compToCSS(comp); - }) - window.api.writeFile(filePath, data, err => { + }); + window.api.writeFile(filePath, data, (err) => { if (err) { console.log('global.css error:', err.message); } else { console.log('global.css written successfully'); } }); -} +}; -export const initFolders = (path:string, appName: string) => { +/** + * Initializes the directory structure for a new application within the specified path. + * This function creates the main application directory along with subdirectories for 'pages' and 'components'. + * These directories are essential for organizing application files according to typical project conventions. + * @param {string} path - The base path where the application directories will be created. + * @param {string} appName - The name of the application, used to create a specific directory under the given path. + * @returns {void} - Does not return a value; it performs directory creation operations directly. + */ +export const initFolders = (path: string, appName: string): void => { let dir = path; let dirPages; let dirComponents; dir = `${dir}/${appName}`; - if(!window.api.existsSync(dir)){ + if (!window.api.existsSync(dir)) { window.api.mkdirSync(dir); dirPages = `${dir}/pages`; window.api.mkdirSync(dirPages); dirComponents = `${dir}/components`; window.api.mkdirSync(dirComponents); } -} -//createBaseTsx -export const createBaseTsx = (path, appName) => { - const filePath:string = `${path}/${appName}/pages/_app.tsx`; - const data:string = ` +}; + +/** + * Creates a base TypeScript file (_app.tsx) for the specified application. This file sets up a fundamental React component structure, + * importing global CSS and defining a root component that renders any child components passed to it. + * The function writes this configuration to a file within the 'pages' directory of the application. + * @param {string} path - The base directory path where the application files will be created. + * @param {string} appName - The name of the application, used to determine the specific directory for the _app.tsx file. + * @returns {void} - Does not return a value; it performs file writing operations directly. + */ +export const createBaseTsx = (path, appName): void => { + const filePath: string = `${path}/${appName}/pages/_app.tsx`; + const data: string = ` import React from 'react'; import '../global.css'; const Base = ({ Component }):JSX.Element => { @@ -171,7 +226,7 @@ export const createBaseTsx = (path, appName) => { } export default Base; `; - window.api.writeFile(filePath, data, err => { + window.api.writeFile(filePath, data, (err) => { if (err) { console.log('_app.tsx error:', err.message); } else { @@ -180,26 +235,45 @@ export const createBaseTsx = (path, appName) => { }); }; +/** + * Asynchronously creates a full Next.js application setup including directories, base React component files, + * CSS configurations, package management files, and TypeScript configurations. This function may also set up + * a testing suite if specified. It handles all necessary asynchronous operations to ensure the full application + * structure is correctly built. + * @param {Object} options The configuration options for creating the Next.js app. + * @param {string} options.path The base path where the application will be created. + * @param {string} options.appName The name of the application to be created. + * @param {Component[]} options.components Array of components to be included in the application. + * @param {number[]} options.rootComponents Array of indices pointing to root components, which affects file placement within the 'pages' directory. + * @param {boolean} options.testchecked Indicates whether to include test setup in the generated application. + * @returns {Promise} A promise that resolves when all operations are completed, indicating the application has been fully set up. + */ async function createNextAppUtil({ path, appName, components, rootComponents, - testchecked, + testchecked }: { path: string; appName: string; components: Component[]; rootComponents: number[]; testchecked: boolean; -}) { +}): Promise { await initFolders(path, appName); await createBaseTsx(path, appName); await createDefaultCSS(path, appName, components); await createPackage(path, appName, testchecked); await createTsConfig(path, appName); if (testchecked) { - await createTestSuiteNext({path, appName, components, rootComponents, testchecked}); + await createTestSuiteNext({ + path, + appName, + components, + rootComponents, + testchecked + }); } await createNextFiles(components, path, appName, rootComponents); } diff --git a/app/src/utils/createNextFiles.util.ts b/app/src/utils/createNextFiles.util.ts index 31ff60a04..37c88622e 100644 --- a/app/src/utils/createNextFiles.util.ts +++ b/app/src/utils/createNextFiles.util.ts @@ -3,16 +3,35 @@ // all other components will be in a components directory import { Component } from '../interfaces/Interfaces'; -const isRoot = (component: Component, rootArray: number[]) => { +/** + * Determines whether a given component is a root component. + * Root components are identified by their IDs being included in the provided rootArray. + * @param {Component} component The component to check. + * @param {number[]} rootArray An array of IDs that identify root components. + * @returns {boolean} Returns true if the component is a root component, false otherwise. + */ +const isRoot = (component: Component, rootArray: number[]): boolean => { return rootArray.includes(component.id) ? true : false; }; +/** + * Asynchronously creates TypeScript component files for a Next.js application, organizing them into 'pages' and 'components' directories. + * Components identified as root components (based on their IDs in the 'rootComponents' array) are placed in the 'pages' directory, + * with the first root component specifically stored as 'index.tsx'. All other root components are named according to their component name. + * Non-root components are stored in the 'components' directory. + * @param {Component[]} components An array of component objects that include necessary data for generating files. + * @param {string} path The base directory path for the Next.js application. + * @param {string} appName The name of the Next.js application. + * @param {number[]} rootComponents An array of component IDs designated as root components. + * @returns {Promise} A promise that resolves when all component files have been successfully written to disk. + * The promise returns an array of the paths where the files were created, which can be used for further processing or verification. + */ const createNextFiles = ( components: Component[], path: string, appName: string, rootComponents: number[] -) => { +): Promise => { let dir = path; dir = `${dir}/${appName}`; diff --git a/app/src/utils/exportProject.util.ts b/app/src/utils/exportProject.util.ts index 76a8dc76c..7f67e9f58 100644 --- a/app/src/utils/exportProject.util.ts +++ b/app/src/utils/exportProject.util.ts @@ -3,7 +3,21 @@ import createNextApp from './createNextApp.util'; import createFiles from './createFiles.util'; import createGatsbyApp from './createGatsbyApp.util'; -// When a user clicks the "Export project" function from the app, this function is invoked +/** + * Handles the exporting of projects in various configurations based on user selections. + * This function supports the creation of Classic React applications, Next.js applications, + * Gatsby.js applications, and a basic export of component files without full application scaffolding. + * + * @param {string} path - The directory path where the project should be exported. + * @param {string} appName - The name of the application to be created. + * @param {number} genOption - Indicates the type of generation: + * 0 for only component files, 1 for complete application setup. + * @param {string} projectType - Specifies the type of project: 'Classic React', 'Next.js', or 'Gatsby.js'. + * @param {any[]} components - An array of components to be included in the project. + * @param {number[]} rootComponents - An array of indices identifying which components are root components. + * @param {boolean} [tests] - Optional. Whether to include test setups in the generated application. + * @returns {void} - This function does not return a value but will log the outcome of the file writing operation to the console. + */ const exportProject = ( path: string, appName: string, @@ -12,25 +26,36 @@ const exportProject = ( components: any, rootComponents: number[], tests?: boolean -) => { +): void => { // Create fully functional classic react application if (genOption === 1 && projectType === 'Classic React') { - createApplicationUtil({ path, appName, components, testchecked: tests }).catch(err => - console.log(err) - ); + createApplicationUtil({ + path, + appName, + components, + testchecked: tests + }).catch((err) => console.log(err)); } // export all component files, but don't create all application files else if (genOption === 0) { createFiles(components, path, appName, false); } // Create fully functional Next.js and Gatsby.js application files else if (genOption === 1 && projectType === 'Next.js') { - createNextApp({ path, appName, components, rootComponents, testchecked: tests }).catch(err => - console.log(err) - ); + createNextApp({ + path, + appName, + components, + rootComponents, + testchecked: tests + }).catch((err) => console.log(err)); } else if (genOption === 1 && projectType === 'Gatsby.js') { - createGatsbyApp({ path, appName, components, rootComponents, testchecked: tests }).catch(err => - console.log(err)); + createGatsbyApp({ + path, + appName, + components, + rootComponents, + testchecked: tests + }).catch((err) => console.log(err)); } - }; export default exportProject; diff --git a/server/controllers/cookieController.ts b/server/controllers/cookieController.ts index 8d1e80a05..ff6797a09 100644 --- a/server/controllers/cookieController.ts +++ b/server/controllers/cookieController.ts @@ -1,35 +1,46 @@ import { CookieController } from '../interfaces'; - const cookieController: CookieController = { - // setSSIDCookie - store the user id from database in cookie - setSSIDCookie: (req, res, next) => { + /** + * Middleware function to set the 'ssid' and 'username' cookies. + * + * @callback SetSSIDCookieMiddleware + * @param {object} req - The request object. + * @param {object} res - The response object. + * @param {Function} next - The next middleware function in the stack. + * @returns {void} + */ + setSSIDCookie: (req, res, next): void => { // set cookie with key 'ssid' and value to user's id res.cookie('ssid', res.locals.id, { httpOnly: true, sameSite: 'none', - secure: true, + secure: true //maxAge: 60 * 60 * 1000 * 24 //uncomment to set expiration of cookies, but make sure there is something in place to expire local storage info too - }); res.cookie('username', res.locals.username, { httpOnly: true, sameSite: 'none', - secure: true, + secure: true }); return next(); - }, - - deleteCookies: (req, res, next) => { - + }, + /** + * Middleware function to delete cookies. + * + * @callback DeleteCookiesMiddleware + * @param {object} req - The request object. + * @param {object} res - The response object. + * @param {Function} next - The next middleware function in the stack. + * @returns {void} + */ + deleteCookies: (req, res, next): void => { res.clearCookie('ssid'); res.clearCookie('username'); res.clearCookie('connect.sid'); return next(); } - - }; export default cookieController; diff --git a/server/controllers/marketplaceController.ts b/server/controllers/marketplaceController.ts index 91e49be84..8b7d433d1 100644 --- a/server/controllers/marketplaceController.ts +++ b/server/controllers/marketplaceController.ts @@ -8,11 +8,17 @@ type Projects = { project: {} }[]; const marketplaceController: MarketplaceController = { /** - * Middleware function that finds and returns all published projects from the database - * @return sends the entire project document to frontend + * Middleware function to find and return all published projects from the database. + * + * @callback GetPublishedProjectsMiddleware + * @param {object} req - The request object. + * @param {object} res - The response object. + * @param {Function} next - The next middleware function in the stack. + * @returns {void} */ - getPublishedProjects: (req, res, next) => { - Projects.find({ published: true }, (err, projects) => {//removed the typing for now for projects: since its the mongodb doc + getPublishedProjects: (req, res, next): void => { + Projects.find({ published: true }, (err, projects) => { + //removed the typing for now for projects: since its the mongodb doc if (err) { return next({ log: `Error in marketplaceController.getPublishedProjects: ${err}`, @@ -32,104 +38,141 @@ const marketplaceController: MarketplaceController = { }, /** - * - * Middleware function that publishes (and saves) a project to the database - * @return sends the updated entire project document to the frontend + * Middleware function to publish a project to the database. + * + * @callback PublishProjectMiddleware + * @param {object} req - The request object. + * @param {object} res - The response object. + * @param {Function} next - The next middleware function in the stack. + * @returns {Promise} */ - publishProject: async (req, res, next) => { + publishProject: async (req, res, next): Promise => { const { _id, project, comments, name } = req.body; const username = req.cookies.username; const userId = req.cookies.ssid; const createdAt = Date.now(); - try{ + try { if (mongoose.isValidObjectId(_id)) { - const noPub = {...project} + const noPub = { ...project }; delete noPub.published; delete noPub._id; - const publishedProject = await Projects.findOneAndUpdate - ( // looks in projects collection for project by Mongo id - { _id }, - // update or insert the project - { project: noPub, createdAt, published: true, comments, name, userId, username }, - // Options: - // upsert: true - if none found, inserts new project, otherwise updates it - // new: true - returns updated document not the original one - { upsert: true, new: true } - ); + const publishedProject = await Projects.findOneAndUpdate( + // looks in projects collection for project by Mongo id + { _id }, + // update or insert the project + { + project: noPub, + createdAt, + published: true, + comments, + name, + userId, + username + }, + // Options: + // upsert: true - if none found, inserts new project, otherwise updates it + // new: true - returns updated document not the original one + { upsert: true, new: true } + ); res.locals.publishedProject = publishedProject; return next(); - }else{ - - const noId = {...project}; - delete noId._id; //removing the empty string _id from project + } else { + const noId = { ...project }; + delete noId._id; //removing the empty string _id from project delete noId.published; - const publishedProject = await Projects.create( { project: noId, createdAt, published: true, comments, name, userId, username }) - res.locals.publishedProject = publishedProject.toObject({ minimize: false }); + const publishedProject = await Projects.create({ + project: noId, + createdAt, + published: true, + comments, + name, + userId, + username + }); + res.locals.publishedProject = publishedProject.toObject({ + minimize: false + }); return next(); } - } - catch { - + } catch { // we should not expect a user to be able to access another user's id, but included error handling for unexpected errors return next({ - log: 'Error in marketplaceController.publishProject', + log: 'Error in marketplaceController.publishProject', message: { err: 'Error in marketplaceController.publishProject, check server logs for details' } - }) + }); } }, /** - * - * Middleware function that marks a project as unpublished in the database - * @return sends the updated project to the frontend + * Middleware function to mark a project as unpublished in the database. + * + * @callback UnpublishProjectMiddleware + * @param {object} req - The request object. + * @param {object} res - The response object. + * @param {Function} next - The next middleware function in the stack. + * @returns {void} */ - unpublishProject: (req, res, next) => { + + unpublishProject: (req, res, next): void => { const { _id } = req.body; const userId = req.cookies.ssid; //check if req.cookies.ssid matches userId - + try { - Projects.findOneAndUpdate({ _id, userId }, {published: false}, { new: true }, (err, result) => { - if (err || result === null) { - return next({ - log: `Error in marketplaceController.unpublishProject: ${err || null}`, - message: { - err: 'Error in marketplaceController.unpublishProject, check server logs for details' - } - }); + Projects.findOneAndUpdate( + { _id, userId }, + { published: false }, + { new: true }, + (err, result) => { + if (err || result === null) { + return next({ + log: `Error in marketplaceController.unpublishProject: ${ + err || null + }`, + message: { + err: 'Error in marketplaceController.unpublishProject, check server logs for details' + } + }); + } + res.locals.unpublishedProject = result.toObject({ minimize: false }); + return next(); } - res.locals.unpublishedProject = result.toObject({ minimize: false }); - return next(); - }); - } - catch { + ); + } catch { // we should not expect a user to be able to access another user's id, but included error handling for unexpected errors return next({ log: `Error in marketplaceController.unpublishProject`, message: { err: 'Error in marketplaceController.unpublishProject, userId of project does not match cookies.ssid' } - }) - + }); } - }, + }, /** - * Middleware function that clones and saves project to user's library - * + * Middleware function to clone and save a project to a user's library. + * + * @callback CloneProjectMiddleware + * @param {object} req - The request object. + * @param {object} res - The response object. + * @param {Function} next - The next middleware function in the stack. + * @returns {Promise} */ - cloneProject: async (req, res, next) => { + cloneProject: async (req, res, next): Promise => { // pulls cookies from request const userId = req.cookies.ssid; const username = req.cookies.username; - try { // trying to find project, update its userId and username to a new project, then save it - const originalProject = await Projects.findOne({ _id: req.params.docId }).exec(); + try { + // trying to find project, update its userId and username to a new project, then save it + const originalProject = await Projects.findOne({ + _id: req.params.docId + }).exec(); const updatedProject = originalProject.toObject({ minimize: false }); // minimize false makes sure Mongoose / MongoDB does not remove nested properties with values of empty objects {} updatedProject.userId = userId; - updatedProject.project.forked = true; + updatedProject.project.forked = true; updatedProject.published = false; updatedProject.forked = `Forked from ${updatedProject.username}`; // add forked tag with current project owner username updatedProject.username = username; // then switch to the cloning username @@ -138,8 +181,7 @@ const marketplaceController: MarketplaceController = { const clonedProject = await Projects.create(updatedProject); res.locals.clonedProject = clonedProject.toObject({ minimize: false }); // need to convert back to an object to send to frontend, again make sure minimize is false return next(); - } - catch (err) { + } catch (err) { return next({ log: `Error in marketplaceController.cloneProject: ${err}`, message: { @@ -147,6 +189,6 @@ const marketplaceController: MarketplaceController = { } }); } - }, + } }; export default marketplaceController; diff --git a/server/controllers/projectController.ts b/server/controllers/projectController.ts index 0dd7d5bbf..3c7d1457c 100644 --- a/server/controllers/projectController.ts +++ b/server/controllers/projectController.ts @@ -1,27 +1,34 @@ import { ProjectController } from '../interfaces'; import { Projects } from '../models/reactypeModels'; -import { State} from '../../app/src/interfaces/Interfaces' +import { State } from '../../app/src/interfaces/Interfaces'; // array of objects, objects inside type Projects = { project: {} }[]; const projectController: ProjectController = { - // saveProject saves current workspace to database - saveProject: (req, res, next) => { - + /** + * Middleware function to save the current workspace to the database. + * + * @callback SaveProjectMiddleware + * @param {object} req - The request object. + * @param {object} res - The response object. + * @param {Function} next - The next middleware function in the stack. + * @returns {void} + */ + saveProject: (req, res, next): void => { // pull project name and project itself from body const { name, project, comments } = req.body; const username = req.cookies.username; const userId = req.cookies.ssid; //deleted published from project - const noPub = {...project}; + const noPub = { ...project }; delete noPub.published; // create createdBy field for the document const createdAt = Date.now(); // pull ssid from cookies for user id Projects.findOneAndUpdate( // looks in projects collection for project by user and name - { name, userId, username}, + { name, userId, username }, // update or insert the project { project: noPub, createdAt, published: false, comments }, // Options: @@ -43,32 +50,60 @@ const projectController: ProjectController = { ); }, - // gets all of current user's projects - getProjects: (req, res, next) => { - const userId = req.cookies.ssid - Projects.find({ userId }, (err, projects: Array<{_id: string; published: boolean; project: object }>) => { - if (err) { - return next({ - log: `Error in projectController.getProjects: ${err}`, - message: { - err: 'Error in projectController.getProjects, check server logs for details' - } - }); + /** + * Middleware function to retrieve all projects of the current user from the database. + * + * @callback GetProjectsMiddleware + * @param {object} req - The request object. + * @param {object} res - The response object. + * @param {Function} next - The next middleware function in the stack. + * @returns {void} + */ + getProjects: (req, res, next): void => { + const userId = req.cookies.ssid; + Projects.find( + { userId }, + ( + err, + projects: Array<{ _id: string; published: boolean; project: object }> + ) => { + if (err) { + return next({ + log: `Error in projectController.getProjects: ${err}`, + message: { + err: 'Error in projectController.getProjects, check server logs for details' + } + }); + } + // so it returns each project like it is in state, not the whole object in DB + res.locals.projects = projects.map( + (elem: { + _id: string; + name: string; + published: boolean; + project: object; + }) => ({ + _id: elem._id, + name: elem.name, + published: elem.published, + ...elem.project + }) + ); + return next(); } - // so it returns each project like it is in state, not the whole object in DB - res.locals.projects = projects.map((elem: {_id: string; name: string; published: boolean; project: object } ) =>({ - _id: elem._id, - name: elem.name, - published: elem.published, - ...elem.project - })); - return next(); - }); + ); }, - - // delete project from database - deleteProject: async (req, res, next) => { + /** + * Middleware function to delete a project from the database. + * + * @callback DeleteProjectMiddleware + * @param {object} req - The request object. + * @param {object} res - The response object. + * @param {Function} next - The next middleware function in the stack. + * @returns {Promise} + */ + deleteProject: async (req, res, next): Promise => { // pull project name and userId from req.body const { _id } = req.body; const userId = req.cookies.ssid; diff --git a/server/controllers/sessionController.ts b/server/controllers/sessionController.ts index 81853c1af..ce3d66610 100644 --- a/server/controllers/sessionController.ts +++ b/server/controllers/sessionController.ts @@ -6,7 +6,16 @@ import { SessionController, SessionCookie } from '../interfaces'; dotenv.config(); // here we are cheching that the user making the request is login in and has a valid cookieId const sessionController: SessionController = { - isLoggedIn: async (req, res, next) => { + /** + * Middleware function to check if the user is logged in. + * + * @callback IsLoggedInMiddleware + * @param {object} req - The request object. + * @param {object} res - The response object. + * @param {Function} next - The next middleware function in the stack. + * @returns {Promise} + */ + isLoggedIn: async (req, res, next): Promise => { // if (process.env.NODE_ENV === 'test') { // // Skip authentication checks in test environment // return next(); @@ -38,8 +47,17 @@ const sessionController: SessionController = { }); } }, - // startSession - create and save a new session into the database - startSession: (req, res, next) => { + + /** + * Middleware function to create and save a new session into the database. + * + * @callback StartSessionMiddleware + * @param {object} req - The request object. + * @param {object} res - The response object. + * @param {Function} next - The next middleware function in the stack. + * @returns {void} + */ + startSession: (req, res, next): void => { // first check if user is logged in already Sessions.findOne({ cookieId: res.locals.id || req.user.id }, (err, ses) => { if (err) { @@ -77,7 +95,16 @@ const sessionController: SessionController = { }); }, - endSession: (req, res, next) => { + /** + * Middleware function to end the current session. + * + * @callback EndSessionMiddleware + * @param {object} req - The request object. + * @param {object} res - The response object. + * @param {Function} next - The next middleware function in the stack. + * @returns {void} + */ + endSession: (req, res, next): void => { //finding then deleting the session Sessions.findOneAndDelete( { cookieId: req.cookies.ssid }, diff --git a/server/controllers/userController.ts b/server/controllers/userController.ts index 6f327c697..0e99bd494 100644 --- a/server/controllers/userController.ts +++ b/server/controllers/userController.ts @@ -3,8 +3,12 @@ import { Users } from '../models/reactypeModels'; import bcrypt from 'bcryptjs'; import { newUserError, UserController } from '../interfaces'; -// random password is subtituted when user uses Oauth and no new password is provided -const randomPassword = () => { +/** + * Generates a random password. + * + * @returns {string} A randomly generated password. + */ +const randomPassword = (): string => { function getRandomSpecialChar() { const code = Math.round(Math.random() * (38 - 37) + 37); return String.fromCharCode(code); @@ -25,7 +29,16 @@ const randomPassword = () => { }; const userController: UserController = { - getUser: async (req, res, next) => { + /** + * Middleware function to get user details. + * + * @callback GetUserMiddleware + * @param {object} req - The request object. + * @param {object} res - The response object. + * @param {Function} next - The next middleware function in the stack. + * @returns {Promise} + */ + getUser: async (req, res, next): Promise => { try { const { username } = req.body; const user = await Users.findOne({ username }); @@ -39,7 +52,17 @@ const userController: UserController = { }); } }, - createUser: (req, res, next) => { + + /** + * Middleware function to create a new user. + * + * @callback CreateUserMiddleware + * @param {object} req - The request object. + * @param {object} res - The response object. + * @param {Function} next - The next middleware function in the stack. + * @returns {void | import('express').Response} Returns void or an Express Response object. + */ + createUser: (req, res, next): void | import('express').Response => { let email, username, password; // use this condition for Oauth login if (res.locals.signUpType === 'oauth') { @@ -96,9 +119,18 @@ const userController: UserController = { ); }, - // verifyUser - Obtain username and password from the request body, locate - // the appropriate user in the database, and then authenticate the submitted password against the password stored in the database. - verifyUser: (req, res, next) => { + /** + * Middleware function to verify user credentials. + * Obtain username and password from the request body, then locates the appropriate user in the database. + * Then authenticates the submitted password against the password stored in the database. + * + * @callback VerifyUserMiddleware + * @param {object} req - The request object. + * @param {object} res - The response object. + * @param {Function} next - The next middleware function in the stack. + * @returns {void | import('express').Response} Returns void or an Express Response object. + */ + verifyUser: (req, res, next): void | import('express').Response => { let { username, password, isFbOauth } = req.body; // handle Oauth if (res.locals.signUpType === 'oauth') { @@ -142,8 +174,20 @@ const userController: UserController = { }); }, - // updatePassword - update the user's password in the database - updatePassword: async (req, res, next) => { + /** + * Middleware function to update user password. + * + * @callback UpdatePasswordMiddleware + * @param {object} req - The request object. + * @param {object} res - The response object. + * @param {Function} next - The next middleware function in the stack. + * @returns {Promise} Returns a promise resolving to void or an Express Response object. + */ + updatePassword: async ( + req, + res, + next + ): Promise => { try { if (!req.body.password) { return res.status(400).json({ error: 'Password is required.' }); diff --git a/server/controllers/userStylesController.ts b/server/controllers/userStylesController.ts index b41bd46f1..c67cc3264 100644 --- a/server/controllers/userStylesController.ts +++ b/server/controllers/userStylesController.ts @@ -3,9 +3,17 @@ import path from 'path'; import { UserStylesController } from '../interfaces'; const userStylesController: UserStylesController = { - // Rewrite file - saveCssFile: (req, res, next) => { - console.log("I am here in the cstylesController.") + /** + * Middleware function to save CSS data to a file. + * + * @callback SaveCssFileMiddleware + * @param {object} req - The request object. + * @param {object} res - The response object. + * @param {Function} next - The next middleware function in the stack. + * @returns {void} This function does not return anything directly but calls the next middleware function. + */ + saveCssFile: (req, res, next): void => { + console.log('I am here in the cstylesController.'); const newText = req.body.data; fs.writeFile( path.join(__dirname, '../assets/renderDemo.css'), diff --git a/server/graphQL/resolvers/mutation.ts b/server/graphQL/resolvers/mutation.ts index 94db6c44d..45a3d19dd 100644 --- a/server/graphQL/resolvers/mutation.ts +++ b/server/graphQL/resolvers/mutation.ts @@ -9,6 +9,16 @@ import { Projects, Users, Comments } from '../../models/reactypeModels'; */ const Project = { + /** + * Resolver function for adding likes to a project. + * + * @param {object} parent - The parent object. + * @param {object} args - The arguments passed to the resolver. + * @param {string} args.projId - The ID of the project to add likes to. + * @param {number} args.likes - The number of likes to add. + * @returns {Promise} The updated project object. + * @throws {ApolloServerErrorCode} Throws an error if the project is not found. + */ addLike: async (parent, { projId, likes }) => { const filter = { _id: projId }; const update = { likes }; @@ -32,7 +42,17 @@ const Project = { } ); }, - + /** + * Resolver function for making a copy of a project. + * + * @param {object} parent - The parent object. + * @param {object} args - The arguments passed to the resolver. + * @param {string} args.projId - The ID of the project to copy. + * @param {string} args.userId - The ID of the user making the copy. + * @param {string} args.username - The username of the user making the copy. + * @returns {Promise} The copied project object. + * @throws {ApolloServerErrorCode} Throws an error if the project or user is not found. + */ makeCopy: async (parent, { projId, userId, username }) => { const filter = { _id: projId }; const target = await Projects.findOne(filter); @@ -81,7 +101,15 @@ const Project = { } ); }, - + /** + * Resolver function for deleting a project. + * + * @param {object} parent - The parent object. + * @param {object} args - The arguments passed to the resolver. + * @param {string} args.projId - The ID of the project to delete. + * @returns {Promise} The deleted project object. + * @throws {ApolloServerErrorCode} Throws an error if the project is not found. + */ deleteProject: async (parent, { projId }) => { const filter = { _id: projId }; const options = { strict: true }; @@ -105,7 +133,16 @@ const Project = { } ); }, - + /** + * Resolver function for publishing/unpublishing a project. + * + * @param {object} parent - The parent object. + * @param {object} args - The arguments passed to the resolver. + * @param {string} args.projId - The ID of the project to publish/unpublish. + * @param {boolean} args.published - The status indicating whether the project should be published or unpublished. + * @returns {Promise} The updated project object. + * @throws {ApolloServerErrorCode} Throws an error if the project is not found. + */ publishProject: async (parent, { projId, published }) => { const filter = { _id: projId }; const update = { published }; @@ -129,7 +166,17 @@ const Project = { } ); }, - + /** + * Resolver function for adding a comment to a project. + * + * @param {object} parent - The parent object. + * @param {object} args - The arguments passed to the resolver. + * @param {string} args.projId - The ID of the project to add the comment to. + * @param {string} args.comment - The comment text. + * @param {string} args.username - The username of the user adding the comment. + * @returns {Promise} The updated project object with added comment. + * @throws {ApolloServerErrorCode} Throws an error if the project is not found. + */ addComment: async (parent, { projId, comment, username }) => { const filter = { _id: projId }; const options = { new: true }; diff --git a/server/graphQL/resolvers/query.ts b/server/graphQL/resolvers/query.ts index 3e8ff9e2a..cdd29486e 100644 --- a/server/graphQL/resolvers/query.ts +++ b/server/graphQL/resolvers/query.ts @@ -9,6 +9,15 @@ import { Projects, Comments } from '../../models/reactypeModels'; // https://www.apollographql.com/docs/apollo-server/data/resolvers/#defining-a-resolver const Project = { + /** + * Resolver function for retrieving a single project by ID. + * + * @param {object} parent - The parent object. + * @param {object} args - The arguments passed to the resolver. + * @param {string} args.projId - The ID of the project to retrieve. + * @returns {Promise} The project object. + * @throws {ApolloServerErrorCode} Throws an error if the project is not found. + */ getProject: async (parent, { projId }) => { const resp = await Projects.findOne({ _id: projId }); if (resp) { @@ -32,7 +41,15 @@ const Project = { } ); }, - + /** + * Resolver function for retrieving all projects, optionally filtered by user ID. + * + * @param {object} parent - The parent object. + * @param {object} args - The arguments passed to the resolver. + * @param {string} args.userId - (Optional) The ID of the user to filter projects by. + * @returns {Promise>} An array of project objects. + * @throws {ApolloServerErrorCode} Throws an error if the user or projects are not found. + */ getAllProjects: async (parent, { userId }) => { let resp = await Projects.find({}); if (userId) { diff --git a/server/routers/auth.ts b/server/routers/auth.ts index 4b02c6894..fd097c836 100644 --- a/server/routers/auth.ts +++ b/server/routers/auth.ts @@ -16,6 +16,9 @@ interface UserReq extends Request { const { API_BASE_URL2 } = config; const router = express.Router(); +/** + * Route handler for initiating GitHub OAuth authentication. Redirects the user to GitHub OAuth login page. + */ router.get( '/github', passport.authenticate('github', { @@ -23,6 +26,13 @@ router.get( }) ); +/** + * Route handler for handling GitHub OAuth callback. After successful authentication, + * starts a session, sets session cookies, and redirects the user back to the specified base URL. + * + * @param {UserReq} req - The request object from Express extended with user information. + * @param {express.Response} res - The response object from Express. + */ router.get( '/github/callback', passport.authenticate('github'), @@ -45,6 +55,9 @@ router.get( } ); +/** + * Route handler for initiating Google OAuth authentication. Redirects the user to Google OAuth login page. + */ router.get( '/google', passport.authenticate('google', { @@ -52,6 +65,13 @@ router.get( }) ); +/** + * Route handler for handling Google OAuth callback. After successful authentication, + * starts a session, sets session cookies, and redirects the user back to the specified base URL. + * + * @param {UserReq} req - The request object from Express extended with user information. + * @param {express.Response} res - The response object from Express. + */ router.get( '/google/callback', passport.authenticate('google'), diff --git a/server/routers/passport-setup.ts b/server/routers/passport-setup.ts index a55b7d9f7..dea2b1f5f 100644 --- a/server/routers/passport-setup.ts +++ b/server/routers/passport-setup.ts @@ -17,6 +17,14 @@ passport.deserializeUser((id, done) => { }); }); +/** + * Passport strategy for GitHub OAuth authentication. + * + * @param {string} accessToken - Access token provided by GitHub for authentication. + * @param {string} refreshToken - Refresh token provided by GitHub for authentication. + * @param {object} profile - User profile data retrieved from GitHub. + * @param {Function} done - Callback function to be called when authentication process is completed. + */ passport.use( new GitHubStrategy( { @@ -55,6 +63,14 @@ passport.use( ) ); +/** + * Passport strategy for Google OAuth authentication. + * + * @param {string} accessToken - Access token provided by Google for authentication. + * @param {string} refreshToken - Refresh token provided by Google for authentication. + * @param {object} profile - User profile data retrieved from Google. + * @param {Function} done - Callback function to be called when authentication process is completed. + */ passport.use( new GoogleStrategy( { diff --git a/server/routers/stylesRouter.ts b/server/routers/stylesRouter.ts index 89e8d280e..5b073fb04 100644 --- a/server/routers/stylesRouter.ts +++ b/server/routers/stylesRouter.ts @@ -2,7 +2,13 @@ import express, { Request, Response} from 'express'; import userStylesController from '../controllers/userStylesController'; const router = express.Router(); -// save new css file +/** + * Route handler for saving a new CSS file. Invokes the userStylesController to save the CSS file, + * and sends a JSON response with status 200. + * + * @param {express.Request} req - The request object from Express. + * @param {express.Response} res - The response object from Express. + */ router.post('/save', userStylesController.saveCssFile, (req: Request, res: Response) => { console.log("I am here in the styles Router.") res.status(200).json({}); diff --git a/server/server.ts b/server/server.ts index 09db24c5f..a8510b355 100644 --- a/server/server.ts +++ b/server/server.ts @@ -50,6 +50,14 @@ app.use( }) ); +/** + * Middleware function to log each request received by the server. It constructs the full URL of the request + * and logs it with the HTTP method to the console before passing control to the next middleware function. + * + * @param {Object} req - The request object from Express. Provides information about the HTTP request. + * @param {Object} res - The response object from Express. Used to send back the desired HTTP response. + * @param {Function} next - Callback argument to the middleware function, called to pass control to the next middleware function. + */ function logRequest(req, res, next) { const fullUrl = `${req.protocol}://${req.get('host')}${req.originalUrl}`; console.log(`Received request on ${req.method}: ${fullUrl}`); @@ -106,6 +114,11 @@ const io = new Server(httpServer, { } }); +/** + * Asynchronously creates a new meeting room using the Video SDK API. + * + * @returns {Promise} A Promise that resolves with the ID of the created meeting room. + */ const createMeeting = async () => { const res = await fetch(`https://api.videosdk.live/v2/rooms`, { method: 'POST', @@ -123,6 +136,12 @@ const createMeeting = async () => { const roomLists = {}; //server listening to new connections +/** + * Event handler for new socket connections. Handles the logic related to creating or joining a room, managing users within the room, + * and handling disconnections. + * + * @param {Socket} client - The socket object representing the newly connected client. + */ io.on('connection', (client) => { client.on( 'creating a room', @@ -459,8 +478,11 @@ const resolvers = { // Re-direct to route handlers: app.use('/user-styles', stylesRouter); -// schemas used for graphQL - +/** + * GraphQL schema definition for the server. Combines type definitions and resolvers into a single schema. + * + * @type {import('graphql').GraphQLSchema} - GraphQL schema object representing the server's schema. + */ import typeDefs from './graphQL/schema/typeDefs'; const schema = makeExecutableSchema({ typeDefs, resolvers });