Skip to content

Commit 1949216

Browse files
committed
new export option for nextjs app
1 parent 9fd5aa7 commit 1949216

File tree

8 files changed

+376
-26
lines changed

8 files changed

+376
-26
lines changed

src/components/App.tsx

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import initialState from '../context/initialState';
99
import reducer from '../reducers/componentReducer';
1010
import { getProjects } from '../helperFunctions/projectGetSave';
1111
import { saveProject } from '../helperFunctions/projectGetSave';
12-
import { loadInitData } from '../actions/actionCreators';
12+
1313
// import { Context, State } from '../interfaces/InterfacesNew';
1414

1515
// Intermediary component to wrap main App component with higher order provider components
@@ -20,27 +20,27 @@ export const App = (): JSX.Element => {
2020
const [state, dispatch] = useReducer(reducer, initialState);
2121

2222
// gets projects from DB for current user on mount
23-
useEffect(() => {
24-
// getProjects returns a promise which is thenable
25-
getProjects().then(project => {
26-
if (project) {
27-
// if user has project we run a dispatch to update state with received project
28-
dispatch({
29-
type: 'SET INITIAL STATE',
30-
payload: project
31-
});
32-
}
33-
});
34-
}, []);
23+
// useEffect(() => {
24+
// // getProjects returns a promise which is thenable
25+
// getProjects().then(project => {
26+
// if (project) {
27+
// // if user has project we run a dispatch to update state with received project
28+
// dispatch({
29+
// type: 'SET INITIAL STATE',
30+
// payload: project
31+
// });
32+
// }
33+
// });
34+
// }, []);
3535

3636
// saves project to DB whenever there are changes to the state via this canvas component
37-
useEffect(() => {
38-
console.log('useEffect in CanvasNew ran');
39-
// setTimeout is necessary so the saveProjects method does not fire and save an empty project before the initial getProjects in AppNew
40-
setTimeout(() => {
41-
saveProject(state);
42-
}, 1000);
43-
}, [state]);
37+
// useEffect(() => {
38+
// console.log('useEffect in CanvasNew ran');
39+
// // setTimeout is necessary so the saveProjects method does not fire and save an empty project before the initial getProjects in AppNew
40+
// setTimeout(() => {
41+
// saveProject(state);
42+
// }, 1000);
43+
// }, [state]);
4444

4545
return (
4646
<div className="app">

src/components/left/ComponentPanel.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,7 @@ const ComponentPanel = (): JSX.Element => {
179179
>
180180
ADD
181181
</Button>
182-
{/* <FormControlLabel
182+
<FormControlLabel
183183
control={
184184
<Switch
185185
checked={isRoot}
@@ -189,7 +189,7 @@ const ComponentPanel = (): JSX.Element => {
189189
}
190190
className={classes.rootToggle}
191191
label="ROOT"
192-
/> */}
192+
/>
193193
</div>
194194
</div>
195195
<div className={classes.panelWrapperList}>

src/components/login/SignUp.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import React, { Component, useState, useEffect } from 'react';
22
import { LoginInt } from '../../interfaces/Interfaces';
3-
import { setLoginState } from '../../actions/actionCreators';
43
import {
54
Link as RouteLink,
65
withRouter,

src/containers/LeftContainer.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,8 @@ const LeftContainer = (): JSX.Element => {
5353
// genOption = 1 --> export an entire project w/ webpack, server, etc.
5454
const genOptions: string[] = [
5555
'Export components',
56-
'Export components with application files'
56+
'Export components with application files',
57+
'Export project as Next.js application'
5758
];
5859
const [genOption, setGenOption] = useState(1);
5960
// state to keep track of whether there should be a modal
@@ -124,7 +125,7 @@ const LeftContainer = (): JSX.Element => {
124125
setGenOption(genOpt);
125126
console.log('Gen option is ', genOpt);
126127
// closeModal
127-
exportProject('/Users', 'NEW PROJECT', genOpt, state.components);
128+
exportProject('/Users', 'NEW PROJECT', genOpt, state.components, state.rootComponents);
128129
closeModal();
129130
// Choose app dir
130131
// NOTE: This functionality isn't working right now. Will upgrade Electron and see if that fixes it
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
import { format } from 'prettier';
2+
import { Component, State, ChildElement } from '../interfaces/InterfacesNew';
3+
import HTMLTypes from '../context/HTMLTypes';
4+
5+
//this is nearly identical to generateCode.ts but accounts for pages referencing imports in the components subfolder
6+
7+
// generate code based on the component heirarchy
8+
const generateUnformattedCode = (comps: Component[], componentId: number, rootComponents: number[]) => {
9+
const components = [...comps];
10+
// find the component that we're going to generate code for
11+
const currentComponent = components.find(elem => elem.id === componentId);
12+
// find the unique components that we need to import into this component file
13+
let imports: any = [];
14+
15+
const isRoot = rootComponents.includes(componentId);
16+
17+
// get metadata for each child
18+
const getEnrichedChildren = (currentComponent: Component | ChildElement) => {
19+
const enrichedChildren = currentComponent.children.map((elem: any) => {
20+
const child = { ...elem };
21+
if (child.type === 'Component') {
22+
const referencedComponent = components.find(
23+
elem => elem.id === child.typeId
24+
);
25+
if (!imports.includes(referencedComponent.name))
26+
imports.push(referencedComponent.name);
27+
child['name'] = referencedComponent.name;
28+
return child;
29+
} else if (child.type === 'HTML Element') {
30+
const referencedHTML = HTMLTypes.find(elem => elem.id === child.typeId);
31+
child['tag'] = referencedHTML.tag;
32+
if (referencedHTML.tag === 'div') {
33+
child.children = getEnrichedChildren(child);
34+
}
35+
return child;
36+
}
37+
});
38+
return enrichedChildren;
39+
};
40+
41+
const writeNestedElements = (enrichedChildren: any) => {
42+
return `${enrichedChildren
43+
.map((child: any) => {
44+
if (child.type === 'Component') {
45+
return `<${child.name}${formatStyles(child.style)} />`;
46+
} else if (child.type === 'HTML Element') {
47+
if (child.tag === 'img') {
48+
return `<${child.tag} src=""${formatStyles(child.style)} />`;
49+
} else if (child.tag === 'a') {
50+
return `<${child.tag} href=""${formatStyles(child.style)}>[LINK]</${child.tag}>`;
51+
} else if (child.tag === 'div') {
52+
return `<${child.tag}${formatStyles(
53+
child.style
54+
)}>${writeNestedElements(child.children)}</${child.tag}>`;
55+
} else if (child.tag === 'h1') {
56+
return `<${child.tag}${formatStyles(child.style)}>HEADER 1</${child.tag}>`;
57+
} else if (child.tag === 'h2') {
58+
return `<${child.tag}${formatStyles(child.style)}>HEADER 2</${child.tag}>`;
59+
} else if (child.tag === 'form') {
60+
return `<${child.tag}${formatStyles(child.style)}>FORM</${child.tag}>`;
61+
} else if (child.tag === 'p') {
62+
return `<${child.tag}${formatStyles(child.style)}>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</${child.tag}>`;
63+
} else if (child.tag === 'li') {
64+
return `<ul${formatStyles(child.style)}><li>item 1</li>
65+
<li>item 2</li>
66+
<li>item 3</li></ul>`;
67+
} else if (child.tag === 'button'){
68+
return `<${child.tag}${formatStyles(child.style)}>BUTTON</${child.tag}>`;
69+
} else {
70+
return `<${child.tag}${formatStyles(child.style)}></${child.tag}>`;
71+
}
72+
}
73+
})
74+
.join('\n')}`;
75+
};
76+
77+
const formatStyles = (styleObj: any) => {
78+
if (Object.keys(styleObj).length === 0) return ``;
79+
const formattedStyles = [];
80+
for (let i in styleObj) {
81+
const styleString = i + ': ' + "'" + styleObj[i] + "'";
82+
formattedStyles.push(styleString);
83+
}
84+
return ' style={{' + formattedStyles.join(',') + '}}';
85+
};
86+
87+
const enrichedChildren: any = getEnrichedChildren(currentComponent);
88+
89+
console.log(enrichedChildren);
90+
91+
// import statements differ between root (pages) and regular components (components)
92+
const importsMapped = imports.map((comp: string) => {
93+
return isRoot ? `import ${comp} from '../components/${comp}'`: `import ${comp} from './${comp}'`;
94+
})
95+
.join('\n')
96+
97+
return `
98+
import React, { useState } from 'react';
99+
100+
${importsMapped}
101+
102+
const ${currentComponent.name} = (props) => {
103+
104+
const [value, setValue] = useState("INITIAL VALUE");
105+
106+
return (
107+
<div className="${currentComponent.name}" style={props.style}>
108+
${writeNestedElements(enrichedChildren)}
109+
</div>
110+
);
111+
}
112+
113+
export default ${currentComponent.name};
114+
`;
115+
};
116+
117+
// formats code with prettier linter
118+
const formatCode = (code: string) => {
119+
return format(code, {
120+
singleQuote: true,
121+
trailingComma: 'es5',
122+
bracketSpacing: true,
123+
jsxBracketSameLine: true,
124+
parser: 'babel'
125+
});
126+
};
127+
128+
// generate code based on component heirarchy and then return the rendered code
129+
const generateNextCode = (components: Component[], componentId: number, rootComponents: number[]) => {
130+
const code = generateUnformattedCode(components, componentId, rootComponents);
131+
return formatCode(code);
132+
};
133+
134+
export default generateNextCode;
135+

src/utils/createNextApp.util.ts

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
import fs from 'fs';
2+
import createNextFiles from './createNextFiles.util';
3+
import { Component } from '../interfaces/InterfacesNew';
4+
5+
const camelToKebab= (camel:string) => {
6+
return camel.replace(/([a-z0-9]|(?=[A-Z]))([A-Z])/g, '$1-$2').toLowerCase();
7+
};
8+
9+
const compToCSS = (component: Component) => {
10+
const name = component.name;
11+
const styleObj = component.style;
12+
let cssClass = `
13+
.${name} {
14+
`;
15+
Object.keys(styleObj).forEach(property => {
16+
let cssStyle = `${camelToKebab(property)}: ${styleObj[property]};
17+
`;
18+
cssClass += cssStyle;
19+
})
20+
cssClass += `}
21+
`;
22+
return cssClass;
23+
}
24+
25+
//createPackage
26+
export const createPackage = (path, appName) => {
27+
const filePath = `${path}/${appName}/package.json`;
28+
const data = `
29+
{
30+
"name": "reactype-next",
31+
"version": "1.0.0",
32+
"description": "",
33+
"scripts": {
34+
"dev": "next dev",
35+
"build": "next build",
36+
"start": "next start"
37+
},
38+
"dependencies": {
39+
"next": "9.3.5",
40+
"react": "16.13.1",
41+
"react-dom": "16.13.1"
42+
},
43+
"devDependencies": {
44+
"@types/node": "^14.0.20",
45+
"@types/react": "^16.9.41",
46+
"typescript": "^3.9.6"
47+
}
48+
}
49+
`;
50+
fs.writeFile(filePath, data, err => {
51+
if (err) {
52+
console.log('package.json error:', err.message);
53+
} else {
54+
console.log('package.json written successfully');
55+
}
56+
});
57+
};
58+
//createTSConfig (empty)
59+
export const createTsConfig = (path, appName) => {
60+
const filePath = `${path}/${appName}/tsconfig.json`;
61+
//running 'next dev' will autopopulate this with default values
62+
fs.writeFile(filePath, '', err => {
63+
if (err) {
64+
console.log('TSConfig error:', err.message);
65+
} else {
66+
console.log('TSConfig written successfully');
67+
}
68+
});
69+
};
70+
71+
//createDefaultCSS
72+
export const createDefaultCSS = (path, appName, components) => {
73+
const filePath = `${path}/${appName}/global.css`;
74+
let data = `
75+
#__next div {
76+
box-sizing: border-box;
77+
width: 100%;
78+
border: 1px solid rgba(0,0,0,0.25);
79+
padding: 12px;
80+
text-align: center;
81+
font-family: Helvetica, Arial;
82+
}
83+
`;
84+
components.forEach(comp => {
85+
data += compToCSS(comp);
86+
})
87+
fs.writeFile(filePath, data, err => {
88+
if (err) {
89+
console.log('global.css error:', err.message);
90+
} else {
91+
console.log('global.css written successfully');
92+
}
93+
});
94+
}
95+
96+
export const initFolders = (path:string, appName: string) => {
97+
let dir = path;
98+
let dirPages;
99+
let dirComponents;
100+
dir = `${dir}/${appName}`;
101+
if(!fs.existsSync(dir)){
102+
fs.mkdirSync(dir);
103+
dirPages = `${dir}/pages`;
104+
fs.mkdirSync(dirPages);
105+
dirComponents = `${dir}/components`;
106+
fs.mkdirSync(dirComponents);
107+
}
108+
}
109+
110+
//createBaseTsx
111+
export const createBaseTsx = (path, appName) => {
112+
113+
const filePath:string = `${path}/${appName}/pages/_app.tsx`;
114+
const data:string = `
115+
import React from 'react';
116+
import '../global.css';
117+
118+
const Base = ({ Component }):JSX.Element => {
119+
return (
120+
<>
121+
<Component />
122+
</>
123+
)
124+
}
125+
126+
export default Base;
127+
`;
128+
fs.writeFile(filePath, data, err => {
129+
if (err) {
130+
console.log('_app.tsx error:', err.message);
131+
} else {
132+
console.log('_app.tsx written successfully');
133+
}
134+
});
135+
};
136+
137+
async function createNextAppUtil({
138+
path,
139+
appName,
140+
components,
141+
rootComponents
142+
}: {
143+
path: string;
144+
appName: string;
145+
components: Component[];
146+
rootComponents: number[];
147+
}) {
148+
console.log('in the createNextApplication util');
149+
150+
await initFolders(path, appName);
151+
await createBaseTsx(path, appName);
152+
await createDefaultCSS(path, appName, components);
153+
await createPackage(path, appName);
154+
await createTsConfig(path, appName);
155+
await createNextFiles(components, path, appName, rootComponents);
156+
157+
}
158+
export default createNextAppUtil;

0 commit comments

Comments
 (0)