Skip to content

Commit 1fdcb10

Browse files
authored
Merge pull request #6 from oslabs-beta/importComponent/bryankerolos
Import component/bryankerolos
2 parents 23b9fc3 + 37763d5 commit 1fdcb10

File tree

4 files changed

+303
-18
lines changed

4 files changed

+303
-18
lines changed

src/components/home_sidebar_items/ComponentTab/CreateComponent.vue

Lines changed: 38 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -41,33 +41,31 @@ Description:
4141
@click="createComponent"
4242
:disabled="!componentNameInputValue.trim()"
4343
/>
44+
<ImportComponent v-if="activeComponent === ''" @imported="createComponent"/>
4445

45-
<q-btn
46-
v-if="activeComponent === ''"
47-
id="import-component-btn"
48-
color="secondary"
49-
label="Import Component"
50-
@click="click"
51-
/>
5246
</div>
5347
</template>
5448

5549
<script>
5650
import Icons from "./Icons.vue";
5751
import ParentMultiselect from "./ParentMultiselect.vue";
52+
import ImportComponent from "./ImportComponent.vue"
5853
import { mapState, mapActions } from "vuex";
5954
export default {
6055
name: "HomeSidebar",
6156
components: {
6257
Icons,
6358
ParentMultiselect,
64-
},
59+
ImportComponent
60+
},
6561
computed: {
6662
...mapState([
6763
"componentMap",
6864
"selectedElementList",
6965
"activeComponent",
7066
"activeHTML",
67+
"userActions",
68+
"userState",
7169
]),
7270
componentNameInputValue: {
7371
get() {
@@ -88,15 +86,33 @@ export default {
8886
"addNestedHTML",
8987
"addNestedNoActive",
9088
"editComponentName",
89+
"openProject",
90+
"createAction",
91+
"createState",
9192
]),
92-
click() {
93-
console.log("click");
94-
},
95-
createComponent() {
96-
if (!this.componentNameInputValue.replace(/[^a-z0-9-_.]/gi, "")) {
93+
94+
createComponent(importObj) {
95+
let imported = false;
96+
if (importObj.hasOwnProperty('componentName')){
97+
imported = true;
98+
//Check if state and actions on import exist in the store. If not, add them.
99+
for (let i = 0; i < importObj.actions.length; i++){
100+
if (!this.userActions.includes(importObj.actions[i])){
101+
this.createAction(importObj.actions[i])
102+
}
103+
}
104+
for (let i = 0; i < importObj.state.length; i++){
105+
if (!this.userState.includes(importObj.state[i])){
106+
this.createState(importObj.state[i])
107+
}
108+
}
109+
}
110+
111+
if (!this.componentNameInputValue.replace(/[^a-z0-9-_.]/gi, "") && imported === false) {
97112
event.preventDefault();
98113
return false;
99114
}
115+
100116
// boilerplate properties for each component upon creation
101117
const component = {
102118
componentName: this.componentNameInputValue.replace(
@@ -116,12 +132,20 @@ export default {
116132
parent: {},
117133
isActive: false,
118134
};
135+
136+
if (imported === true){
137+
component.componentName = importObj.componentName;
138+
component.htmlList = importObj.htmlList;
139+
component.actions = importObj.actions;
140+
component.state = importObj.state;
141+
component.props = importObj.props;
142+
}
143+
119144
if (!this.componentMap[component.componentName]) {
120145
this.registerComponent(component);
121146
this.setActiveComponent(component.componentName);
122147
}
123148
124-
console.log(this.$store.state);
125149
},
126150
},
127151
};
Lines changed: 258 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,261 @@
1+
<template>
2+
<q-btn
3+
id="import-component-btn"
4+
color="secondary"
5+
label="Import Component"
6+
@click="importComponent"
7+
/>
8+
</template>
9+
110
<script>
2-
import { defineComponent } from "@vue/composition-api";
311
4-
export default defineComponent({
5-
setup() {},
6-
});
12+
export default {
13+
14+
methods: {
15+
//emitter to send the importedObj to CreateComponent when fully parsed.
16+
createImport(importObj){
17+
this.$emit('imported', importObj)
18+
},
19+
20+
//renders the open file
21+
importComponent() {
22+
ipcRenderer
23+
.invoke("openProject", {
24+
properties: ["openProject"],
25+
filters: [
26+
{
27+
name: "Vue Files",
28+
extensions: ["vue"],
29+
},
30+
],
31+
})
32+
.then((res) => this.openVueFile(res.filePaths))
33+
.catch((err) => console.log(err));
34+
},
35+
36+
//parses script tag string for props
37+
parsingStringToProps (str){
38+
let props = [];
39+
let split = str.split(' ');
40+
for(let i = 0; i < split.length; i++){
41+
if(split[i].includes(':') && split[i] !== 'computed:' && split[i] !== 'methods:' && split[i] !== 'name:' && split[i] !=='components:'){
42+
let cleanStr = split[i].slice(0,split[i].indexOf(':'))
43+
props.push(cleanStr)
44+
}
45+
}
46+
return props
47+
},
48+
49+
//parses script tag string for actions
50+
parsingStringToAction (str){
51+
let action = [];
52+
if (str.indexOf('...mapActions') === -1){return action};
53+
let trashedSlice = str.slice(str.lastIndexOf('...mapActions')+15);
54+
let slice = trashedSlice.slice(0, trashedSlice.indexOf('])'));
55+
let split = slice.split(' ')
56+
for(let i = 0; i < split.length; i++){
57+
if (split[i].includes(',')){
58+
let cleanStr = split[i].slice(split[i].indexOf('"')+1,split[i].lastIndexOf('"'))
59+
action.push(cleanStr)
60+
}
61+
}
62+
return action;
63+
},
64+
65+
//parses script tag string for state
66+
parsingStringToState (str){
67+
let state = [];
68+
if (str.indexOf('...mapState') === -1){return state};
69+
let trashedSlice = str.slice(str.lastIndexOf('...mapState')+15);
70+
let slice = trashedSlice.slice(0, trashedSlice.indexOf('])'));
71+
let split = slice.split(' ')
72+
for (let i = 0; i < split.length; i++){
73+
if (split[i].includes(',')){
74+
let cleanStr = split[i].slice(split[i].indexOf('"')+1,split[i].lastIndexOf('"'))
75+
state.push(cleanStr)
76+
}
77+
}
78+
return state
79+
},
80+
81+
//the bulk of the work for this component
82+
openVueFile(data) {
83+
if (data === undefined) return;
84+
85+
const importObj = {}; //final output
86+
const htmlList = []; //array populated with substrings '<div>' '</div>' '<p>' etc.
87+
let compName = data[0].slice(data[0].lastIndexOf('/')+1).replace(/[^a-z0-9-_.]/gi, '');
88+
const vueFile = fs.readFileSync(data[0], "utf8");
89+
90+
for (const key in this.$store.state.componentMap){
91+
if (this.$store.state.componentMap[key].componentName === compName){
92+
compName += 'duplicate'; //appends duplicate if the componentName already exists in state.
93+
}
94+
}
95+
importObj.componentName = compName;
96+
97+
const htmlElementMap = { //OverVue state management only handles these HTML tags.
98+
div: ["<div>", "</div>"],
99+
button: ["<button>", "</button>"],
100+
form: ["<form>", "</form>"],
101+
img: ["<img>", ""],
102+
link: ['<a href="#"/>', ""],
103+
list: ["<li>", "</li>"],
104+
paragraph: ["<p>", "</p>"],
105+
"list-ol": ["<ol>", "</ol>"],
106+
"list-ul": ["<ul>", "</ul>"],
107+
input: ["<input />", ""],
108+
navbar: ["<nav>", "</nav>"],
109+
};
110+
111+
112+
let htmlString = vueFile.substring(vueFile.indexOf('<template >')+10, vueFile.indexOf('</template>'));
113+
let scriptString = vueFile.substring(vueFile.indexOf(`<script>`)+8, vueFile.indexOf(`/script>`)-1)
114+
115+
htmlParser(htmlString);
116+
importObj.props = this.parsingStringToProps(scriptString);
117+
importObj.actions = this.parsingStringToAction(scriptString);
118+
importObj.state = this.parsingStringToState(scriptString);
119+
120+
htmlList.pop(); htmlList.shift(); //OverVue adds a <div></div> wrapper to all components. remove this before importing.
121+
122+
let groupings = findGroupings(htmlList);
123+
let groupingObj = objectGenerator(groupings);
124+
let groupingArray = [];
125+
for (const key in groupingObj){
126+
groupingArray.push(groupingObj[key])
127+
}
128+
importObj.htmlList = groupingArray;
129+
this.createImport(importObj) //send the importObj to CreateComponent.
130+
131+
/**
132+
* @name: htmlParser
133+
* @desc: parses imported .vue file for html elements and state, props and actions
134+
* @param: a string generated by reading a .vue file from the filesystem
135+
* @return: nothing: passes the substrings into buildList to generate an array of elements
136+
*/
137+
138+
function htmlParser(htmlString){
139+
if (!htmlString){return}
140+
//remove new lines and tabs from HTML
141+
htmlString = htmlString.replaceAll('\n', '').replaceAll('\t', '');
142+
//find index for the first < and > in the string
143+
let openTag = htmlString.indexOf('<');
144+
let closeTag = htmlString.indexOf('>');
145+
146+
//push the substring wrapped by < and > into the helper func
147+
buildList(htmlString.substring(openTag+1, closeTag))
148+
149+
//recursively call htmlParser on the remainder of the string
150+
return htmlParser(htmlString.substring(closeTag+1))
151+
}
152+
153+
154+
/**
155+
* @name: buildList
156+
* @desc: takes incoming substrings and pushes the appropriate
157+
* elements fromt he htmlElementMap defined by Overvue
158+
*
159+
* @param: substrings from htmlParser function
160+
* @return: nothing -- pushes into htmlList array which is defined outside scope of buildList
161+
*/
162+
163+
function buildList(substring){
164+
for (const elem in htmlElementMap){
165+
for (let i = 0; i < htmlElementMap[elem].length; i++){
166+
//if the htmlElementMap[elem][index] matches the substring, push it into the output array
167+
if (substring === 'p'){
168+
htmlList.push(htmlElementMap.paragraph[0])
169+
return;
170+
} else if (substring === '/p') {
171+
htmlList.push(htmlElementMap.paragraph[1])
172+
return;
173+
}
174+
if (htmlElementMap[elem][i].indexOf(substring) !== -1){
175+
htmlList.push(htmlElementMap[elem][i])
176+
return;
177+
}
178+
}
179+
}
180+
}
181+
182+
/**
183+
* @name: findGroupings
184+
* @desc: creates groupings of parent/child elements
185+
* @param: array generated by buildList of html elements from imported Vue file
186+
* @return: returns an array of arrays (grouped by parent/child)
187+
*/
188+
189+
function findGroupings(array){
190+
let count = 0; //tracks where the parent ends
191+
let stopIndexes = []; //an array that will be used to slice out the parent/child relationships
192+
for (let i = 0; i < array.length; i++){
193+
if (array[i][1] !== '/'){
194+
if (array[i] === '<img>' || array[i] === '<a href="#"/>' || array[i] === '<input />'){
195+
if (count === 0){
196+
stopIndexes.push(i);
197+
continue;
198+
}
199+
} else {
200+
count++;
201+
}
202+
} else {
203+
count--;
204+
}
205+
if (count === 0){
206+
stopIndexes.push(i)
207+
}
208+
}
209+
let groupings = [];
210+
let startIndex = 0;
211+
for (let i = 0; i < stopIndexes.length; i++){
212+
groupings.push(array.slice(startIndex, stopIndexes[i]+1));
213+
startIndex = stopIndexes[i]+1;
214+
}
215+
return groupings;
216+
}
217+
218+
219+
/**
220+
* @name: objectGenerator
221+
* @desc: generates a nested object from the result of findGroupings nesting children within parents
222+
* @param: array generated by findGroupings
223+
* @return: returns an object containing parent/children html elements nested.
224+
*/
225+
226+
function objectGenerator(array){
227+
let groupingObj = {};
228+
for (let i = 0; i < array.length; i++){
229+
for (const key in htmlElementMap){
230+
if (array[i][0] === htmlElementMap[key][0]){
231+
let idGen = Date.now()-Math.floor(Math.random()*1000000);
232+
groupingObj[i] = {text: key, id: idGen}
233+
}
234+
}
235+
array[i].pop();
236+
array[i].shift();
237+
if (array[i].length > 0){
238+
const childGroupings = findGroupings(array[i]);
239+
const childrenObj = objectGenerator(childGroupings);
240+
const childrenArray = [];
241+
for (const key in childrenObj){
242+
childrenArray.push(childrenObj[key])
243+
}
244+
groupingObj[i].children = childrenArray;
245+
} else {
246+
groupingObj[i].children = [];
247+
}
248+
}
249+
return groupingObj;
250+
}
251+
}
252+
}
253+
}
254+
7255
</script>
256+
257+
258+
259+
260+
261+

src/store/actions.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,12 @@ const actions = {
273273
commit(types.SET_ROUTES, payload.routes);
274274
},
275275

276+
[types.importComponent]: ({ commit }, payload) => {
277+
//import component
278+
console.log('inside import component action')
279+
console.log(payload)
280+
},
281+
276282
// Add project
277283
[types.addProject]: ({ commit }, payload) => {
278284
commit(types.ADD_PROJECT, payload);

src/store/types.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ export const deletePropsFromComponent = 'deletePropsFromComponent'
9494
export const deleteSelectedElement = 'deleteSelectedElement'
9595
export const deleteStateFromComponent = 'deleteStateFromComponent'
9696
export const editComponentName = 'editComponentName'
97+
export const importComponent = 'importComponent'
9798
export const importImage = 'importImage'
9899
export const incrementProjectId = 'incrementProjectId'
99100
export const openProject = 'openProject'

0 commit comments

Comments
 (0)