diff --git a/CHANGE_LOG.md b/CHANGE_LOG.md
index 101ce12c2..64ebcb155 100644
--- a/CHANGE_LOG.md
+++ b/CHANGE_LOG.md
@@ -3,6 +3,17 @@
ReacType Change Log
+**Version 12.0.0 Changes**
+
+-Context Visualizer: You can now visually see what component is consuming which context. As you click on the interactive tree, the component assigned to the context will be revealed.
+-React 18: Updated to React 18
+-Export Feature: Created an exportable context file, integrated with original codebase.
+Ready to go code: Added boilerplate codes to components based on which contexts they are consuming.
+
+**A note to future contributors**
+
+Attempted to implement Facebook and Google OAuth via passport but as of Electron’s current version, neither of them not compatible with electron.
+
**Version 11.0.0 Changes:**
- Added Next.js functionality
diff --git a/README.md b/README.md
index 90e7df329..f7b451dc8 100644
--- a/README.md
+++ b/README.md
@@ -30,6 +30,9 @@ How to use
- **Start a project (only after registration)**: Registered users can create a new project and select whether they want their project to be a Next.js, Gatsby.js, or classic React project. Also, registered users can save projects to return to them at a later time.
- **Add Components**: Create components on the right panel. Components can be associated with a route, or they can be used within other components.
- **Delete Components**: Delete components after focusing on them in the right panel. Be careful when deleting components: Upon deletion, all instances of the component will be removed within the application/project.
+- **Context Visualizer**: You can now visually see what component is consuming which context. As you click on the interactive tree, the component assigned to the context will be revealed.
+- **Context Code Preview**: Once contexts have been assigned to the desired components, click ‘Export’ to incorporate context into your existing codebase so you can save it as a file.
+- **Ready to go code**: Added boilerplate codes to components based on which contexts they are consuming.
- **Add Custom Elements**: Create custom elements or add provided HTML elements into the application. Once the project is exported, the HTML tags generated in the code preview will function as expected. You can specify functionality for custom elements in the code preview. The tutorial on HTML Elements explains more on how to do this.
- **Delete Custom HTML Elements**: Delete custom HTML elements by clicking on the ‘X’ button adjacent to the element. Be careful when deleting custom elements: All instances of the element will be deleted within the application/project.
- **Create Instances on the Canvas**: Each component has its own canvas. Add an element to a component by dragging it onto the canvas. Div components are arbitrarily nestable and useful for complex layouts. Next.js and Gatsby.js projects have Link components to enable client-side navigation to other routes.
@@ -107,6 +110,8 @@ How to use
[Anthony Torrero](https://www.linkedin.com/in/anthony-torrero-4b8798159/) [@Anthonytorrero](https://github.com/Anthonytorrero)
+[Bianca Picasso](linkedin.com/in/bianca-picasso) [@BiancaPicasso](https://github.com/BiancaPicasso)
+
[Brian Han](https://www.linkedin.com/in/brianjisoohan/) [@brianjshan](https://github.com/brianjshan)
[Bryan Chau](https://www.linkedin.com/in/chaubryan1/) [@bchauu](https://github.com/bchauu)
@@ -139,6 +144,8 @@ How to use
[Fredo Chen](https://www.linkedin.com/in/fredochen/) [@fredosauce](https://github.com/fredosauce)
+[Huy Pham](linkedin.com/in/huypham048) [@huypham048](https://github.com/huypham048)
+
[Jonathan Calvo Ramirez](https://www.linkedin.com/in/jonathan-calvo/) [@jonocr](https://github.com/jonocr)
[Jesse Zuniga](https://linkedin.com/in/jesse-zuniga) [@jzuniga206](https://github.com/jzuniga206)
@@ -149,6 +156,8 @@ How to use
[Katrina Henderson](https://www.linkedin.com/in/katrinahenderson/) [@kchender](https://github.com/kchender)
+[Ken Bains](linkedin.com/in/ken-bains) [@ken-Bains](https://github.com/ken-Bains)
+
[Kevin Park](https://www.linkedin.com/in/xkevinpark/) [@xkevinpark](https://github.com/xkevinpark)
[Khuong Nguyen](https://www.linkedin.com/in/khuong-nguyen/) [@khuongdn16](https://github.com/khuongdn16)
@@ -171,6 +180,8 @@ How to use
[Ron Fu](https://www.linkedin.com/in/ronfu)[@rfvisuals](https://github.com/rfvisuals)
+[Salvatore Saluga](linkedin.com/in/salvatore-saluga) [@SalSaluga](https://github.com/SalSaluga)
+
[Sean Sadykoff](https://www.linkedin.com/in/sean-sadykoff/) [@sean1292](https://github.com/sean1292)
[Shana Hoehn](https://www.linkedin.com/in/shana-hoehn-70297b169/) [@slhoehn](https://github.com/slhoehn)
diff --git a/__tests__/contextReducer.test.js b/__tests__/contextReducer.test.js
new file mode 100644
index 000000000..9a2f11dd4
--- /dev/null
+++ b/__tests__/contextReducer.test.js
@@ -0,0 +1,165 @@
+import subject from '../app/src/redux/reducers/slice/contextReducer';
+
+describe('Context Reducer', () => {
+ let state;
+
+ beforeEach(() => {
+ state = {
+ allContext: []
+ };
+ });
+
+ describe('default state', () => {
+ it('should return a default state when given an undefined input', () => {
+ expect(subject(undefined, { type: undefined })).toEqual(state);
+ });
+ });
+
+ describe('unrecognized action types', () => {
+ it('should return the original state without any duplication', () => {
+ expect(subject(state, { type: 'REMOVE_STATE' })).toBe(state);
+ });
+ });
+
+ describe('ADD_CONTEXT', () => {
+ const action = {
+ type: 'ADD_CONTEXT',
+ payload: {
+ name: 'Theme Context'
+ }
+ };
+
+ it('adds a context', () => {
+ const { allContext } = subject(state, action);
+ expect(allContext[0]).toEqual({
+ name: 'Theme Context',
+ values: [],
+ components: []
+ });
+ });
+
+ it('returns a state object not strictly equal to the original', () => {
+ const newState = subject(state, action);
+ expect(newState).not.toBe(state);
+ });
+
+ it('should immutably update the nested state object', () => {
+ const { allContext } = subject(state, action);
+ expect(allContext).not.toBe(state.allContext);
+ });
+ });
+
+ describe('ADD_CONTEXT_VALUES', () => {
+ beforeEach(() => {
+ state = {
+ allContext: [
+ {
+ name: 'Theme Context',
+ values: [],
+ components: []
+ }
+ ]
+ };
+ });
+
+ const action = {
+ type: 'ADD_CONTEXT_VALUES',
+ payload: {
+ name: 'Theme Context',
+ inputKey: 'Theme Color',
+ inputValue: 'Dark'
+ }
+ };
+
+ it('adds a key-value pair to values array of the specified context', () => {
+ const { allContext } = subject(state, action);
+ expect(allContext[0].values.length).toEqual(1);
+ expect(allContext[0].values[0].key).toEqual('Theme Color');
+ expect(allContext[0].values[0].value).toEqual('Dark');
+ });
+
+ it('includes an allContext not strictly equal to the original', () => {
+ const { allContext } = subject(state, action);
+
+ expect(allContext).not.toBe(state.allContext);
+ });
+ });
+
+ describe('DELETE CONTEXT', () => {
+ let action;
+ beforeEach(() => {
+ state = {
+ allContext: [
+ {
+ name: 'Theme Context',
+ values: [],
+ components: []
+ },
+ {
+ name: 'To be deleted',
+ values: [],
+ components: []
+ }
+ ]
+ };
+
+ action = {
+ type: 'DELETE_CONTEXT',
+ payload: {
+ name: 'Theme Context'
+ }
+ };
+ });
+
+ it('removes specified context from the state', () => {
+ const { allContext } = subject(state, action);
+
+ expect(allContext.length).toEqual(1);
+ });
+
+ it('includes an allContext not strictly equal to the original', () => {
+ const { allContext } = subject(state, action);
+
+ expect(allContext).not.toBe(state.allContext);
+ });
+ });
+
+ describe('ADD_COMPONENT_TO_CONTEXT', () => {
+ beforeEach(() => {
+ state = {
+ allContext: [
+ {
+ name: 'Theme Context',
+ values: [],
+ components: []
+ }
+ ]
+ };
+ });
+
+ const action = {
+ type: 'ADD_COMPONENT_TO_CONTEXT',
+ payload: {
+ context: {
+ name: 'Theme Context'
+ },
+ component: {
+ name: 'Main Component'
+ }
+ }
+ };
+
+ it('adds a new component to the specified context', () => {
+ const { allContext } = subject(state, action);
+
+ expect(allContext[0].components.length).toEqual(1);
+ expect(allContext[0].components[0]).toEqual('Main Component');
+ });
+
+ it('includes an allContext not strictly equal to the original', () => {
+ const { allContext } = subject(state, action);
+
+ expect(allContext).not.toBe(state.allContext);
+ });
+ });
+});
diff --git a/__tests__/contextReducer.test.ts b/__tests__/contextReducer.test.ts
new file mode 100644
index 000000000..9a2f11dd4
--- /dev/null
+++ b/__tests__/contextReducer.test.ts
@@ -0,0 +1,165 @@
+import subject from '../app/src/redux/reducers/slice/contextReducer';
+
+describe('Context Reducer', () => {
+ let state;
+
+ beforeEach(() => {
+ state = {
+ allContext: []
+ };
+ });
+
+ describe('default state', () => {
+ it('should return a default state when given an undefined input', () => {
+ expect(subject(undefined, { type: undefined })).toEqual(state);
+ });
+ });
+
+ describe('unrecognized action types', () => {
+ it('should return the original state without any duplication', () => {
+ expect(subject(state, { type: 'REMOVE_STATE' })).toBe(state);
+ });
+ });
+
+ describe('ADD_CONTEXT', () => {
+ const action = {
+ type: 'ADD_CONTEXT',
+ payload: {
+ name: 'Theme Context'
+ }
+ };
+
+ it('adds a context', () => {
+ const { allContext } = subject(state, action);
+ expect(allContext[0]).toEqual({
+ name: 'Theme Context',
+ values: [],
+ components: []
+ });
+ });
+
+ it('returns a state object not strictly equal to the original', () => {
+ const newState = subject(state, action);
+ expect(newState).not.toBe(state);
+ });
+
+ it('should immutably update the nested state object', () => {
+ const { allContext } = subject(state, action);
+ expect(allContext).not.toBe(state.allContext);
+ });
+ });
+
+ describe('ADD_CONTEXT_VALUES', () => {
+ beforeEach(() => {
+ state = {
+ allContext: [
+ {
+ name: 'Theme Context',
+ values: [],
+ components: []
+ }
+ ]
+ };
+ });
+
+ const action = {
+ type: 'ADD_CONTEXT_VALUES',
+ payload: {
+ name: 'Theme Context',
+ inputKey: 'Theme Color',
+ inputValue: 'Dark'
+ }
+ };
+
+ it('adds a key-value pair to values array of the specified context', () => {
+ const { allContext } = subject(state, action);
+ expect(allContext[0].values.length).toEqual(1);
+ expect(allContext[0].values[0].key).toEqual('Theme Color');
+ expect(allContext[0].values[0].value).toEqual('Dark');
+ });
+
+ it('includes an allContext not strictly equal to the original', () => {
+ const { allContext } = subject(state, action);
+
+ expect(allContext).not.toBe(state.allContext);
+ });
+ });
+
+ describe('DELETE CONTEXT', () => {
+ let action;
+ beforeEach(() => {
+ state = {
+ allContext: [
+ {
+ name: 'Theme Context',
+ values: [],
+ components: []
+ },
+ {
+ name: 'To be deleted',
+ values: [],
+ components: []
+ }
+ ]
+ };
+
+ action = {
+ type: 'DELETE_CONTEXT',
+ payload: {
+ name: 'Theme Context'
+ }
+ };
+ });
+
+ it('removes specified context from the state', () => {
+ const { allContext } = subject(state, action);
+
+ expect(allContext.length).toEqual(1);
+ });
+
+ it('includes an allContext not strictly equal to the original', () => {
+ const { allContext } = subject(state, action);
+
+ expect(allContext).not.toBe(state.allContext);
+ });
+ });
+
+ describe('ADD_COMPONENT_TO_CONTEXT', () => {
+ beforeEach(() => {
+ state = {
+ allContext: [
+ {
+ name: 'Theme Context',
+ values: [],
+ components: []
+ }
+ ]
+ };
+ });
+
+ const action = {
+ type: 'ADD_COMPONENT_TO_CONTEXT',
+ payload: {
+ context: {
+ name: 'Theme Context'
+ },
+ component: {
+ name: 'Main Component'
+ }
+ }
+ };
+
+ it('adds a new component to the specified context', () => {
+ const { allContext } = subject(state, action);
+
+ expect(allContext[0].components.length).toEqual(1);
+ expect(allContext[0].components[0]).toEqual('Main Component');
+ });
+
+ it('includes an allContext not strictly equal to the original', () => {
+ const { allContext } = subject(state, action);
+
+ expect(allContext).not.toBe(state.allContext);
+ });
+ });
+});
diff --git a/app/src/components/App.tsx b/app/src/components/App.tsx
index d1e0467da..119950398 100644
--- a/app/src/components/App.tsx
+++ b/app/src/components/App.tsx
@@ -49,16 +49,16 @@ export const App = (): JSX.Element => {
} else {
console.log(
'No user project found in localforage, setting initial state blank'
- );
- }
- });
- }
- }, []);
+ );
+ }
+ });
+ }
+ }, []);
useEffect(() => {
// provide config properties to legacy projects so new edits can be auto saved
if (state.config === undefined) {
- state.config = {saveFlag:true, saveTimer:false};
- };
+ state.config = { saveFlag: true, saveTimer: false };
+ }
// New project save configuration to optimize server load and minimize Ajax requests
if (state.config.saveFlag) {
state.config.saveFlag = false;
@@ -82,7 +82,7 @@ export const App = (): JSX.Element => {
state.config.saveFlag = true;
}, 15000);
}
- }, [state])
+ }, [state]);
return (
diff --git a/app/src/components/ContextAPIManager/AssignTab/AssignContainer.tsx b/app/src/components/ContextAPIManager/AssignTab/AssignContainer.tsx
new file mode 100644
index 000000000..993dfa7e2
--- /dev/null
+++ b/app/src/components/ContextAPIManager/AssignTab/AssignContainer.tsx
@@ -0,0 +1,144 @@
+import React, { useContext, useState, Fragment, useEffect } from 'react';
+import DataTable from '../CreateTab/components/DataTable';
+import { useStore, useDispatch } from 'react-redux';
+import ContextDropDown from './components/ContextDropDown';
+import ComponentDropDown from './components/ComponentDropDrown';
+import Divider from '@mui/material/Divider';
+import Grid from '@mui/material/Grid';
+import ComponentTable from './components/ComponentTable';
+import { Button } from '@mui/material';
+import DoubleArrowIcon from '@mui/icons-material/DoubleArrow';
+import * as actions from '../../../redux/actions/actions';
+import StateContext from '../../../context/context';
+
+const AssignContainer = () => {
+ const store = useStore();
+ const dispatch = useDispatch();
+
+ const [state, setState] = useState([]);
+ const defaultTableData = [{ key: 'Key', value: 'Value' }];
+ const [tableState, setTableState] = React.useState(defaultTableData);
+ const [contextInput, setContextInput] = React.useState(null);
+ const [componentInput, setComponentInput] = React.useState(null);
+ const [componentTable, setComponentTable] = useState([]);
+ const [stateContext, dispatchContext] = useContext(StateContext);
+
+ //fetching data from redux store
+ useEffect(() => {
+ setState(store.getState().contextSlice);
+ }, []);
+
+ const renderTable = targetContext => {
+ if (targetContext === null || !targetContext.values) {
+ setTableState(defaultTableData);
+ } else {
+ setTableState(targetContext.values);
+ }
+ };
+
+ //construct data for table displaying component table
+ const renderComponentTable = targetComponent => {
+ //target Component is main
+
+ const listOfContexts = [];
+ if (
+ !Array.isArray(state) &&
+ targetComponent !== null &&
+ targetComponent.name
+ ) {
+ state.allContext.forEach(context => {
+ if (context.components.includes(targetComponent.name)) {
+ listOfContexts.push(context.name);
+ }
+ });
+ setComponentTable(listOfContexts);
+ }
+ };
+
+ //handling assignment of contexts to components
+ const handleAssignment = () => {
+ if (
+ contextInput === '' ||
+ contextInput === null ||
+ componentInput === '' ||
+ componentInput === null
+ )
+ return;
+ dispatch(
+ actions.addComponentToContext({
+ context: contextInput,
+ component: componentInput
+ })
+ );
+ //trigger generateCode(), update code preview tab
+ dispatchContext({
+ type: 'DELETE ELEMENT',
+ payload: 'FAKE_ID'
+ });
+
+ setState(store.getState().contextSlice);
+ renderComponentTable(componentInput);
+ };
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default AssignContainer;
diff --git a/app/src/components/ContextAPIManager/AssignTab/components/ComponentDropDrown.tsx b/app/src/components/ContextAPIManager/AssignTab/components/ComponentDropDrown.tsx
new file mode 100644
index 000000000..00bd3922a
--- /dev/null
+++ b/app/src/components/ContextAPIManager/AssignTab/components/ComponentDropDrown.tsx
@@ -0,0 +1,95 @@
+import React, { Fragment, useState, useEffect, useContext } from 'react';
+import TextField from '@mui/material/TextField';
+import Autocomplete, { createFilterOptions } from '@mui/material/Autocomplete';
+import Box from '@mui/material/Box';
+import StateContext from '../../../../context/context';
+
+const filter = createFilterOptions();
+
+const ComponentDropDown = ({
+ contextStore,
+ renderComponentTable,
+ componentInput,
+ setComponentInput
+}) => {
+ const { allContext } = contextStore;
+ const [componentList] = useContext(StateContext);
+
+ const onChange = (event, newValue) => {
+ if (typeof newValue === 'string') {
+ setComponentInput({
+ name: newValue
+ });
+ } else if (newValue && newValue.inputValue) {
+ // Create a new contextInput from the user input
+ //console.log(newValue,newValue.inputValue)
+ setComponentInput({
+ name: newValue.inputValue,
+ values: []
+ });
+ renderComponentTable(newValue);
+ } else {
+ setComponentInput(newValue);
+ renderComponentTable(newValue);
+ }
+ };
+
+ const filterOptions = (options, params) => {
+ // setBtnDisabled(true);
+ const filtered = filter(options, params);
+ const { inputValue } = params;
+ // Suggest the creation of a new contextInput
+ const isExisting = options.some(option => inputValue === option.name);
+ if (inputValue !== '' && !isExisting) {
+ filtered.push({
+ inputValue,
+ name: `Add "${inputValue}"`
+ });
+
+ // setBtnDisabled(false);
+ }
+
+ return filtered;
+ };
+
+ const getOptionLabel = option => {
+ // Value selected with enter, right from the input
+ if (typeof option === 'string') {
+ return option;
+ }
+ // Add "xxx" option created dynamically
+ if (option.inputValue) {
+ return option.inputValue;
+ }
+ // Regular option
+ return option.name;
+ };
+
+ const renderOption = (props, option) => {option.name};
+
+ return (
+
+
+ (
+
+ )}
+ />
+
+
+ );
+};
+
+export default ComponentDropDown;
diff --git a/app/src/components/ContextAPIManager/AssignTab/components/ComponentTable.tsx b/app/src/components/ContextAPIManager/AssignTab/components/ComponentTable.tsx
new file mode 100644
index 000000000..e436fc0fd
--- /dev/null
+++ b/app/src/components/ContextAPIManager/AssignTab/components/ComponentTable.tsx
@@ -0,0 +1,52 @@
+import React from 'react';
+import Table from '@mui/material/Table';
+import TableBody from '@mui/material/TableBody';
+import TableContainer from '@mui/material/TableContainer';
+import TableHead from '@mui/material/TableHead';
+import TableRow from '@mui/material/TableRow';
+import Paper from '@mui/material/Paper';
+import { styled } from '@mui/material/styles';
+import TableCell, { tableCellClasses } from '@mui/material/TableCell';
+
+const StyledTableCell = styled(TableCell)(({ theme }) => ({
+ [`&.${tableCellClasses.head}`]: {
+ backgroundColor: theme.palette.common.black,
+ color: theme.palette.common.white
+ },
+ [`&.${tableCellClasses.body}`]: {
+ fontSize: 14
+ }
+}));
+
+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
+ }
+}));
+
+export default function DataTable({ target }) {
+ return (
+
+
+
+
+ Contexts Consumed
+
+
+
+ {target.map((data, index) => (
+
+
+ {data}
+
+
+ ))}
+
+
+
+ );
+}
diff --git a/app/src/components/ContextAPIManager/AssignTab/components/ContextDropDown.tsx b/app/src/components/ContextAPIManager/AssignTab/components/ContextDropDown.tsx
new file mode 100644
index 000000000..37459a45f
--- /dev/null
+++ b/app/src/components/ContextAPIManager/AssignTab/components/ContextDropDown.tsx
@@ -0,0 +1,95 @@
+import React, { Fragment, useState, useEffect } from 'react';
+import TextField from '@mui/material/TextField';
+import Autocomplete, { createFilterOptions } from '@mui/material/Autocomplete';
+import Button from '@mui/material/Button';
+import Box from '@mui/material/Box';
+import { Typography } from '@mui/material';
+
+const filter = createFilterOptions();
+
+const ContextDropDown = ({
+ contextStore,
+ renderTable,
+ contextInput,
+ setContextInput
+}) => {
+ const { allContext } = contextStore;
+
+ const onChange = (event, newValue) => {
+ if (typeof newValue === 'string') {
+ setContextInput({
+ name: newValue
+ });
+ } else if (newValue && newValue.inputValue) {
+ // Create a new contextInput from the user input
+ //console.log(newValue,newValue.inputValue)
+ setContextInput({
+ name: newValue.inputValue,
+ values: []
+ });
+ renderTable(newValue);
+ } else {
+ setContextInput(newValue);
+ renderTable(newValue);
+ }
+ };
+
+ const filterOptions = (options, params) => {
+ // setBtnDisabled(true);
+ const filtered = filter(options, params);
+ const { inputValue } = params;
+ // Suggest the creation of a new contextInput
+ const isExisting = options.some(option => inputValue === option.name);
+ if (inputValue !== '' && !isExisting) {
+ filtered.push({
+ inputValue,
+ name: `Add "${inputValue}"`
+ });
+
+ // setBtnDisabled(false);
+ }
+
+ return filtered;
+ };
+
+ const getOptionLabel = option => {
+ // Value selected with enter, right from the input
+ if (typeof option === 'string') {
+ return option;
+ }
+ // Add "xxx" option created dynamically
+ if (option.inputValue) {
+ return option.inputValue;
+ }
+ // Regular option
+ return option.name;
+ };
+
+ const renderOption = (props, option) => {option.name};
+
+ return (
+
+
+ (
+
+ )}
+ />
+
+
+ );
+};
+
+export default ContextDropDown;
diff --git a/app/src/components/ContextAPIManager/AssignTab/components/ContextTable.tsx b/app/src/components/ContextAPIManager/AssignTab/components/ContextTable.tsx
new file mode 100644
index 000000000..9c306de08
--- /dev/null
+++ b/app/src/components/ContextAPIManager/AssignTab/components/ContextTable.tsx
@@ -0,0 +1,72 @@
+import * as React from 'react';
+import { styled } from '@mui/material/styles';
+import Table from '@mui/material/Table';
+import TableBody from '@mui/material/TableBody';
+import TableCell, { tableCellClasses } from '@mui/material/TableCell';
+import TableContainer from '@mui/material/TableContainer';
+import TableHead from '@mui/material/TableHead';
+import TableRow from '@mui/material/TableRow';
+import Paper from '@mui/material/Paper';
+
+const StyledTableCell = styled(TableCell)(({ theme }) => ({
+ [`&.${tableCellClasses.head}`]: {
+ backgroundColor: theme.palette.common.black,
+ color: theme.palette.common.white
+ },
+ [`&.${tableCellClasses.body}`]: {
+ fontSize: 14
+ }
+}));
+
+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
+ }
+}));
+
+function createData(
+ name: string,
+ calories: number,
+ fat: number,
+ carbs: number,
+ protein: number
+) {
+ return { name, calories, fat, carbs, protein };
+}
+
+const rows = [
+ createData('Frozen yoghurt', 159, 6.0, 24, 4.0),
+ createData('Ice cream sandwich', 237, 9.0, 37, 4.3),
+ createData('Eclair', 262, 16.0, 24, 6.0),
+ createData('Cupcake', 305, 3.7, 67, 4.3),
+ createData('Gingerbread', 356, 16.0, 49, 3.9)
+];
+{/* */}
+export default function ContextTable() {
+ return (
+
+
+
+
+ Context
+ Component
+
+
+
+ {rows.map(row => (
+
+
+ {row.name}
+
+ {row.calories}
+
+ ))}
+
+
+
+ );
+}
diff --git a/app/src/components/ContextAPIManager/ContextManager.tsx b/app/src/components/ContextAPIManager/ContextManager.tsx
new file mode 100644
index 000000000..958112c8a
--- /dev/null
+++ b/app/src/components/ContextAPIManager/ContextManager.tsx
@@ -0,0 +1,56 @@
+import React, { useContext } from 'react';
+import { makeStyles } from '@material-ui/styles';
+import Box from '@mui/material/Box';
+import Tab from '@mui/material/Tab';
+import TabContext from '@mui/lab/TabContext';
+import TabList from '@mui/lab/TabList';
+import TabPanel from '@mui/lab/TabPanel';
+
+import CreateContainer from './CreateTab/CreateContainer';
+import AssignContainer from './AssignTab/AssignContainer';
+import DisplayContainer from './DisplayTab/DisplayContainer';
+
+const useStyles = makeStyles({
+ contextContainer: {
+ backgroundColor: 'white',
+ height: '100%'
+ }
+});
+
+const ContextManager = (props): JSX.Element => {
+ const classes = useStyles();
+ const [value, setValue] = React.useState('1');
+
+ const handleChange = (event: React.SyntheticEvent, newValue: string) => {
+ setValue(newValue);
+ };
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default ContextManager;
diff --git a/app/src/components/ContextAPIManager/CreateTab/CreateContainer.tsx b/app/src/components/ContextAPIManager/CreateTab/CreateContainer.tsx
new file mode 100644
index 000000000..7353526d7
--- /dev/null
+++ b/app/src/components/ContextAPIManager/CreateTab/CreateContainer.tsx
@@ -0,0 +1,122 @@
+import React, { useEffect, useState, useContext } from 'react';
+import { useStore } from 'react-redux';
+import { useDispatch } from 'react-redux';
+import Divider from '@mui/material/Divider';
+import Grid from '@mui/material/Grid';
+import DataTable from './components/DataTable';
+import AddDataForm from './components/AddDataForm';
+import AddContextForm from './components/AddContextForm';
+import * as actions from '../../../redux/actions/actions';
+import { Typography } from '@mui/material';
+import StateContext from '../../../context/context';
+
+const CreateContainer = () => {
+ const defaultTableData = [{ key: 'Enter Key', value: 'Enter value' }];
+ const store = useStore();
+ const [state, setState] = useState([]);
+ const [tableState, setTableState] = React.useState(defaultTableData);
+ const [contextInput, setContextInput] = React.useState(null);
+ const [stateContext, dispatchContext] = useContext(StateContext);
+
+ //pulling data from redux store
+ useEffect(() => {
+ setState(store.getState().contextSlice);
+ }, []);
+
+ const dispatch = useDispatch();
+
+ //update data store when user adds a new context
+ const handleClickSelectContext = () => {
+ //prevent user from adding duplicate context
+ for (let i = 0; i < state.allContext.length; i += 1) {
+ if (state.allContext[i].name === contextInput.name) {
+ return;
+ }
+ }
+ setContextInput('');
+ dispatch(actions.addContextActionCreator(contextInput));
+ setState(store.getState().contextSlice);
+ };
+
+ //update data store when user add new key-value pair to context
+ const handleClickInputData = ({ name }, { inputKey, inputValue }) => {
+ dispatch(
+ actions.addContextValuesActionCreator({ name, inputKey, inputValue })
+ );
+ setState(store.getState().contextSlice);
+ };
+
+ //update data store when user deletes context
+ const handleDeleteContextClick = () => {
+ dispatch(actions.deleteContext(contextInput));
+ setContextInput('');
+ setState(store.getState().contextSlice);
+ setTableState(defaultTableData);
+ dispatchContext({
+ type: 'DELETE ELEMENT',
+ payload: 'FAKE_ID'
+ });
+ };
+
+ //re-render data table when there's new changes
+ const renderTable = targetContext => {
+ if (
+ targetContext === null ||
+ targetContext === undefined ||
+ !targetContext.values
+ ) {
+ // if (targetContext === null || targetContext === undefined) {
+ setTableState(defaultTableData);
+ } else {
+ setTableState(targetContext.values);
+ }
+ };
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Context Data Table
+
+
+
+
+ >
+ );
+};
+
+export default CreateContainer;
diff --git a/app/src/components/ContextAPIManager/CreateTab/components/AddContextForm.tsx b/app/src/components/ContextAPIManager/CreateTab/components/AddContextForm.tsx
new file mode 100644
index 000000000..4bba060ac
--- /dev/null
+++ b/app/src/components/ContextAPIManager/CreateTab/components/AddContextForm.tsx
@@ -0,0 +1,129 @@
+import React, { Fragment, useState, useEffect, useContext } from 'react';
+import TextField from '@mui/material/TextField';
+import Autocomplete, { createFilterOptions } from '@mui/material/Autocomplete';
+import Button from '@mui/material/Button';
+import Box from '@mui/material/Box';
+import { Typography } from '@mui/material';
+import StateContext from '../../../../context/context';
+
+const filter = createFilterOptions();
+
+const AddContextForm = ({
+ contextStore,
+ handleClickSelectContext,
+ handleDeleteContextClick,
+ renderTable,
+ contextInput,
+ setContextInput
+}) => {
+ const { allContext } = contextStore;
+ const [btnDisabled, setBtnDisabled] = useState(false);
+ const [state, dispatch] = useContext(StateContext);
+
+ const handleClick = () => {
+ if (contextInput === '' || contextInput === null) return;
+ handleClickSelectContext();
+
+ //need to trigger the generate code functionality to update the code preview tab. Sending dummy data to trigger with a DELELTE ELEMENT dispatch method
+ dispatch({
+ type: 'DELETE ELEMENT',
+ payload: 'FAKE_ID'
+ });
+ };
+
+ const onChange = (event, newValue) => {
+ if (typeof newValue === 'string') {
+ setContextInput({
+ name: newValue
+ });
+ } else if (newValue && newValue.inputValue) {
+ // Create a new contextInput from the user input
+ //console.log(newValue,newValue.inputValue)
+ setContextInput({
+ name: newValue.inputValue,
+ values: []
+ });
+ renderTable(newValue);
+ } else {
+ setContextInput(newValue);
+ renderTable(newValue);
+ }
+ };
+
+ const filterOptions = (options, params) => {
+ // setBtnDisabled(true);
+ const filtered = filter(options, params);
+ const { inputValue } = params;
+ // Suggest the creation of a new contextInput
+ const isExisting = options.some(option => inputValue === option.name);
+ if (inputValue !== '' && !isExisting) {
+ filtered.push({
+ inputValue,
+ name: `Add "${inputValue}"`
+ });
+
+ // setBtnDisabled(false);
+ }
+
+ return filtered;
+ };
+
+ const getOptionLabel = option => {
+ // Value selected with enter, right from the input
+ if (typeof option === 'string') {
+ return option;
+ }
+ // Add "xxx" option created dynamically
+ if (option.inputValue) {
+ return option.inputValue;
+ }
+ // Regular option
+ return option.name;
+ };
+
+ const renderOption = (props, option) => {option.name};
+
+ return (
+
+
+ Context Input
+
+
+ (
+
+ )}
+ />
+
+
+ {/* */}
+
+
+ );
+};
+
+export default AddContextForm;
diff --git a/app/src/components/ContextAPIManager/CreateTab/components/AddDataForm.tsx b/app/src/components/ContextAPIManager/CreateTab/components/AddDataForm.tsx
new file mode 100644
index 000000000..b21439053
--- /dev/null
+++ b/app/src/components/ContextAPIManager/CreateTab/components/AddDataForm.tsx
@@ -0,0 +1,59 @@
+import React, { Fragment, useState, useEffect } from 'react';
+import TextField from '@mui/material/TextField';
+import Button from '@mui/material/Button';
+import Box from '@mui/material/Box';
+import { Typography } from '@mui/material';
+
+const AddDataForm = ({ handleClickInputData, contextInput }) => {
+ //const [contextInput, setContextInput] = React.useState(null);
+ const defaultInputData = {inputKey: '', inputValue: ''};
+ const [dataContext, setDataContext] = React.useState(defaultInputData);
+
+ const saveData = () => {
+ setDataContext(defaultInputData);
+ handleClickInputData(contextInput, dataContext)
+ }
+
+ const handleChange = e => {
+ setDataContext(prevDataContext => {
+ return {
+ ...prevDataContext,
+ [e.target.name]: e.target.value
+ };
+ });
+ };
+
+ return (
+
+
+ Add context data
+
+
+ handleChange(e)}
+ />
+ handleChange(e)}
+ />
+
+
+
+ );
+};
+
+export default AddDataForm;
diff --git a/app/src/components/ContextAPIManager/CreateTab/components/DataTable.tsx b/app/src/components/ContextAPIManager/CreateTab/components/DataTable.tsx
new file mode 100644
index 000000000..790c9fd30
--- /dev/null
+++ b/app/src/components/ContextAPIManager/CreateTab/components/DataTable.tsx
@@ -0,0 +1,68 @@
+import React from 'react';
+import Table from '@mui/material/Table';
+import TableBody from '@mui/material/TableBody';
+import TableContainer from '@mui/material/TableContainer';
+import TableHead from '@mui/material/TableHead';
+import TableRow from '@mui/material/TableRow';
+import Paper from '@mui/material/Paper';
+import { styled } from '@mui/material/styles';
+import TableCell, { tableCellClasses } from '@mui/material/TableCell';
+import TableFooter from '@mui/material/TableFooter';
+
+const StyledTableCell = styled(TableCell)(({ theme }) => ({
+ [`&.${tableCellClasses.head}`]: {
+ backgroundColor: theme.palette.common.black,
+ color: theme.palette.common.white
+ },
+ [`&.${tableCellClasses.body}`]: {
+ fontSize: 14
+ }
+}));
+
+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
+ }
+}));
+
+export default function DataTable({ target, contextInput }) {
+ return (
+ <>
+
+
+
+
+ {/* Key */}
+
+ {contextInput ? contextInput.name : 'Context Name'}
+
+
+
+
+ {target.map((data, index) => (
+
+
+ {data.key}
+
+ {data.value}
+
+ ))}
+
+ {/*
+
+ {contextInput ? contextInput.name : 'Context Name'}
+
+ */}
+
+
+ >
+ );
+}
diff --git a/app/src/components/ContextAPIManager/DisplayTab/DisplayContainer.tsx b/app/src/components/ContextAPIManager/DisplayTab/DisplayContainer.tsx
new file mode 100644
index 000000000..c28cd1016
--- /dev/null
+++ b/app/src/components/ContextAPIManager/DisplayTab/DisplayContainer.tsx
@@ -0,0 +1,47 @@
+import React, { useEffect, useState } from 'react';
+import { useStore } from 'react-redux';
+import { Chart } from 'react-google-charts';
+import Grid from '@mui/material/Grid';
+
+const DisplayContainer = () => {
+ const store = useStore();
+ const { allContext } = store.getState().contextSlice;
+ const [contextData, setContextData] = useState([]);
+
+ //build data for Google charts, tree rendering
+ useEffect(() => {
+ transformData(allContext);
+ }, []);
+
+ const transformData = contexts => {
+ const formattedData = contexts
+ .map(el => {
+ return el.components.map(component => {
+ return [`App ${el.name} ${component}`];
+ });
+ })
+ .flat();
+ setContextData([['Phrases'], ...formattedData]);
+ };
+
+ const options = {
+ wordtree: {
+ format: 'implicit',
+ word: 'App'
+ }
+ };
+ return (
+
+
+
+
+
+ );
+};
+export default DisplayContainer;
diff --git a/app/src/components/bottom/BottomTabs.tsx b/app/src/components/bottom/BottomTabs.tsx
index a392b3225..005b3ee8c 100644
--- a/app/src/components/bottom/BottomTabs.tsx
+++ b/app/src/components/bottom/BottomTabs.tsx
@@ -5,8 +5,9 @@ import Tabs from '@material-ui/core/Tabs';
import Tab from '@material-ui/core/Tab';
import CodePreview from './CodePreview';
import StylesEditor from './StylesEditor';
-import CustomizationPanel from '../../containers/CustomizationPanel'
-import CreationPanel from './CreationPanel'
+import CustomizationPanel from '../../containers/CustomizationPanel';
+import CreationPanel from './CreationPanel';
+import ContextManager from '../ContextAPIManager/ContextManager';
import Box from '@material-ui/core/Box';
import Tree from '../../tree/TreeChart';
import FormControl from '@material-ui/core/FormControl';
@@ -43,8 +44,17 @@ const BottomTabs = (props): JSX.Element => {
Arrow.renderArrow(state.canvasFocus.childId);
return (
-
-
+
+
{
classes={{ root: classes.tabRoot, selected: classes.tabSelected }}
label="Component Tree"
/>
+
-
-
+
+
@@ -101,6 +122,7 @@ const BottomTabs = (props): JSX.Element => {
{tab === 2 && }
{tab === 3 && }
{tab === 4 && }
+ {tab === 5 && }
);
};
@@ -110,8 +132,7 @@ const useStyles = makeStyles(theme => ({
flexGrow: 1,
height: '100%',
color: '#E8E8E8',
- boxShadow: '0 3px 6px rgba(0,0,0,0.16), 0 3px 6px rgba(0,0,0,0.23)',
-
+ boxShadow: '0 3px 6px rgba(0,0,0,0.16), 0 3px 6px rgba(0,0,0,0.23)'
},
rootLight: {
backgroundColor: '#003366'
@@ -126,7 +147,7 @@ const useStyles = makeStyles(theme => ({
minHeight: '50%'
},
tabsIndicator: {
- backgroundColor: 'white',
+ backgroundColor: 'white'
},
tabRoot: {
textTransform: 'initial',
@@ -155,7 +176,7 @@ const useStyles = makeStyles(theme => ({
fontWeight: theme.typography.fontWeightMedium
},
'&:focus': {
- color: 'white',
+ color: 'white'
}
},
tabSelected: {},
@@ -168,7 +189,7 @@ const useStyles = makeStyles(theme => ({
switch: {
marginRight: '10px',
marginTop: '2px'
- },
+ },
projectTypeWrapper: {
marginTop: '10px',
marginBotton: '10px'
@@ -180,4 +201,3 @@ const useStyles = makeStyles(theme => ({
}));
export default BottomTabs;
-
diff --git a/app/src/components/bottom/CodePreview.tsx b/app/src/components/bottom/CodePreview.tsx
index 458f01460..c917d2bcb 100644
--- a/app/src/components/bottom/CodePreview.tsx
+++ b/app/src/components/bottom/CodePreview.tsx
@@ -19,27 +19,24 @@ import store from '../../redux/store';
const CodePreview: React.FC<{
theme: string | null;
setTheme: any | null;
- }> = ({ theme, setTheme }) => {
-
-
+}> = ({ theme, setTheme }) => {
const ref = useRef();
-
+
/**
- * Starts the Web Assembly service.
- */
+ * Starts the Web Assembly service.
+ */
const startService = async () => {
ref.current = await esbuild.startService({
worker: true,
- wasmURL: 'https://unpkg.com/esbuild-wasm@0.8.27/esbuild.wasm',
- })
- }
+ wasmURL: 'https://unpkg.com/esbuild-wasm@0.8.27/esbuild.wasm'
+ });
+ };
const wrapper = useRef();
const dimensions = useResizeObserver(wrapper);
- const {height } =
- dimensions || 0;
+ const { height } = dimensions || 0;
- const [state,] = useContext(StateContext);
+ const [state] = useContext(StateContext);
const [, setDivHeight] = useState(0);
let currentComponent = state.components.find(
(elem: Component) => elem.id === state.canvasFocus.componentId
@@ -53,49 +50,51 @@ const CodePreview: React.FC<{
useEffect(() => {
setDivHeight(height);
- }, [height])
+ }, [height]);
useEffect(() => {
-
- setInput(currentComponent.code);
- store.dispatch({type: "CODE_PREVIEW_INPUT", payload: currentComponent.code});
- }, [state.components])
+ setInput(currentComponent.code);
+ store.dispatch({
+ type: 'CODE_PREVIEW_INPUT',
+ payload: currentComponent.code
+ });
+ }, [state.components]);
/**
- * Handler thats listens to changes in code editor
- * @param {string} data - Code entered by the user
- */
- const handleChange = async (data) => {
+ * Handler thats listens to changes in code editor
+ * @param {string} data - Code entered by the user
+ */
+ const handleChange = async data => {
setInput(data);
- store.dispatch({type: "CODE_PREVIEW_INPUT", payload: data});
- if(!ref.current) {
+ store.dispatch({ type: 'CODE_PREVIEW_INPUT', payload: data });
+ if (!ref.current) {
return;
}
let result = await ref.current.build({
entryPoints: ['index.js'],
bundle: true,
write: false,
- incremental:true,
+ incremental: true,
minify: true,
- plugins: [
- unpkgPathPlugin(),
- fetchPlugin(data)
- ],
+ plugins: [unpkgPathPlugin(), fetchPlugin(data)],
define: {
'process.env.NODE_ENV': '"production"',
global: 'window'
}
- })
- store.dispatch({type: "CODE_PREVIEW_SAVE", payload: result.outputFiles[0].text});
- }
+ });
+ store.dispatch({
+ type: 'CODE_PREVIEW_SAVE',
+ payload: result.outputFiles[0].text
+ });
+ };
return (
);
};
export default CodePreview;
-
-
-
-
diff --git a/app/src/components/bottom/CreationPanel.tsx b/app/src/components/bottom/CreationPanel.tsx
index 6b5c55bd8..bd1547edb 100644
--- a/app/src/components/bottom/CreationPanel.tsx
+++ b/app/src/components/bottom/CreationPanel.tsx
@@ -10,6 +10,7 @@ const CreationPanel = (props): JSX.Element => {
const {style} = useContext(styleContext);
return (
+
diff --git a/app/src/components/login/FBPassWord.tsx b/app/src/components/login/FBPassWord.tsx
index d19dc7861..6601e9e3b 100644
--- a/app/src/components/login/FBPassWord.tsx
+++ b/app/src/components/login/FBPassWord.tsx
@@ -195,7 +195,7 @@ const SignUp: React.FC = props => {
>
Sign Up
+
-
+
Already have an account? Sign In
diff --git a/app/src/components/login/SignUp.tsx b/app/src/components/login/SignUp.tsx
index f0de334aa..ef657e855 100644
--- a/app/src/components/login/SignUp.tsx
+++ b/app/src/components/login/SignUp.tsx
@@ -310,7 +310,7 @@ const SignUp: React.FC = props => {
>
Sign Up
-
+
Already have an account? Sign In
diff --git a/app/src/components/main/DemoRender.tsx b/app/src/components/main/DemoRender.tsx
index 3635215a2..4df4bcd49 100644
--- a/app/src/components/main/DemoRender.tsx
+++ b/app/src/components/main/DemoRender.tsx
@@ -1,5 +1,5 @@
import React, { useState, useEffect, useRef, useContext } from 'react';
-import { BrowserRouter as Router, Switch, Route, Link } from "react-router-dom";
+import { BrowserRouter as Router, Switch, Route, Link } from 'react-router-dom';
import Box from '@material-ui/core/Box';
import cssRefresher from '../../helperFunctions/cssRefresh';
import { useSelector } from 'react-redux';
@@ -17,12 +17,12 @@ const DemoRender = (): JSX.Element => {
(elem: Component) => elem.id === state.canvasFocus.componentId
);
- // Create React ref to inject transpiled code in inframe
+ // Create React ref to inject transpiled code in inframe
const iframe = useRef();
const demoContainerStyle = {
width: '100%',
backgroundColor: '#FBFBFB',
- border: '2px Solid grey',
+ border: '2px Solid grey'
};
const html = `
@@ -55,14 +55,19 @@ const DemoRender = (): JSX.Element => {
`;
//Switch between components when clicking on a link in the live render
- window.onmessage = (event) => {
- if(event.data === undefined) return;
- const component:string = event.data?.split('/').at(-1);
- const componentId = component && state.components?.find((el) => {
- return el.name.toLowerCase() === component.toLowerCase();
- }).id;
- componentId && dispatch({ type: 'CHANGE FOCUS', payload: {componentId, childId: null}})
-
+ window.onmessage = event => {
+ if (event.data === undefined) return;
+ const component: string = event.data?.split('/').at(-1);
+ const componentId =
+ component &&
+ state.components?.find(el => {
+ return el.name.toLowerCase() === component.toLowerCase();
+ }).id;
+ componentId &&
+ dispatch({
+ type: 'CHANGE FOCUS',
+ payload: { componentId, childId: null }
+ });
};
// This function is the heart of DemoRender it will take the array of components stored in state and dynamically construct the desired React component for the live demo
@@ -78,17 +83,81 @@ const DemoRender = (): JSX.Element => {
const classRender = element.attributes.cssClasses;
const activeLink = element.attributes.compLink;
let renderedChildren;
- if (elementType !== 'input' && elementType !== 'img' && elementType !== 'Image' && element.children.length > 0) {
+ if (
+ elementType !== 'input' &&
+ elementType !== 'img' &&
+ elementType !== 'Image' &&
+ element.children.length > 0
+ ) {
renderedChildren = componentBuilder(element.children);
}
- if (elementType === 'input') componentsToRender.push();
- else if (elementType === 'img') componentsToRender.push();
- else if (elementType === 'Image') componentsToRender.push();
- else if (elementType === 'a' || elementType === 'Link') componentsToRender.push({innerText}{renderedChildren});
- else if (elementType === 'Switch') componentsToRender.push({renderedChildren});
- else if (elementType === 'Route') componentsToRender.push({renderedChildren});
- else componentsToRender.push({innerText}{renderedChildren}
- );
+ if (elementType === 'input')
+ componentsToRender.push(
+
+ );
+ else if (elementType === 'img')
+ componentsToRender.push(
+
+ );
+ else if (elementType === 'Image')
+ componentsToRender.push(
+
+ );
+ else if (elementType === 'a' || elementType === 'Link')
+ componentsToRender.push(
+
+ {innerText}
+ {renderedChildren}
+
+ );
+ else if (elementType === 'Switch')
+ componentsToRender.push({renderedChildren});
+ else if (elementType === 'Route')
+ componentsToRender.push(
+
+ {renderedChildren}
+
+ );
+ else
+ componentsToRender.push(
+
+ {innerText}
+ {renderedChildren}
+
+ );
key += 1;
}
}
@@ -96,10 +165,12 @@ const DemoRender = (): JSX.Element => {
};
let code = '';
- const currComponent = state.components.find(element => element.id === state.canvasFocus.componentId);
+ const currComponent = state.components.find(
+ element => element.id === state.canvasFocus.componentId
+ );
componentBuilder(currComponent.children).forEach(element => {
- try{
+ try {
code += ReactDOMServer.renderToString(element);
} catch {
return;
@@ -108,17 +179,23 @@ const DemoRender = (): JSX.Element => {
useEffect(() => {
cssRefresher();
- }, [])
+ }, []);
useEffect(() => {
iframe.current.contentWindow.postMessage(code, '*');
- }, [code])
+ }, [code]);
return (
-
+
);
};
-export default DemoRender;
\ No newline at end of file
+export default DemoRender;
diff --git a/app/src/components/right/ComponentDrag.tsx b/app/src/components/right/ComponentDrag.tsx
index 5326d4503..f87ddc105 100644
--- a/app/src/components/right/ComponentDrag.tsx
+++ b/app/src/components/right/ComponentDrag.tsx
@@ -19,7 +19,7 @@ const ComponentDrag = ({ isThemeLight }): JSX.Element => {
{/* Heading just below ADD button */}
{state.projectType === 'Next.js' || state.projectType === 'Gatsby.js' ? 'Pages' : 'Root Components'}
-
+
{state.components
.filter(comp => state.rootComponents.includes(comp.id))
.map(comp => {
@@ -38,7 +38,7 @@ const ComponentDrag = ({ isThemeLight }): JSX.Element => {
{/* Display all reusable components */}
Reusable Components
-
+
{state.components
.filter(comp => !state.rootComponents.includes(comp.id))
.map(comp => {
@@ -61,7 +61,7 @@ const ComponentDrag = ({ isThemeLight }): JSX.Element => {
diff --git a/app/src/components/right/ContextMenu.tsx b/app/src/components/right/ContextMenu.tsx
new file mode 100644
index 000000000..8ff22d78c
--- /dev/null
+++ b/app/src/components/right/ContextMenu.tsx
@@ -0,0 +1,14 @@
+/* imput component
+input component
+button component
+*/
+import React from 'react';
+
+
+const ContextMenu = () => {
+ return (
+ This is from context menu
+ )
+}
+
+export default ContextMenu;
\ No newline at end of file
diff --git a/app/src/components/right/ExportButton.tsx b/app/src/components/right/ExportButton.tsx
index 879bbcc22..ddc3f4e1a 100644
--- a/app/src/components/right/ExportButton.tsx
+++ b/app/src/components/right/ExportButton.tsx
@@ -1,4 +1,3 @@
-
import React, { useState, useContext, useCallback, useEffect } from 'react';
import StateContext from '../../context/context';
import List from '@material-ui/core/List';
@@ -11,7 +10,7 @@ import createModal from './createModal';
import { styleContext } from '../../containers/AppContainer';
export default function ExportButton() {
const [modal, setModal] = useState(null);
- const [state,] = useContext(StateContext);
+ const [state] = useContext(StateContext);
const genOptions: string[] = [
'Export components',
@@ -22,7 +21,6 @@ export default function ExportButton() {
// Closes out the open modal
const closeModal = () => setModal('');
-
const showGenerateAppModal = () => {
const children = (
@@ -92,29 +90,31 @@ export default function ExportButton() {
);
};
- const exportKeyBind = useCallback((e) => {
+ const exportKeyBind = useCallback(e => {
//Export Project
- (e.key === 'e' && e.metaKey || e.key === 'e' && e.ctrlKey ) ? showGenerateAppModal() : '';
+ (e.key === 'e' && e.metaKey) || (e.key === 'e' && e.ctrlKey)
+ ? showGenerateAppModal()
+ : '';
}, []);
useEffect(() => {
document.addEventListener('keydown', exportKeyBind);
return () => {
- document.removeEventListener('keydown', exportKeyBind)
- }
+ document.removeEventListener('keydown', exportKeyBind);
+ };
}, []);
return (
-
- {modal}
+ }
+ >
+ EXPORT
+
+ {modal}
);
-};
\ No newline at end of file
+}
diff --git a/app/src/helperFunctions/generateCode.ts b/app/src/helperFunctions/generateCode.ts
index 9104330a7..bae579f9b 100644
--- a/app/src/helperFunctions/generateCode.ts
+++ b/app/src/helperFunctions/generateCode.ts
@@ -1,4 +1,5 @@
import { element } from 'prop-types';
+import store from '../redux/store.js';
import {
Component,
State,
@@ -85,12 +86,13 @@ const generateUnformattedCode = (
}
// when we see a Switch or LinkTo, import React Router
- if ((referencedHTML.tag === 'Switch' || referencedHTML.tag === 'Route') && projectType === 'Classic React')
+ if (
+ (referencedHTML.tag === 'Switch' || referencedHTML.tag === 'Route') &&
+ projectType === 'Classic React'
+ )
importReactRouter = true;
- else if(referencedHTML.tag === 'Link')
- links = true;
- if(referencedHTML.tag === 'Image')
- images = true;
+ else if (referencedHTML.tag === 'Link') links = true;
+ if (referencedHTML.tag === 'Image') images = true;
return child;
} else if (child.type === 'Route Link') {
links = true;
@@ -118,25 +120,28 @@ const generateUnformattedCode = (
};
// function to dynamically add classes, ids, and styles to an element if it exists.
const elementTagDetails = (childElement: object) => {
- let customizationDetails = "";
- if (childElement.childId && childElement.tag !== 'Route') customizationDetails += (' ' + `id="${+childElement.childId}"`);
+ let customizationDetails = '';
+ if (childElement.childId && childElement.tag !== 'Route')
+ customizationDetails += ' ' + `id="${+childElement.childId}"`;
if (childElement.attributes && childElement.attributes.cssClasses) {
- customizationDetails += (' ' + `className="${childElement.attributes.cssClasses}"`);
+ customizationDetails +=
+ ' ' + `className="${childElement.attributes.cssClasses}"`;
}
- if (childElement.style && Object.keys(childElement.style).length > 0) customizationDetails += (' ' + formatStyles(childElement));
+ if (childElement.style && Object.keys(childElement.style).length > 0)
+ customizationDetails += ' ' + formatStyles(childElement);
return customizationDetails;
};
// function to fix the spacing of the ace editor for new lines of added content. This was breaking on nested components, leaving everything right justified.
const tabSpacer = (level: number) => {
- let tabs = ' '
+ let tabs = ' ';
for (let i = 0; i < level; i++) tabs += ' ';
return tabs;
- }
+ };
// function to dynamically generate the appropriate levels for the code preview
const levelSpacer = (level: number, spaces: number) => {
if (level === 2) return `\n${tabSpacer(spaces)}`;
- else return ''
- }
+ else return '';
+ };
// function to dynamically generate a complete html (& also other library type) elements
const elementGenerator = (childElement: object, level: number = 2) => {
let innerText = '';
@@ -155,7 +160,8 @@ const generateUnformattedCode = (
activeLink = '"' + childElement.attributes.compLink + '"';
}
}
- const nestable = childElement.tag === 'div' ||
+ const nestable =
+ childElement.tag === 'div' ||
childElement.tag === 'form' ||
childElement.tag === 'ol' ||
childElement.tag === 'ul' ||
@@ -165,34 +171,72 @@ const generateUnformattedCode = (
childElement.tag === 'Route';
if (childElement.tag === 'img') {
- return `${levelSpacer(level, 5)}<${childElement.tag} src=${activeLink} ${elementTagDetails(childElement)}/>${levelSpacer(2, (3 + level))}`;
+ return `${levelSpacer(level, 5)}<${
+ childElement.tag
+ } src=${activeLink} ${elementTagDetails(childElement)}/>${levelSpacer(
+ 2,
+ 3 + level
+ )}`;
} else if (childElement.tag === 'a') {
- return `${levelSpacer(level, 5)}<${childElement.tag} href=${activeLink} ${elementTagDetails(childElement)}>${innerText}${childElement.tag}>${levelSpacer(2, (3 + level))}`;
+ return `${levelSpacer(level, 5)}<${
+ childElement.tag
+ } href=${activeLink} ${elementTagDetails(childElement)}>${innerText}${
+ childElement.tag
+ }>${levelSpacer(2, 3 + level)}`;
} else if (childElement.tag === 'input') {
- return `${levelSpacer(level, 5)}<${childElement.tag}${elementTagDetails(childElement)}>${childElement.tag}>${levelSpacer(2, (3 + level))}`;
+ return `${levelSpacer(level, 5)}<${childElement.tag}${elementTagDetails(
+ childElement
+ )}>${childElement.tag}>${levelSpacer(2, 3 + level)}`;
} else if (childElement.tag === 'Link' && projectType === 'Classic React') {
- return `${levelSpacer(level, 5)}
- ${tabSpacer(level)}${writeNestedElements(childElement.children, level + 1)}${innerText}
- ${tabSpacer(level - 1)}${levelSpacer(2, (3 + level))}`;
+ return `${levelSpacer(level, 5)}
+ ${tabSpacer(level)}${writeNestedElements(
+ childElement.children,
+ level + 1
+ )}${innerText}
+ ${tabSpacer(level - 1)}${levelSpacer(2, 3 + level)}`;
} else if (childElement.tag === 'Link' && projectType === 'Next.js') {
- return `${levelSpacer(level, 5)}
- ${tabSpacer(level)}${innerText}${writeNestedElements(childElement.children, level + 1)}
- ${tabSpacer(level - 1)}${levelSpacer(2, (3 + level))}`;
+ return `${levelSpacer(
+ level,
+ 5
+ )}
+ ${tabSpacer(level)}${innerText}${writeNestedElements(
+ childElement.children,
+ level + 1
+ )}
+ ${tabSpacer(level - 1)}${levelSpacer(2, 3 + level)}`;
} else if (childElement.tag === 'Image') {
- return `${levelSpacer(level, 5)}<${childElement.tag} src=${activeLink} ${elementTagDetails(childElement)}/>`;
+ return `${levelSpacer(level, 5)}<${
+ childElement.tag
+ } src=${activeLink} ${elementTagDetails(childElement)}/>`;
} else if (nestable) {
- if((childElement.tag === 'Route' || childElement.tag === 'Switch') && projectType === 'Next.js') {
+ if (
+ (childElement.tag === 'Route' || childElement.tag === 'Switch') &&
+ projectType === 'Next.js'
+ ) {
return `${writeNestedElements(childElement.children, level)}`;
- }
- const routePath = (childElement.tag === 'Route') ? (' ' + 'exact path=' + activeLink) : '';
- return `${levelSpacer(level, 5)}<${childElement.tag}${elementTagDetails(childElement)}${routePath}>
+ }
+ const routePath =
+ childElement.tag === 'Route' ? ' ' + 'exact path=' + activeLink : '';
+ return `${levelSpacer(level, 5)}<${childElement.tag}${elementTagDetails(
+ childElement
+ )}${routePath}>
${tabSpacer(level)}${innerText}
- ${tabSpacer(level)}${writeNestedElements(childElement.children, level + 1)}
- ${tabSpacer(level - 1)}${childElement.tag}>${levelSpacer(2, (3 + level))}`;
+ ${tabSpacer(level)}${writeNestedElements(
+ childElement.children,
+ level + 1
+ )}
+ ${tabSpacer(level - 1)}${childElement.tag}>${levelSpacer(
+ 2,
+ 3 + level
+ )}`;
} else if (childElement.tag !== 'separator') {
- return `${levelSpacer(level, 5)}<${childElement.tag}${elementTagDetails(childElement)}>${innerText}${childElement.tag}>${levelSpacer(2, (3 + level))}`;
+ return `${levelSpacer(level, 5)}<${childElement.tag}${elementTagDetails(
+ childElement
+ )}>${innerText}${childElement.tag}>${levelSpacer(2, 3 + level)}`;
}
- }
+ };
// write all code that will be under the "return" of the component
const writeNestedElements = (enrichedChildren: any, level: number = 2) => {
return `${enrichedChildren
@@ -206,50 +250,53 @@ const generateUnformattedCode = (
else if (child.type === 'Route Link') {
if (projectType === 'Next.js') {
// if route link points to index, to go endpoint / rather than /index
- if (child.name === 'index') return ``;
- else return ``;
+ if (child.name === 'index')
+ return ``;
+ else
+ return ``;
} else if (projectType === 'Gatsby.js') {
- if (child.name === 'index') return `${child.name}
`;
- else return `${child.name}
`;
- } else return ``
+ if (child.name === 'index')
+ return `${child.name}
`;
+ else
+ return `${child.name}
`;
+ } else return ``;
}
})
.filter(element => !!element)
- .join('')
- }`;
+ .join('')}`;
};
// function to properly incorporate the user created state that is stored in the application state
const writeStateProps = (stateArray: any) => {
let stateToRender = '';
for (const element of stateArray) {
- stateToRender += levelSpacer(2, 2) + element + ';'
+ stateToRender += levelSpacer(2, 2) + element + ';';
}
- return stateToRender
- }
+ return stateToRender;
+ };
const enrichedChildren: any = getEnrichedChildren(currComponent);
// import statements differ between root (pages) and regular components (components)
const importsMapped =
projectType === 'Next.js' || projectType === 'Gatsby.js'
? imports
- .map((comp: string) => {
- return isRoot
- ? `import ${comp} from '../components/${comp}'`
- : `import ${comp} from './${comp}'`;
- })
- .join('\n')
+ .map((comp: string) => {
+ return isRoot
+ ? `import ${comp} from '../components/${comp}'`
+ : `import ${comp} from './${comp}'`;
+ })
+ .join('\n')
: imports
- .map((comp: string) => {
- return `import ${comp} from './${comp}'`;
- })
- .join('\n');
- const createState = (stateProps) => {
+ .map((comp: string) => {
+ return `import ${comp} from './${comp}'`;
+ })
+ .join('\n');
+ const createState = stateProps => {
let state = '{';
- stateProps.forEach((ele) => {
+ stateProps.forEach(ele => {
state += ele.key + ':' + JSON.stringify(ele.value) + ', ';
});
state = state.substring(0, state.length - 2) + '}';
return state;
- }
+ };
// Generate import
let importContext = '';
if (currComponent.useContext) {
@@ -260,9 +307,15 @@ const generateUnformattedCode = (
}
if (currComponent.useContext) {
for (const providerId of Object.keys(currComponent.useContext)) {
- const statesFromProvider = currComponent.useContext[parseInt(providerId)].statesFromProvider; //{1: {Set, compLink, compText}, 2 : {}...}
+ const statesFromProvider =
+ currComponent.useContext[parseInt(providerId)].statesFromProvider; //{1: {Set, compLink, compText}, 2 : {}...}
const providerComponent = components[parseInt(providerId) - 1];
- providers += 'const ' + providerComponent.name.toLowerCase() + 'Context = useContext(' + providerComponent.name + 'Context);\n \t\t';
+ providers +=
+ 'const ' +
+ providerComponent.name.toLowerCase() +
+ 'Context = useContext(' +
+ providerComponent.name +
+ 'Context);\n \t\t';
for (let i = 0; i < providerComponent.stateProps.length; i++) {
if (statesFromProvider.has(providerComponent.stateProps[i].id)) {
context +=
@@ -280,28 +333,92 @@ const generateUnformattedCode = (
// create final component code. component code differs between classic react, next.js, gatsby.js
// classic react code
if (projectType === 'Classic React') {
+ //string to store all imports string for context
+ let contextImports = '';
+
+ const { allContext } = store.getState().contextSlice;
+
+ for (const context of allContext) {
+ contextImports += `import ${context.name}Provider from '../contexts/${context.name}.js'\n\t\t`;
+ }
+
+ //build an object with keys representing all components, their values are arrays storing all contexts that those components are consuming
+ const componentContext = allContext.reduce((acc, curr) => {
+ for (const component of curr.components) {
+ if (acc[component] === undefined) acc[component] = [];
+ acc[component].push(curr.name);
+ }
+ return acc;
+ }, {});
+
+ //return a string with all contexts provider in component's body
+ const createRender = () => {
+ let result = `${writeNestedElements(enrichedChildren)}`;
+ if (importReactRouter) result = `\n ${result}\n `;
+ if (allContext.length < 1) return result;
+
+ if (currComponent.name === 'App') {
+ allContext.reverse().forEach((el, i) => {
+ let tabs = `\t\t\t`;
+ if (i === allContext.length - 1) {
+ tabs = `\t\t\t\t`;
+ }
+ result = `${tabs.repeat(allContext.length - i)}<${
+ el.name
+ }Provider>\n ${result}\n ${tabs.repeat(allContext.length - i)}${
+ el.name
+ }Provider>`;
+ });
+ }
+ return result;
+ };
+
+ //decide which imports statements to use for which components
+ const createContextImport = () => {
+ if (!(currComponent.name in componentContext)) return '';
+
+ let importStr = '';
+ componentContext[currComponent.name].forEach(context => {
+ importStr += `import { ${context} } from '../contexts/${context}.js'\n\t\t`;
+ });
+
+ return importStr;
+ };
+
+ //call use context hooks for components that are consuming contexts
+ const createUseContextHook = () => {
+ if (!(currComponent.name in componentContext)) return '';
+
+ let importStr = '';
+ componentContext[currComponent.name].forEach(context => {
+ importStr += ` const [${context}Val] = useContext(${context})\n\t\t`;
+ });
+
+ return importStr;
+ };
return `
- ${`import React, { useState, useEffect} from 'react';`}
+ ${`import React, { useState, useEffect, useContext} from 'react';`}
${`import ReactDOM from 'react-dom';`}
- ${importReactRouter ? `import { BrowserRouter as Router, Route, Switch, Link } from 'react-router-dom';` : ``}
+ ${currComponent.name === 'App' ? contextImports : ''}
+ ${
+ importReactRouter
+ ? `import { BrowserRouter as Router, Route, Switch, Link } from 'react-router-dom';`
+ : ``
+ }
+ ${createContextImport()}
${importsMapped}
${`const ${currComponent.name} = (props) => {`}
- ${` const [value, setValue] = useState("");${writeStateProps(currComponent.useStateCodes)}`}
- ${!importReactRouter
- ? ` return (
- <>
- \t${writeNestedElements(enrichedChildren)}
- >
- );`
- : ` return (
-
- \t${writeNestedElements(enrichedChildren)}
-
- );`}
+ ${createUseContextHook()}
+ ${` const [value, setValue] = useState("");${writeStateProps(
+ currComponent.useStateCodes
+ )}`}
+
+ return(\n${createRender()}\n\t\t\t)
${`}\n`}
- ReactDOM.render(<${currComponent.name} />, document.querySelector('#app'));
+ export default ${currComponent.name}
`;
}
+ //
// next.js component code
else if (projectType === 'Next.js') {
return `
@@ -311,22 +428,25 @@ const generateUnformattedCode = (
${links ? `import Link from 'next/link'` : ``}
${images ? `import Image from 'next/image'` : ``}
- const ${currComponent.name[0].toUpperCase() + currComponent.name.slice(1)} = (props): JSX.Element => {
+ const ${currComponent.name[0].toUpperCase() +
+ currComponent.name.slice(1)} = (props): JSX.Element => {
const [value, setValue] = useState("INITIAL VALUE");
return (
<>
- ${isRoot
- ? `
+ ${
+ isRoot
+ ? `
${currComponent.name}
`
- : ``
+ : ``
}
${writeNestedElements(enrichedChildren)}
>
);
}
- export default ${currComponent.name[0].toUpperCase() + currComponent.name.slice(1)};
+ export default ${currComponent.name[0].toUpperCase() +
+ currComponent.name.slice(1)};
`;
} else {
// gatsby component code
@@ -339,12 +459,13 @@ const generateUnformattedCode = (
const[value, setValue] = useState("INITIAL VALUE");
return (
<>
- ${isRoot
- ? `
+ ${
+ isRoot
+ ? `
${currComponent.name}
`
- : ``
- }
+ : ``
+ }
${writeNestedElements(enrichedChildren)}
diff --git a/app/src/redux/actions/actions.js b/app/src/redux/actions/actions.js
index 5ecd38acf..2a25b0e96 100644
--- a/app/src/redux/actions/actions.js
+++ b/app/src/redux/actions/actions.js
@@ -3,3 +3,28 @@ import * as types from '../constants/actionTypes';
export const darkModeToggle = () => ({
type: types.DARK_MODE_TOGGLE
});
+
+//actions for context slice
+export const addContextActionCreator = contextName => ({
+ type: types.ADD_CONTEXT,
+ payload: contextName
+});
+
+export const addContextValuesActionCreator = newEntry => ({
+ type: types.ADD_CONTEXT_VALUES,
+ payload: newEntry
+});
+
+export const addComponentToContext = newEntry => ({
+ type: types.ADD_COMPONENT_TO_CONTEXT,
+ payload: newEntry
+});
+
+export const deleteContext = contextInput => ({
+ type: types.DELETE_CONTEXT,
+ payload: contextInput
+});
+
+export const getAllContext = () => ({
+ type: types.GET_ALL_CONTEXT
+});
diff --git a/app/src/redux/constants/actionTypes.js b/app/src/redux/constants/actionTypes.js
index fcfaba3f7..391beeb92 100644
--- a/app/src/redux/constants/actionTypes.js
+++ b/app/src/redux/constants/actionTypes.js
@@ -2,3 +2,8 @@
export const DARK_MODE_TOGGLE = 'DARK_MODE_TOGGLE';
export const CODE_PREVIEW_SAVE = 'CODE_PREVIEW_SAVE';
export const CODE_PREVIEW_INPUT = 'CODE_PREVIEW_INPUT';
+export const ADD_CONTEXT = 'ADD_CONTEXT';
+export const ADD_CONTEXT_VALUES = 'ADD_CONTEXT_VALUES';
+export const ADD_COMPONENT_TO_CONTEXT = 'ADD_COMPONENT_TO_CONTEXT';
+export const DELETE_CONTEXT = 'DELETE_CONTEXT';
+export const GET_ALL_CONTEXT = 'GET_ALL_CONTEXT';
diff --git a/app/src/redux/reducers/rootReducer.js b/app/src/redux/reducers/rootReducer.js
index 275e9591a..dcb46eac9 100644
--- a/app/src/redux/reducers/rootReducer.js
+++ b/app/src/redux/reducers/rootReducer.js
@@ -2,11 +2,13 @@ import { combineReducers } from 'redux';
import darkModeReducer from './slice/darkModeSlice';
import codePreviewReducer from './slice/codePreviewSlice';
+import contextReducer from './slice/contextReducer';
const rootReducer = combineReducers({
darkModeSlice: darkModeReducer,
- codePreviewSlice: codePreviewReducer
+ codePreviewSlice: codePreviewReducer,
// add the rest of your slice imports here
+ contextSlice: contextReducer,
});
export default rootReducer;
diff --git a/app/src/redux/reducers/slice/contextReducer.js b/app/src/redux/reducers/slice/contextReducer.js
new file mode 100644
index 000000000..7b1b2ef50
--- /dev/null
+++ b/app/src/redux/reducers/slice/contextReducer.js
@@ -0,0 +1,91 @@
+import * as types from '../../constants/actionTypes';
+
+const initialState = {
+ //mock data for context slice commented out
+ allContext: [
+ // {
+ // name: 'FirstContext',
+ // values: [
+ // { key: 'theme', value: 'testValue1' },
+ // { key: 'navbar', value: 'testValue2' }
+ // ],
+ // components: ['MainContainer', 'SubmitForm']
+ // },
+ // {
+ // name: 'ContextExample2',
+ // values: [
+ // { key: 'header', value: 'testValue3' },
+ // { key: 'footer ', value: 'testValue33' }
+ // ],
+ // components: ['MainContainer', 'EditForm', 'TableContainer']
+ // }
+ ]
+};
+
+const contextReducer = (state = initialState, action) => {
+ switch (action.type) {
+ case types.ADD_CONTEXT:
+ let newName = action.payload.name.trim();
+ newName = newName.charAt(0).toUpperCase() + newName.slice(1);
+ const newContext = {
+ name: newName,
+ values: [],
+ components: []
+ };
+
+ return {
+ ...state,
+ allContext: [...state.allContext, newContext]
+ };
+
+ case types.ADD_CONTEXT_VALUES:
+ const newAllContext = [...state.allContext];
+
+ for (let i = 0; i < newAllContext.length; i += 1) {
+ if (newAllContext[i].name === action.payload.name) {
+ newAllContext[i].values.push({
+ key: action.payload.inputKey,
+ value: action.payload.inputValue
+ });
+ }
+ }
+
+ return {
+ ...state,
+ allContext: newAllContext
+ };
+
+ case types.DELETE_CONTEXT:
+ const tempState = [...state.allContext];
+ const remains = tempState.filter(el => el.name !== action.payload.name);
+
+ return {
+ ...state,
+ allContext: remains
+ };
+
+ case types.ADD_COMPONENT_TO_CONTEXT:
+ const newTempState = [...state.allContext];
+
+ for (let i = 0; i < newTempState.length; i += 1) {
+ if (newTempState[i].name === action.payload.context.name) {
+ newTempState[i].components.push(action.payload.component.name);
+ }
+ }
+
+ return {
+ ...state,
+ allContext: newTempState
+ };
+
+ case types.GET_ALL_CONTEXT:
+ return {
+ ...state
+ };
+ default: {
+ return state;
+ }
+ }
+};
+
+export default contextReducer;
diff --git a/app/src/utils/createApplication.util.ts b/app/src/utils/createApplication.util.ts
index 9fa22cd5a..0664d9097 100644
--- a/app/src/utils/createApplication.util.ts
+++ b/app/src/utils/createApplication.util.ts
@@ -1,8 +1,10 @@
// Create all files necessary to run a classic react application
import createFiles from './createFiles.util';
-import { Component} from '../interfaces/Interfaces';
-import createTestSuiteClassic from './createTestSuiteClassic.util'
-const camelToKebab= (camel:string) => {
+import { Component } from '../interfaces/Interfaces';
+import createTestSuiteClassic from './createTestSuiteClassic.util';
+import store from '../redux/store.js';
+
+const camelToKebab = (camel: string) => {
return camel.replace(/([a-z0-9]|(?=[A-Z]))([A-Z])/g, '$1-$2').toLowerCase();
};
const compToCSS = (component: Component) => {
@@ -15,16 +17,17 @@ const compToCSS = (component: Component) => {
let cssStyle = `${camelToKebab(property)}: ${styleObj[property]};
`;
cssClass += cssStyle;
- })
+ });
cssClass += `}
`;
return cssClass;
-}
+};
function createIndexHtml(path, appName) {
let dir = path;
let dirSrc;
let dirServer;
let dirComponent;
+ let dirContext;
if (!dir.match(/`${appName}`|\*$/)) {
dir = `${dir}/${appName}`;
if (!window.api.existsSync(dir)) {
@@ -35,6 +38,9 @@ function createIndexHtml(path, appName) {
window.api.mkdirSync(dirServer);
dirComponent = `${dirSrc}/components`;
window.api.mkdirSync(dirComponent);
+ //create directory for contexts
+ dirContext = `${dirSrc}/contexts`;
+ window.api.mkdirSync(dirContext);
}
}
const filePath: string = `${dir}/index.html`;
@@ -90,7 +96,7 @@ export const createDefaultCSS = (path, appName, components) => {
`;
components.forEach(comp => {
data += compToCSS(comp);
- })
+ });
window.api.writeFile(filePath, data, err => {
if (err) {
console.log('default.css error:', err.message);
@@ -101,7 +107,7 @@ export const createDefaultCSS = (path, appName, components) => {
};
export const createPackage = (path, appName, test) => {
const filePath = `${path}/${appName}/package.json`;
- let tsjest = `,
+ let tsjest = `,
"@types/enzyme": "^3.10.9",
"@types/jest": "^27.0.1",
"babel-jest": "^27.2.0",
@@ -121,8 +127,11 @@ export const createPackage = (path, appName, test) => {
"start": "node server/server.js",
"build": "cross-env NODE_ENV=production webpack",
"dev": "cross-env NODE_ENV=development webpack-dev-server"${
- test ? `,
- "test": "jest"`: '' }
+ test
+ ? `,
+ "test": "jest"`
+ : ''
+ }
},
"nodemonConfig": {
"ignore": [
@@ -163,8 +172,7 @@ export const createPackage = (path, appName, test) => {
"typescript": "^3.8.3",
"webpack": "^4.29.6",
"webpack-cli": "^3.3.0",
- "webpack-dev-server": "^3.2.1"${
- test ? tsjest : '' }
+ "webpack-dev-server": "^3.2.1"${test ? tsjest : ''}
}
}
`;
@@ -329,11 +337,49 @@ app.listen(8080, () => {
}
});
};
+
+//Generate files for all existing contexts in the current application
+export const createContext = (path, appName) => {
+ // const store = useStore();
+ const { allContext } = store.getState().contextSlice;
+
+ for (const context of allContext) {
+ const cached = {};
+ for (const ele of context.values) {
+ cached[ele.key] = ele.value;
+ }
+ const filePath = `${path}/${appName}/src/contexts/${context.name}.js`;
+ const data = `
+ import {createContext, useState} from 'react'
+ export const ${context.name} = createContext();
+
+ const ${context.name}Provider = (props) => {
+ const [${context.name}State] = useState(
+ ${JSON.stringify(cached)}
+ )
+ }
+
+ return (
+ <${context.name}.Provider value={${context.name}State}>
+ {props.children}
+ ${context.name}.Provider>
+ );
+ export default ${context.name}Provider
+ `;
+ window.api.writeFileSync(filePath, data, err => {
+ if (err) {
+ console.log('server file error:', err.message);
+ } else {
+ console.log('server file written successfully');
+ }
+ });
+ }
+};
async function createApplicationUtil({
path,
appName,
components,
- testchecked,
+ testchecked
}: {
path: string;
appName: string;
@@ -349,8 +395,9 @@ async function createApplicationUtil({
await createTsConfig(path, appName);
await createTsLint(path, appName);
await createServer(path, appName);
+ await createContext(path, appName);
if (testchecked) {
- await createTestSuiteClassic({path, appName, components, testchecked});
+ await createTestSuiteClassic({ path, appName, components, testchecked });
}
await createFiles(components, path, appName, true);
}
diff --git a/package.json b/package.json
index 384a784cc..09d666365 100644
--- a/package.json
+++ b/package.json
@@ -117,6 +117,8 @@
"@apollo/client": "^3.3.11",
"@babel/cli": "^7.10.4",
"@babel/register": "^7.10.4",
+ "@mui/icons-material": "^5.8.2",
+ "@mui/lab": "^5.0.0-alpha.83",
"@types/js-cookie": "^2.2.6",
"@types/node": "^14.0.20",
"@types/prettier": "^1.19.0",
@@ -137,6 +139,7 @@
"electron-debug": "^3.2.0",
"electron-devtools-installer": "^3.2.0",
"electron-window-manager": "^1.0.6",
+ "enzyme": "^3.11.0",
"esbuild-wasm": "^0.8.27",
"eslint-plugin-react-hooks": "^4.2.0",
"express-graphql": "^0.12.0",
@@ -154,6 +157,7 @@
"re-resizable": "^6.7.0",
"react-dnd": "^11.1.3",
"react-dnd-html5-backend": "^11.1.3",
+ "react-google-charts": "^4.0.0",
"react-google-login": "^5.1.22",
"react-router-dom": "^5.2.0",
"redux-devtools-extension": "^2.13.9",
@@ -171,11 +175,11 @@
"@babel/preset-env": "^7.16.0",
"@babel/preset-react": "^7.16.0",
"@babel/preset-typescript": "^7.16.0",
- "@emotion/react": "^11.7.1",
- "@emotion/styled": "^11.6.0",
+ "@emotion/react": "^11.9.0",
+ "@emotion/styled": "^11.8.1",
"@material-ui/core": "^4.12.3",
"@material-ui/icons": "^4.11.2",
- "@mui/material": "^5.2.8",
+ "@mui/material": "^5.8.1",
"@mui/styled-engine-sc": "^5.1.0",
"@mui/x-data-grid": "^5.2.1",
"@testing-library/jest-dom": "^5.11.5",