├── .DS_Store
├── .babelrc
├── .eslintrc.json
├── .gitignore
├── .travis.yml
├── LICENSE.md
├── README.md
├── __mocks__
├── appStateMocks.ts
└── localStorageMock.ts
├── __tests__
├── reducerCreateAndDeleteComponentsAndChildren.test.ts
└── reducerReduxStoreConfig.test.ts
├── images
├── actions.PNG
├── addordeletecomponent.PNG
├── addreduxconn.PNG
├── createcomponent.PNG
├── createinterface.PNG
├── deletecomponent.PNG
├── export.PNG
├── household.PNG
├── interfacefields.PNG
├── store.PNG
└── storeselections.PNG
├── jest.config.js
├── main.js
├── package-lock.json
├── package.json
├── preducks-logo-text.svg
├── preducks-logo-text@2x.png
├── src
├── .DS_Store
├── actionTypes
│ └── index.js
├── actions
│ └── components.ts
├── components
│ ├── Actions.tsx
│ ├── App.tsx
│ ├── BottomPanel.tsx
│ ├── BottomTabs.tsx
│ ├── CodePreview.tsx
│ ├── ComponentReduxSetup.tsx
│ ├── DataTable.tsx
│ ├── ErrorMessage.tsx
│ ├── HTMLComponentPanel.tsx
│ ├── HtmlAttr.tsx
│ ├── HtmlChild.tsx
│ ├── Interface.tsx
│ ├── Interfaces.tsx
│ ├── LeftColExpansionPanel.tsx
│ ├── NewTreeDisplay.tsx
│ ├── Reducer.tsx
│ ├── Reducers.tsx
│ ├── RightPanel.tsx
│ ├── SimpleModal.tsx
│ ├── Store.tsx
│ ├── StoreItemHeader.tsx
│ ├── TypeSelect.tsx
│ └── theme.ts
├── containers
│ ├── AppContainer.tsx
│ ├── LeftContainer.tsx
│ └── MainContainer.tsx
├── index.js
├── localStorage.js
├── public
│ ├── LICENSE
│ ├── README.md
│ ├── _headers
│ ├── icons
│ │ ├── mac
│ │ │ └── preducks.icns
│ │ ├── png
│ │ │ ├── 128x128.png
│ │ │ ├── 16x16.png
│ │ │ ├── 24x24.png
│ │ │ ├── 256x256.png
│ │ │ ├── 32x32.png
│ │ │ ├── 48x48.png
│ │ │ ├── 512x512.png
│ │ │ ├── 64x64.png
│ │ │ └── 96x96.png
│ │ └── win
│ │ │ └── preducks.ico
│ ├── images
│ │ ├── TreeImageComponent.png
│ │ └── file structure photo.png
│ ├── index.html
│ └── styles
│ │ └── index.css
├── reducers
│ ├── componentReducer.ts
│ └── index.js
├── store.js
└── utils
│ ├── InterfaceDefinitions.ts
│ ├── cloneDeep.ts
│ ├── colors.util.ts
│ ├── componentReducer.util.ts
│ ├── componentRender.util.ts
│ ├── createApplication.util.ts
│ ├── createComponentFiles.util.ts
│ ├── createModal.util.tsx
│ ├── createReduxFiles.util.ts
│ ├── dummyData.ts
│ ├── formatter.util.ts
│ ├── getSelectable.util.ts
│ ├── htmlElements.util.ts
│ └── validateInput.util.ts
├── tsconfig.json
├── tslint.json
├── webpack.common.js
├── webpack.dev.js
└── webpack.prod.js
/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/preducks/1ef20cbca94cfebdc62ef99a86292269a7275052/.DS_Store
--------------------------------------------------------------------------------
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | [
4 | "env",
5 | {
6 | "modules": false
7 | }
8 | ],
9 | "react",
10 | "stage-0"
11 | // "@babel/preset-typescript"
12 | ],
13 | "plugins": ["transform-es2015-modules-commonjs"]
14 | }
15 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": ["plugin:react/recommended", "airbnb-base"],
3 | "parserOptions": {
4 | "ecmaFeatures": {
5 | "jsx": true
6 | },
7 | "ecmaVersion": 2018,
8 | "sourceType": "module"
9 | },
10 | "plugins": ["import", "react", "jest", "jsx-a11y", "babel"],
11 | "parser": "babel-eslint",
12 | "env": {
13 | "browser": true,
14 | "node": true,
15 | "es6": true,
16 | "jest": true
17 | },
18 | "rules": {
19 | "class-methods-use-this": "off",
20 | "linebreak-style": 0
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | build
3 | .*
4 | !.gitignore
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - '10'
4 | - '12'
5 | services:
6 | - xvfb
7 | # before_script:
8 | # - 'export DISPLAY=:99.0'
9 | # - 'sh -e /etc/init.d/xvfb start'
10 | # - sleep 3 # give xvfb some time to start
11 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 preducks
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6 |
7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8 |
9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
10 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://github.com/oslabs-beta/preducks/pulls)
2 | 
3 | [](https://app.netlify.com/sites/preducks/deploys)
4 |
5 | 
6 |
7 | **preducks** is a visual prototyping tool for developers employing **React** component architecture and **Redux** state management, alongside the comprehensive type checking of **TypeScript**.
8 | In other words, **you can create prototypes and export React / Typescript code!**
9 |
10 | **preducks** allows the user to _visualize_ their application architecture dynamically, employing an _application tree_ and a _real-time component code preview_. The user can create components and load _instances_ of these components, as well as nested HTML elements, onto the tree. The user can also specify the desired shape of their _Redux store_ and _reducers_, as well as _interfaces_ to describe the desired shape of their data, and connections between their components and the store using _Redux hooks_. This architecture can then be _exported_ as TypeScript application files to be used as a starter template for any repository.
11 |
12 | ## how to use:
13 |
14 | #### creating React components:
15 |
16 | To create a new React component, type the name of your component into the _add component_ box and click the _+_ button next to it.\
17 | \
18 | \
19 | To add a component as a child of another component, click on the parent component in the list of components, and press the _+_ button next to the component you wish to add as a child. To remove a child from a parent, click on the parent in the list, and press the _-_ button which appears next to the component you wish to remove.\
20 | You will not be able to add a component's parents or
21 | grandparents as its children.\
22 | \
23 | \
24 | To completely delete a component, click on it in the list of components and click the _DELETE COMPONENT_ button.\
25 | 
26 |
27 | #### creating interfaces for the Redux store:
28 |
29 | Since our app uses TypeScript, we give you the opportunity to create TypeScript interfaces to describe the shapes of the data you wish to use in your Redux store. By default, we allow you to choose from the types _number_, _string_, _boolean_, and _any_. You can specify whether you want a value to be a single value of this type, or an array of these types. Once you define an interface, you will be able to use it as the data type of later values you define along with the default ones we provide. All of your interfaces will be exported in a TypeScript file, and they will be imported in components that need them.\
30 | To create an interface, type in the name of the interface in the _new interface_ field and press the _+_ button next to it.\
31 | \
32 | \
33 | You can add fields to an interface by entering a name and type for a field, and can also specify if you want that field to be an array, then clicking the plus button next to the form fields. Here we create an interface to describe a _person_ data type. We also create a _household_ interface that uses the _person_ interface we just made in a few of its fields.\
34 | \
35 | \
36 | To delete a field, click the trashcan icon that appears next to the field when you hover over it.\
37 | To delete the entire interface, click on the trashcan icon that appears next to the interface's name.
38 |
39 | #### creating reducers for the Redux store
40 |
41 | The form to create reducers appears below the form to create interfaces. You create one the same way as interfaces. Just type the name of the reducer into the field and click the _+_ button. You can make multiple reducers. We combine them for you into one Redux store.\
42 | Once you create a reducer, you can start adding properties to its store. Specify a name for the propety, its type, whether or not it's an array, and an initial value. This will be used as the initial state for your Redux store, and we also generate a TypeScript interface to describe the shape of your store.\
43 | \
44 | \
45 | Under the store configuration options, you can also define action creators. This is mainly just to create the boilerplate and type definitions; you'll have to add the logic for the action creators yourself once you export your project. You can provide a name for your action creator, specify whether or not it's asynchronous, give a name and type to the parameters expected by the action creator, and specify the type of its payload.\
46 | \
47 | \
48 | As before, you can delete individual store properties or actions by clicking on the trashcan icon that appears when you hover over them, and you can delete the entire reducer by clicking on the trashcan icon that appears next to the reducer name.
49 |
50 | #### connecting React components to the Redux store:
51 |
52 | You can connect a component to the Redux store (and also add local state to a component if necessary) using the _local state & redux connection_ tab at the bottom of the app.\
53 | We use Redux hooks (_useSelector()_) to connect components to the Redux store rather than using a _mapStateToProps()_ function and wrapping the exported component in _connect()_.\
54 | You can use the dropdowns to select which store properties you want your component to connect to, and to import actions you want your component to be able to dispatch. (Again, we use the _useDispatch()_ hook to give you access to _dispatch()_ rather than using a _mapDispatchToProps()_ function).\
55 | \
56 | \
57 | When you add store selections or actions to a component, they will show up on a list near the dropdowns. Here, you can remove anything you added from the component.\
58 | \
59 | \
60 | You can also add pieces of local state to your component (we use only functional components in our app, so we use the _useState()_ hook instead of creating _this.state_ in a constructor). As usual, you supply the name, type, and value of the state you want to add. You can delete pieces of state with the trashcan icon next to the specific piece in the list of local state, and edit information about state using the pencil icon.
61 |
62 | #### exporting your project:
63 |
64 | In the bottom left corner, there will be two buttons:\
65 | \
66 | A red button to clear your workspace (this will clear any changes you've made) and a green button to export your project. The export button will download a zip file which will contain the full boilerplate code for your app, based on all the information you've entered. Obviously, you'll have to add most of the application logic yourself, but using our tool should greatly reduce the time you have to spend setting up and organizing the hierarchy of your components and application state.
67 |
68 | ## running your own version
69 |
70 | - **Fork** and **Clone** repository.
71 | - open project directory
72 | - install dependencies
73 |
74 | ```bash
75 | npm install
76 | ```
77 |
78 | - run application
79 |
80 | ```bash
81 | npm start
82 | ```
83 |
84 | - for development experience
85 |
86 | ```bash
87 | npm run dev
88 | ```
89 |
90 | ## License
91 |
92 | This project is licensed under the MIT License - see the [LICENSE.md](https://github.com/oslabs-beta/preducks/blob/development/LICENSE.md) file for details.
93 |
--------------------------------------------------------------------------------
/__mocks__/appStateMocks.ts:
--------------------------------------------------------------------------------
1 | import cloneDeep from '../src/utils/cloneDeep';
2 |
3 | const appComponent = {
4 | id: 1,
5 | stateful: false,
6 | componentState: [],
7 | title: 'App',
8 | color: '#FF6D00',
9 | props: [],
10 | nextPropId: 1,
11 | childrenArray: [],
12 | nextChildId: 1,
13 | focusChildId: 0,
14 | selectors: [],
15 | actions: [],
16 | };
17 |
18 | const appComponent2 = {
19 | id: 1,
20 | stateful: false,
21 | componentState: [
22 | {name: 'test', type: 'string', initialValue: 'test'}
23 | ],
24 | title: 'App',
25 | color: '#FF6D00',
26 | props: [],
27 | nextPropId: 1,
28 | childrenArray: [],
29 | nextChildId: 1,
30 | focusChildId: 0,
31 | selectors: ['test.selector'],
32 | actions: ['test.action'],
33 | };
34 |
35 | const initialApplicationFocusChild = {
36 | childId: 0,
37 | componentName: null,
38 | childType: null,
39 | childSort: 0,
40 | childComponentId: 0,
41 | color: null,
42 | htmlElement: null,
43 | HTMLInfo: null,
44 | };
45 |
46 | export const initialAppStateMock = {
47 | totalComponents: 1,
48 | nextId: 2,
49 | successOpen: false,
50 | errorOpen: false,
51 | focusComponent: appComponent,
52 | selectableChildren: [],
53 | ancestors: [],
54 | initialApplicationFocusChild,
55 | focusChild: cloneDeep(initialApplicationFocusChild),
56 | components: [appComponent],
57 | appDir: '',
58 | loading: false,
59 | storeConfig: { interfaces: {}, reducers: {} },
60 | };
61 |
62 | export const initialAppStateMock2 = {
63 | totalComponents: 1,
64 | nextId: 2,
65 | successOpen: false,
66 | errorOpen: false,
67 | focusComponent: appComponent2,
68 | selectableChildren: [],
69 | ancestors: [],
70 | initialApplicationFocusChild,
71 | focusChild: cloneDeep(initialApplicationFocusChild),
72 | components: [appComponent2],
73 | appDir: '',
74 | loading: false,
75 | storeConfig: { interfaces: {
76 | test: { type1: 'string', type2: 'number' }
77 | }, reducers: {
78 | test: { store: {}, actions: {} }
79 | } },
80 | };
81 |
82 |
83 | export const aboutToAddChildMock = {
84 | totalComponents: 2,
85 | nextId: 3,
86 | successOpen: false,
87 | errorOpen: false,
88 | focusComponent: {
89 | id: 1,
90 | stateful: false,
91 | componentState: [],
92 | title: 'App',
93 | color: '#FF6D00',
94 | props: [],
95 | nextPropId: 1,
96 | childrenArray: [],
97 | nextChildId: 1,
98 | focusChildId: 0,
99 | selectors: [],
100 | actions: [],
101 | },
102 | selectableChildren: [2],
103 | ancestors: [],
104 | initialApplicationFocusChild: {
105 | childId: 0,
106 | componentName: null,
107 | childType: null,
108 | childSort: 0,
109 | childComponentId: 0,
110 | color: null,
111 | htmlElement: null,
112 | HTMLInfo: null,
113 | },
114 | focusChild: {
115 | childId: 0,
116 | componentName: null,
117 | childType: null,
118 | childSort: 0,
119 | childComponentId: 0,
120 | color: null,
121 | htmlElement: null,
122 | HTMLInfo: null,
123 | },
124 | components: [
125 | {
126 | id: 1,
127 | stateful: false,
128 | componentState: [],
129 | title: 'App',
130 | color: '#FF6D00',
131 | props: [],
132 | nextPropId: 1,
133 | childrenArray: [],
134 | nextChildId: 1,
135 | focusChildId: 0,
136 | selectors: [],
137 | actions: [],
138 | },
139 | {
140 | id: 2,
141 | stateful: false,
142 | componentState: [],
143 | title: 'Child',
144 | color: '#7395AE',
145 | props: [],
146 | nextPropId: 1,
147 | childrenArray: [],
148 | nextChildId: 1,
149 | focusChildId: 0,
150 | selectors: [],
151 | actions: [],
152 | },
153 | ],
154 | appDir: '',
155 | loading: false,
156 | storeConfig: {
157 | interfaces: {},
158 | reducers: {},
159 | },
160 | };
161 |
162 | export const aboutToDeleteComponentMock = {
163 | totalComponents: 2,
164 | nextId: 3,
165 | successOpen: false,
166 | errorOpen: false,
167 | focusComponent: {
168 | id: 2,
169 | stateful: false,
170 | componentState: [],
171 | title: 'Test',
172 | color: '#E27D60',
173 | props: [],
174 | nextPropId: 1,
175 | childrenArray: [],
176 | nextChildId: 1,
177 | focusChildId: 0,
178 | selectors: [],
179 | actions: [],
180 | },
181 | selectableChildren: [1],
182 | ancestors: [],
183 | initialApplicationFocusChild: {
184 | childId: 0,
185 | componentName: null,
186 | childType: null,
187 | childSort: 0,
188 | childComponentId: 0,
189 | color: null,
190 | htmlElement: null,
191 | HTMLInfo: null,
192 | },
193 | focusChild: {
194 | childId: 0,
195 | componentName: null,
196 | childType: null,
197 | childSort: 0,
198 | childComponentId: 0,
199 | color: null,
200 | htmlElement: null,
201 | HTMLInfo: null,
202 | },
203 | components: [
204 | {
205 | id: 1,
206 | stateful: false,
207 | componentState: [],
208 | title: 'App',
209 | color: '#FF6D00',
210 | props: [],
211 | nextPropId: 1,
212 | childrenArray: [],
213 | nextChildId: 1,
214 | focusChildId: 0,
215 | selectors: [],
216 | actions: [],
217 | },
218 | {
219 | id: 2,
220 | stateful: false,
221 | componentState: [],
222 | title: 'Test',
223 | color: '#E27D60',
224 | props: [],
225 | nextPropId: 1,
226 | childrenArray: [],
227 | nextChildId: 1,
228 | focusChildId: 0,
229 | selectors: [],
230 | actions: [],
231 | },
232 | ],
233 | appDir: '',
234 | loading: false,
235 | storeConfig: {
236 | interfaces: {},
237 | reducers: {},
238 | },
239 | };
240 |
--------------------------------------------------------------------------------
/__mocks__/localStorageMock.ts:
--------------------------------------------------------------------------------
1 | export default new (class {
2 | store = {};
3 |
4 | setItem = (key, val) => {
5 | this.store[key] = val;
6 | };
7 |
8 | getItem = key => this.store[key];
9 |
10 | removeItem = (key) => {
11 | delete this.store[key];
12 | };
13 |
14 | clear = () => {
15 | this.store = {};
16 | };
17 | })();
18 |
--------------------------------------------------------------------------------
/__tests__/reducerCreateAndDeleteComponentsAndChildren.test.ts:
--------------------------------------------------------------------------------
1 | // import configureMockStore from 'redux-mock-store';
2 | import componentReducer from '../src/reducers/componentReducer';
3 | import {
4 | initialAppStateMock,
5 | aboutToAddChildMock,
6 | aboutToDeleteComponentMock,
7 | } from '../__mocks__/appStateMocks';
8 | import * as types from '../src/actionTypes';
9 |
10 | describe('initialization stuff', () => {
11 | it('should return the initial state if no recognized action argument is given', () => {
12 | expect(componentReducer(undefined, {})).toEqual(initialAppStateMock);
13 | });
14 | });
15 |
16 | describe('addComponent', () => {
17 | it('should handle addComponent with submitted title Test', () => {
18 | const addComponentResult = componentReducer(initialAppStateMock, {
19 | type: types.ADD_COMPONENT,
20 | payload: { title: 'Test' },
21 | });
22 | expect(addComponentResult.nextId).toEqual(3);
23 | expect(addComponentResult.focusComponent.id).toEqual(2);
24 | expect(addComponentResult.focusComponent.title).toEqual('Test');
25 | expect(addComponentResult.components[1].id).toEqual(2);
26 | expect(addComponentResult.components[1].title).toEqual('Test');
27 | expect(addComponentResult.totalComponents).toEqual(2);
28 | expect(addComponentResult.selectableChildren[0]).toEqual(1);
29 | });
30 |
31 | it('should take only alpha chars from name and capitalize first letter of each word', () => {
32 | const addComponentResult = componentReducer(initialAppStateMock, {
33 | type: types.ADD_COMPONENT,
34 | payload: { title: '34*&;first;;98
' },
35 | });
36 | expect(addComponentResult.nextId).toEqual(3);
37 | expect(addComponentResult.focusComponent.id).toEqual(2);
38 | expect(addComponentResult.focusComponent.title).toEqual('FirstPP');
39 | expect(addComponentResult.components[1].id).toEqual(2);
40 | expect(addComponentResult.components[1].title).toEqual('FirstPP');
41 | expect(addComponentResult.totalComponents).toEqual(2);
42 | expect(addComponentResult.selectableChildren[0]).toEqual(1);
43 | });
44 |
45 | it('should not add a component when submitted title is empty', () => {
46 | const addComponentResult = componentReducer(initialAppStateMock, {
47 | type: types.ADD_COMPONENT,
48 | payload: { title: '' },
49 | });
50 | expect(addComponentResult.nextId).toEqual(2);
51 | expect(addComponentResult.focusComponent.id).toEqual(1);
52 | expect(addComponentResult.focusComponent.title).toEqual('App');
53 | expect(addComponentResult.components[0].id).toEqual(1);
54 | expect(addComponentResult.components[0].title).toEqual('App');
55 | expect(addComponentResult.totalComponents).toEqual(1);
56 | expect(addComponentResult.selectableChildren).toEqual([]);
57 | });
58 |
59 | it('should not add a component when a component with that name already exists', () => {
60 | global.alert = jest.fn();
61 | const addComponentResult = componentReducer(initialAppStateMock, {
62 | type: types.ADD_COMPONENT,
63 | payload: { title: 'App' },
64 | });
65 | expect(addComponentResult.nextId).toEqual(2);
66 | expect(addComponentResult.focusComponent.id).toEqual(1);
67 | expect(addComponentResult.focusComponent.title).toEqual('App');
68 | expect(addComponentResult.components[0].id).toEqual(1);
69 | expect(addComponentResult.components[0].title).toEqual('App');
70 | expect(addComponentResult.totalComponents).toEqual(1);
71 | expect(addComponentResult.selectableChildren).toEqual([]);
72 | });
73 | });
74 |
75 | describe('addChild', () => {
76 | it('should add a component child correctly', () => {
77 | const addChildResult = componentReducer(aboutToAddChildMock, {
78 | type: types.ADD_CHILD,
79 | payload: { title: 'Child', childType: 'COMP', HTMLInfo: {} },
80 | });
81 | expect(addChildResult.focusComponent.childrenArray.length).toEqual(1);
82 | expect(addChildResult.focusComponent.focusChildId).toEqual(1);
83 | expect(addChildResult.focusComponent.nextChildId).toEqual(2);
84 | expect(addChildResult.focusChild.childId).toEqual(1);
85 | expect(addChildResult.focusChild.componentName).toEqual('Child');
86 | expect(addChildResult.focusChild.childType).toEqual('COMP');
87 | expect(addChildResult.focusChild.childSort).toEqual(1);
88 | expect(addChildResult.focusChild.childComponentId).toEqual(2);
89 | expect(addChildResult.components[1].childrenArray[0].componentName).toEqual('Child');
90 | });
91 |
92 | it('should add an HTML child correctly', () => {
93 | const addChildResult = componentReducer(aboutToAddChildMock, {
94 | type: types.ADD_CHILD,
95 | payload: { title: 'Image', childType: 'Image', HTMLInfo: {} },
96 | });
97 | expect(addChildResult.focusComponent.childrenArray.length).toEqual(1);
98 | expect(addChildResult.focusComponent.focusChildId).toEqual(1);
99 | expect(addChildResult.focusComponent.nextChildId).toEqual(2);
100 | expect(addChildResult.focusChild.childId).toEqual(1);
101 | expect(addChildResult.focusChild.componentName).toEqual('Image');
102 | expect(addChildResult.focusChild.childType).toEqual('HTML');
103 | expect(addChildResult.focusChild.childSort).toEqual(1);
104 | expect(addChildResult.focusChild.childComponentId).toEqual(null);
105 | expect(addChildResult.components[1].childrenArray[0].componentName).toEqual('Image');
106 | });
107 | });
108 |
109 | describe('deleteChild', () => {
110 | it('should delete a component child correctly', () => {
111 | const addChildResult = componentReducer(aboutToAddChildMock, {
112 | type: types.ADD_CHILD,
113 | payload: { title: 'Child', childType: 'COMP', HTMLInfo: {} },
114 | });
115 |
116 | const deleteChildResult = componentReducer(addChildResult, {
117 | type: types.DELETE_CHILD,
118 | payload: 2, // this is confusing bc this is actually the component's ID, not its child ID
119 | });
120 |
121 | expect(deleteChildResult.focusComponent.childrenArray.length).toEqual(0);
122 | expect(deleteChildResult.focusChild.childId).toEqual(0);
123 | expect(deleteChildResult.components[1].childrenArray.length).toEqual(0);
124 | });
125 |
126 | it('should delete an HTML child correctly', () => {
127 | const addChildResult = componentReducer(aboutToAddChildMock, {
128 | type: types.ADD_CHILD,
129 | payload: { title: 'Image', childType: 'Image', HTMLInfo: {} },
130 | });
131 |
132 | const deleteChildResult = componentReducer(addChildResult, {
133 | type: types.DELETE_CHILD,
134 | payload: 1, // this is the child ID of the html element
135 | });
136 |
137 | expect(deleteChildResult.focusComponent.childrenArray.length).toEqual(0);
138 | expect(deleteChildResult.focusChild.childId).toEqual(0);
139 | expect(deleteChildResult.components[1].childrenArray.length).toEqual(0);
140 | });
141 |
142 | it('should delete instances of a component as a child when the component has been deleted (uses alt form of action payload)', () => {
143 | const addChildResult = componentReducer(aboutToAddChildMock, {
144 | type: types.ADD_CHILD,
145 | payload: { title: 'Child', childType: 'COMP', HTMLInfo: {} },
146 | });
147 |
148 | const deleteChildResult = componentReducer(addChildResult, {
149 | type: types.DELETE_CHILD,
150 | payload: {
151 | parentId: 1,
152 | childId: 1,
153 | calledFromDeleteComponent: true,
154 | },
155 | });
156 |
157 | expect(deleteChildResult.focusComponent.childrenArray.length).toEqual(0);
158 | expect(deleteChildResult.focusChild.childId).toEqual(0);
159 | expect(deleteChildResult.components[1].childrenArray.length).toEqual(0);
160 | });
161 | });
162 |
163 | describe('deleteComponent', () => {
164 | it('should delete a component correctly', () => {
165 | const deleteComponentResult = componentReducer(aboutToDeleteComponentMock, {
166 | type: types.DELETE_COMPONENT,
167 | payload: {
168 | componentId: 2,
169 | },
170 | });
171 | expect(deleteComponentResult.totalComponents).toEqual(1);
172 | expect(deleteComponentResult.components.length).toEqual(1);
173 | expect(deleteComponentResult.components[0].title).toEqual('App');
174 | });
175 |
176 | it('App component is not deletable', () => {
177 | const deleteComponentResult = componentReducer(aboutToDeleteComponentMock, {
178 | type: types.DELETE_COMPONENT,
179 | payload: {
180 | componentId: 1,
181 | },
182 | });
183 | expect(deleteComponentResult.totalComponents).toEqual(2);
184 | expect(deleteComponentResult.components.length).toEqual(2);
185 | expect(deleteComponentResult.components[0].title).toEqual('App');
186 | });
187 | });
188 |
--------------------------------------------------------------------------------
/images/actions.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/preducks/1ef20cbca94cfebdc62ef99a86292269a7275052/images/actions.PNG
--------------------------------------------------------------------------------
/images/addordeletecomponent.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/preducks/1ef20cbca94cfebdc62ef99a86292269a7275052/images/addordeletecomponent.PNG
--------------------------------------------------------------------------------
/images/addreduxconn.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/preducks/1ef20cbca94cfebdc62ef99a86292269a7275052/images/addreduxconn.PNG
--------------------------------------------------------------------------------
/images/createcomponent.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/preducks/1ef20cbca94cfebdc62ef99a86292269a7275052/images/createcomponent.PNG
--------------------------------------------------------------------------------
/images/createinterface.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/preducks/1ef20cbca94cfebdc62ef99a86292269a7275052/images/createinterface.PNG
--------------------------------------------------------------------------------
/images/deletecomponent.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/preducks/1ef20cbca94cfebdc62ef99a86292269a7275052/images/deletecomponent.PNG
--------------------------------------------------------------------------------
/images/export.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/preducks/1ef20cbca94cfebdc62ef99a86292269a7275052/images/export.PNG
--------------------------------------------------------------------------------
/images/household.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/preducks/1ef20cbca94cfebdc62ef99a86292269a7275052/images/household.PNG
--------------------------------------------------------------------------------
/images/interfacefields.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/preducks/1ef20cbca94cfebdc62ef99a86292269a7275052/images/interfacefields.PNG
--------------------------------------------------------------------------------
/images/store.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/preducks/1ef20cbca94cfebdc62ef99a86292269a7275052/images/store.PNG
--------------------------------------------------------------------------------
/images/storeselections.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/preducks/1ef20cbca94cfebdc62ef99a86292269a7275052/images/storeselections.PNG
--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | roots: [''],
3 | globals: {
4 | 'ts-jest': {
5 | diagnostics: false,
6 | },
7 | },
8 | transform: {
9 | '^.+\\.jsx?$': 'babel-jest',
10 | '^.+\\.tsx?$': 'ts-jest',
11 | },
12 | testMatch: ['**/__tests__/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)'],
13 | moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
14 | moduleNameMapper: {
15 | '^.+\\.(css|scss|styl|less|sass|scss|png|jpg|ttf|woff|woff2)$': 'identity-obj-proxy',
16 | electron: '/__mocks__/electron.js',
17 | },
18 | snapshotSerializers: ['enzyme-to-json/serializer'],
19 | };
20 |
--------------------------------------------------------------------------------
/main.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const isElectron = require('is-electron');
3 |
4 | if (isElectron()) {
5 | const {
6 | app, BrowserWindow, Menu, shell, dialog, ipcMain,
7 | } = require('electron');
8 |
9 | // Uncomment below for hot reloading during development
10 | // require('electron-reload')(__dirname);
11 |
12 | // const isDev = true;
13 | const isDev = process.env.NODE_ENV === 'development';
14 |
15 | // Keep a global reference of the window object, if you don't, the window will
16 | // be closed automatically when the JavaScript object is garbage collected.
17 | let mainWindow;
18 |
19 | // Open image file
20 | const openFile = () => {
21 | // Opens file dialog looking for markdown
22 | const files = dialog.showOpenDialog(mainWindow, {
23 | properties: ['openFile'],
24 | filters: [
25 | {
26 | name: 'Images',
27 | extensions: ['jpeg', 'jpg', 'png', 'gif', 'pdf'],
28 | },
29 | ],
30 | });
31 |
32 | // if no files
33 | if (!files) return;
34 | const file = files[0];
35 |
36 | // Send fileContent to renderer
37 | mainWindow.webContents.send('new-file', file);
38 | };
39 |
40 | // Choose directory
41 | ipcMain.on('choose_app_dir', (event) => {
42 | const directory = dialog.showOpenDialog(mainWindow, {
43 | properties: ['openDirectory'],
44 | buttonLabel: 'Export',
45 | });
46 |
47 | if (!directory) return;
48 | event.sender.send('app_dir_selected', directory[0]);
49 | });
50 |
51 | ipcMain.on('view_app_dir', (event, appDir) => {
52 | shell.openItem(appDir);
53 | });
54 |
55 | // Update file
56 | ipcMain.on('update-file', () => {
57 | openFile();
58 | });
59 |
60 | const createWindow = () => {
61 | // Create the browser window.
62 | // eslint-disable-next-line
63 | const { width, height } = require('electron').screen.getPrimaryDisplay().size;
64 | mainWindow = new BrowserWindow({
65 | width,
66 | height,
67 | minWidth: 790,
68 | minHeight: 420,
69 | webPreferences: {
70 | zoomFactor: 0.7,
71 | nodeIntegration: true,
72 | webSecurity: false,
73 | scrollBounce: true,
74 | },
75 | show: false,
76 | backgroundColor: '#333',
77 | titleBarStyle: 'hidden',
78 | icon: path.join(__dirname, '/src/public/icons/mac/icon.icns'),
79 | win: {
80 | icon: path.join(__dirname, '/src/public/icons/win/icon.ico'),
81 | target: ['portable'],
82 | },
83 | });
84 |
85 | // and load the index.html of the app.
86 | mainWindow.loadURL(`file://${__dirname}/build/index.html`);
87 | // load page once window is loaded
88 | mainWindow.once('ready-to-show', () => {
89 | mainWindow.show();
90 | });
91 |
92 | const template = [
93 | {
94 | label: 'File',
95 | submenu: [
96 | {
97 | label: 'Open File',
98 | accelerator: process.platform === 'darwin' ? 'Cmd+O' : 'Ctrl+Shift+O',
99 | click() {
100 | openFile();
101 | },
102 | },
103 | ],
104 | },
105 | {
106 | label: 'Edit',
107 | submenu: [
108 | // { role: 'undo' },
109 | // { role: 'redo' },
110 | // { type: 'separator' },
111 | { role: 'cut' },
112 | { role: 'copy' },
113 | { role: 'paste' },
114 | { role: 'pasteandmatchstyle' },
115 | { role: 'delete' },
116 | { role: 'selectall' },
117 | ],
118 | },
119 | {
120 | label: 'View',
121 | submenu: [
122 | { role: 'reload' },
123 | { role: 'forcereload' },
124 | { type: 'separator' },
125 | { role: 'resetzoom' },
126 | { role: 'zoomin' },
127 | { role: 'zoomout' },
128 | { type: 'separator' },
129 | { role: 'togglefullscreen' },
130 | ],
131 | },
132 | {
133 | role: 'window',
134 | submenu: [{ role: 'minimize' }, { role: 'close' }],
135 | },
136 | {
137 | role: 'help',
138 | submenu: [
139 | {
140 | label: 'Learn More',
141 | click() {
142 | shell.openExternal('https://electronjs.org');
143 | },
144 | },
145 | ],
146 | },
147 | {
148 | label: 'Developer',
149 | submenu: [
150 | {
151 | label: 'Toggle Developer Tools',
152 | accelerator: process.platform === 'darwin' ? 'Alt+Command+I' : 'Ctrl+Shift+I',
153 | click() {
154 | mainWindow.webContents.toggleDevTools();
155 | },
156 | },
157 | ],
158 | },
159 | ];
160 |
161 | if (process.platform === 'darwin') {
162 | template.unshift({
163 | label: app.getName(),
164 | submenu: [
165 | { role: 'about' },
166 | { type: 'separator' },
167 | { role: 'services', submenu: [] },
168 | { type: 'separator' },
169 | { role: 'hide' },
170 | { role: 'hideothers' },
171 | { role: 'unhide' },
172 | { type: 'separator' },
173 | { role: 'quit' },
174 | ],
175 | });
176 |
177 | // Edit menu
178 | // template[2].submenu.push(
179 | // { type: 'separator' },
180 | // { label: 'Speech', submenu: [{ role: 'startspeaking' }, { role: 'stopspeaking' }] },
181 | // );
182 |
183 | // Window menu
184 | template[4].submenu = [
185 | { role: 'close' },
186 | { role: 'minimize' },
187 | { role: 'zoom' },
188 | { type: 'separator' },
189 | { role: 'front' },
190 | ];
191 | }
192 |
193 | const menu = Menu.buildFromTemplate(template);
194 | Menu.setApplicationMenu(menu);
195 |
196 | // Emitted when the window is closed.
197 | mainWindow.on('closed', () => {
198 | // Dereference the window object, usually you would store windows
199 | // in an array if your app supports multi windows, this is the time
200 | // when you should delete the corresponding element.
201 | mainWindow = null;
202 | });
203 | };
204 |
205 | // This method will be called when Electron has finished
206 | // initialization and is ready to create browser windows.
207 | // Some APIs can only be used after this event occurs.
208 | app.on('ready', () => {
209 | if (isDev) {
210 | const {
211 | default: installExtension,
212 | REACT_DEVELOPER_TOOLS,
213 | REDUX_DEVTOOLS,
214 | } = require('electron-devtools-installer');
215 |
216 | installExtension([REACT_DEVELOPER_TOOLS, REDUX_DEVTOOLS])
217 | .then(() => {
218 | createWindow();
219 | })
220 | .catch(err => err);
221 | } else {
222 | createWindow();
223 | }
224 | });
225 |
226 | // Quit when all windows are closed.
227 | app.on('window-all-closed', () => {
228 | // On OS X it is common for applications and their menu bar
229 | // to stay active until the user quits explicitly with Cmd + Q
230 | if (process.platform !== 'darwin') {
231 | app.quit();
232 | }
233 | });
234 |
235 | app.on('activate', () => {
236 | // On OS X it's common to re-create a window in the app when the
237 | // dock icon is clicked and there are no other windows open.
238 | if (mainWindow === null) {
239 | createWindow();
240 | }
241 | });
242 | }
243 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "preducks",
3 | "version": "",
4 | "description": "prototyping tool for react + redux + typescript applications",
5 | "author": "preducks",
6 | "main": "main.js",
7 | "contributors": [
8 | "jacob richards",
9 | "max gonzalez",
10 | "will napier"
11 | ],
12 | "engines": {
13 | "node": "12.x"
14 | },
15 | "repository": {
16 | "type": "git",
17 | "url": "https://github.com/oslabs-beta/preducks"
18 | },
19 | "build": {
20 | "appId": "com.team-preducks.preducks",
21 | "copyright": "Copyright © 2019",
22 | "linux": {
23 | "target": [
24 | "AppImage",
25 | "deb"
26 | ],
27 | "maintainer": "sjaco"
28 | },
29 | "mac": {
30 | "category": "public.app-category.developer-tools",
31 | "target": "dmg"
32 | },
33 | "nsis": {
34 | "createStartMenuShortcut": true,
35 | "createDesktopShortcut": true,
36 | "runAfterFinish": true
37 | },
38 | "win": {
39 | "target": "nsis-web"
40 | },
41 | "files": [
42 | "main.js",
43 | "build/electron"
44 | ],
45 | "productName": "preducks",
46 | "dmg": {
47 | "contents": [
48 | {
49 | "x": 110,
50 | "y": 150
51 | },
52 | {
53 | "x": 240,
54 | "y": 150,
55 | "type": "link",
56 | "path": "/Applications"
57 | }
58 | ]
59 | }
60 | },
61 | "scripts": {
62 | "prestart": "cross-env NODE_ENV=production webpack --config webpack.prod.js",
63 | "start": "cross-env NODE_ENV=production",
64 | "dev-electron": "cross-env NODE_ENV=development webpack --target electron-main --open --config webpack.dev.js --watch & cross-env NODE_ENV=development electron .",
65 | "build-electron": "rm -rf ./build && cross-env NODE_ENV=production webpack --target electron-renderer --config webpack.prod.js",
66 | "build-electron-bin": "electron-builder -mwl",
67 | "dev-web": "cross-env NODE_ENV=development webpack-dev-server --target web --open --config webpack.dev.js --watch",
68 | "build-web": "rm -rf ./build && cross-env NODE_ENV=production webpack --target web --config webpack.prod.js",
69 | "test": "cross-env NODE_ENV=test jest --verbose --runInBand",
70 | "refresh": "rm -rf ./node_modules ./build && npm install"
71 | },
72 | "bin": {
73 | "preducks": "./src/index.js"
74 | },
75 | "preferGlobal": true,
76 | "license": "MIT",
77 | "dependencies": {
78 | "@material-ui/core": "^3.9.3",
79 | "@material-ui/icons": "^2.0.0",
80 | "@types/react": "^16.8.14",
81 | "@types/react-dom": "^16.8.4",
82 | "@types/react-redux": "^7.0.8",
83 | "babel-jest": "^24.8.0",
84 | "babel-polyfill": "^6.26.0",
85 | "clean-webpack-plugin": "^3.0.0",
86 | "copy-webpack-plugin": "^5.0.3",
87 | "d3": "^5.9.7",
88 | "extract-text-webpack-plugin": "^4.0.0-beta.0",
89 | "file-saver": "^2.0.2",
90 | "html-webpack-plugin": "^3.1.0",
91 | "is-electron": "^2.2.0",
92 | "jszip": "^3.2.2",
93 | "localforage": "^1.7.2",
94 | "lodash.throttle": "^4.1.1",
95 | "minimist": "^1.2.0",
96 | "prettier": "^1.18.2",
97 | "prop-types": "^15.6.2",
98 | "react": "^16.8.6",
99 | "react-d3-tree": "^1.13.0",
100 | "react-dom": "^16.8.6",
101 | "react-redux": "^7.1.0",
102 | "react-syntax-highlighter": "^11.0.1",
103 | "redux": "^4.0.1",
104 | "redux-devtools-extension": "^2.13.8",
105 | "redux-thunk": "^2.3.0",
106 | "webpack": "^4.35.2",
107 | "webpack-merge": "^4.2.1",
108 | "webpack-target-electron-renderer": "^0.4.0"
109 | },
110 | "devDependencies": {
111 | "electron": "^5.0.8",
112 | "electron-devtools-installer": "^2.2.4",
113 | "electron-prebuilt": "^1.4.13",
114 | "@babel/preset-typescript": "^7.3.3",
115 | "@types/enzyme": "^3.10.2",
116 | "@types/jest": "^24.0.15",
117 | "@typescript-eslint/parser": "^1.11.0",
118 | "babel-core": "^6.26.3",
119 | "babel-eslint": "^8.2.6",
120 | "babel-loader": "^7.1.4",
121 | "babel-preset-env": "^1.6.1",
122 | "babel-preset-react": "^6.24.1",
123 | "babel-preset-stage-0": "^6.24.1",
124 | "cross-env": "^5.2.0",
125 | "css-loader": "^0.28.11",
126 | "electron-builder": "^21.1.5",
127 | "electron-installer-dmg": "^3.0.0",
128 | "electron-reload": "^1.4.1",
129 | "enzyme": "^3.10.0",
130 | "enzyme-adapter-react-16": "^1.14.0",
131 | "enzyme-to-json": "^3.3.5",
132 | "eslint": "^4.19.1",
133 | "eslint-config-airbnb-base": "^13.0.0",
134 | "eslint-plugin-babel": "^5.1.0",
135 | "eslint-plugin-import": "^2.13.0",
136 | "eslint-plugin-jest": "^21.21.0",
137 | "eslint-plugin-jsx-a11y": "^6.1.1",
138 | "eslint-plugin-react": "^7.10.0",
139 | "identity-obj-proxy": "^3.0.0",
140 | "jest": "^23.6.0",
141 | "ts-jest": "^24.0.2",
142 | "typescript": "^3.4.4",
143 | "webpack-cli": "^3.3.5",
144 | "webpack-dev-server": "^3.7.2"
145 | }
146 | }
147 |
--------------------------------------------------------------------------------
/preducks-logo-text.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Artboard
5 | Created with Sketch.
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/preducks-logo-text@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/preducks/1ef20cbca94cfebdc62ef99a86292269a7275052/preducks-logo-text@2x.png
--------------------------------------------------------------------------------
/src/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/preducks/1ef20cbca94cfebdc62ef99a86292269a7275052/src/.DS_Store
--------------------------------------------------------------------------------
/src/actionTypes/index.js:
--------------------------------------------------------------------------------
1 | // add new action types ADD_SELECTOR, DELETE_SELECTOR, ADD_ACTION, DELETE_ACTION
2 |
3 | export const LOAD_INIT_DATA = 'LOAD_INIT_DATA';
4 | export const ADD_COMPONENT = 'ADD_COMPONENT';
5 | export const ADD_CHILD = 'ADD_CHILD';
6 | export const DELETE_CHILD = 'DELETE_CHILD';
7 | export const UPDATE_COMPONENT = 'UPDATE_COMPONENT';
8 | export const DELETE_COMPONENT = 'DELETE_COMPONENT';
9 | export const CHANGE_FOCUS_COMPONENT = 'CHANGE_FOCUS_COMPONENT';
10 | export const CHANGE_COMPONENT_FOCUS_CHILD = 'CHANGE_COMPONENT_FOCUS_CHILD';
11 | export const CHANGE_FOCUS_CHILD = 'CHANGE_FOCUS_CHILD';
12 | export const UPDATE_CHILDREN = 'UPDATE_CHILDREN';
13 | export const REASSIGN_PARENT = 'REASSIGN_PARENT';
14 | export const SET_SELECTABLE_PARENTS = 'SET_SELECTABLE_PARENTS';
15 | export const EXPORT_FILES = 'EXPORT_FILES';
16 | export const EXPORT_FILES_SUCCESS = 'EXPORT_FILES_SUCCESS';
17 | export const EXPORT_FILES_ERROR = 'EXPORT_FILES_ERROR';
18 | export const HANDLE_CLOSE = 'HANDLE_CLOSE';
19 | export const HANDLE_TRANSFORM = 'HANDLE_TRANSFORM';
20 | export const CREATE_APPLICATION = 'CREATE_APPLICATION';
21 | export const CREATE_APPLICATION_SUCCESS = 'CREATE_APPLICATION_SUCCESS';
22 | export const CREATE_APPLICATION_ERROR = 'CREATE_APPLICATION_ERROR';
23 | export const MOVE_TO_BOTTOM = 'MOVE_TO_BOTTOM';
24 | export const MOVE_TO_TOP = 'MOVE_TO_TOP';
25 | export const OPEN_EXPANSION_PANEL = 'OPEN_EXPANSION_PANEL';
26 | export const DELETE_PROP = 'DELETE_PROP';
27 | export const ADD_PROP = 'ADD_PROP';
28 | export const DELETE_ALL_DATA = 'DELETE_ALL_DATA';
29 | export const CHANGE_IMAGE_PATH = 'CHANGE_IMAGE_PATH';
30 | export const UPDATE_HTML_ATTR = 'UPDATE_HTML_ATTR';
31 | export const UPDATE_CHILDREN_SORT = 'UPDATE_CHILDREN_SORT';
32 | export const ADD_SELECTOR = 'ADD_SELECTOR';
33 | export const DELETE_SELECTOR = 'DELETE_SELECTOR';
34 | export const ADD_ACTION_TO_COMPONENT = 'ADD_ACTION_TO_COMPONENT';
35 | export const DELETE_ACTION_FROM_COMPONENT = 'DELETE_ACTION_FROM_COMPONENT';
36 | export const SET_REDUCER = 'SET_REDUCER';
37 | export const DELETE_REDUCER = 'DELETE_REDUCER';
38 | // export const RENAME_REDUCER = 'RENAME_REDUCER';
39 | export const SET_INTERFACE = 'SET_INTERFACE';
40 | export const DELETE_INTERFACE = 'DELETE_INTERFACE';
41 | // export const RENAME_INTERFACE = 'RENAME_INTERFACE';
42 | export const SET_STATE = 'SET_STATE';
43 | export const DELETE_STATE = 'DELETE_STATE';
44 | // export const RENAME_STATE = 'RENAME_STATE';
45 |
--------------------------------------------------------------------------------
/src/actions/components.ts:
--------------------------------------------------------------------------------
1 | import JSZip from 'jszip';
2 | import FileSaver from 'file-saver';
3 | import {
4 | ComponentInt,
5 | ComponentsInt,
6 | PropInt,
7 | ChildInt,
8 | InterfacesInterface,
9 | ReducersInterface,
10 | StoreConfigInterface,
11 | ComponentStateInterface,
12 | } from '../utils/InterfaceDefinitions';
13 | import {
14 | LOAD_INIT_DATA,
15 | ADD_COMPONENT,
16 | ADD_CHILD,
17 | DELETE_CHILD,
18 | DELETE_COMPONENT,
19 | CHANGE_FOCUS_COMPONENT,
20 | CHANGE_FOCUS_CHILD,
21 | CHANGE_COMPONENT_FOCUS_CHILD,
22 | EXPORT_FILES,
23 | EXPORT_FILES_SUCCESS,
24 | EXPORT_FILES_ERROR,
25 | HANDLE_CLOSE,
26 | HANDLE_TRANSFORM,
27 | CREATE_APPLICATION,
28 | CREATE_APPLICATION_SUCCESS,
29 | CREATE_APPLICATION_ERROR,
30 | OPEN_EXPANSION_PANEL,
31 | DELETE_PROP,
32 | ADD_PROP,
33 | DELETE_ALL_DATA,
34 | UPDATE_HTML_ATTR,
35 | UPDATE_CHILDREN_SORT,
36 | ADD_SELECTOR,
37 | DELETE_SELECTOR,
38 | ADD_ACTION_TO_COMPONENT,
39 | DELETE_ACTION_FROM_COMPONENT,
40 | SET_REDUCER,
41 | DELETE_REDUCER,
42 | // RENAME_REDUCER,
43 | SET_INTERFACE,
44 | DELETE_INTERFACE,
45 | // RENAME_INTERFACE,
46 | SET_STATE,
47 | DELETE_STATE,
48 | // RENAME_STATE,
49 | } from '../actionTypes/index.js';
50 |
51 | import { loadState } from '../localStorage';
52 | import createComponentFiles from '../utils/createComponentFiles.util';
53 | import createApplicationUtil from '../utils/createApplication.util';
54 |
55 | const zip = new JSZip();
56 |
57 | export const loadInitData = () => (dispatch: any) => {
58 | loadState().then((data: any) => dispatch({
59 | type: LOAD_INIT_DATA,
60 | payload: {
61 | data: data ? data.workspace : {},
62 | },
63 | }));
64 | };
65 |
66 | export const addComponent = ({ title }: { title: string }) => (dispatch: any) => {
67 | dispatch({ type: ADD_COMPONENT, payload: { title } });
68 | };
69 |
70 | export const addChild = ({
71 | title,
72 | childType,
73 | HTMLInfo,
74 | }: {
75 | title: string;
76 | childType: string;
77 | HTMLInfo: object;
78 | }) => (dispatch: any) => {
79 | dispatch({ type: ADD_CHILD, payload: { title, childType, HTMLInfo } });
80 | };
81 |
82 | export const deleteChild = (childToDeleteId?: number) => (dispatch: any) => {
83 | // with no payload, it will delete focusd child
84 | dispatch({ type: DELETE_CHILD, payload: childToDeleteId });
85 | };
86 |
87 | export const deleteComponent = ({
88 | componentId,
89 | stateComponents,
90 | }: {
91 | componentId: number;
92 | stateComponents: ComponentsInt;
93 | }) => (dispatch: any) => {
94 | // find all places where the "to be deleted" is a child and do what u gotta do
95 | stateComponents.forEach((parent: ComponentInt) => {
96 | parent.childrenArray
97 | .filter((child: ChildInt) => child.childComponentId === componentId)
98 | .forEach((child: ChildInt) => {
99 | dispatch({
100 | type: DELETE_CHILD,
101 | payload: {
102 | parentId: parent.id,
103 | childId: child.childId,
104 | calledFromDeleteComponent: true,
105 | },
106 | });
107 | });
108 | });
109 |
110 | // change focus to app
111 | dispatch({ type: CHANGE_FOCUS_COMPONENT, payload: { title: 'App' } });
112 | // after taking care of the children delete the component
113 | dispatch({ type: DELETE_COMPONENT, payload: { componentId } });
114 | };
115 |
116 | export const changeFocusComponent = ({ title }: { title: string }) => (dispatch: any) => {
117 | dispatch({ type: CHANGE_FOCUS_COMPONENT, payload: { title } });
118 | };
119 |
120 | // make sure childId is being sent in
121 | export const changeFocusChild = ({ childId }: { childId: number }) => (dispatch: any) => {
122 | dispatch({ type: CHANGE_FOCUS_CHILD, payload: { childId } });
123 | };
124 |
125 | export const changeComponentFocusChild = ({
126 | componentId,
127 | childId,
128 | }: {
129 | componentId: number;
130 | childId: number;
131 | }) => (dispatch: any) => {
132 | dispatch({
133 | type: CHANGE_COMPONENT_FOCUS_CHILD,
134 | payload: { componentId, childId },
135 | });
136 | };
137 |
138 | export const exportFiles = ({
139 | components,
140 | path,
141 | appName,
142 | exportAppBool,
143 | }: {
144 | components: ComponentsInt;
145 | path: string;
146 | appName: string;
147 | exportAppBool: boolean;
148 | }) => (dispatch: any) => {
149 | // this dispatch sets the global state property 'loading' to true until the createComponentFiles call resolves below
150 | // dispatch({
151 | // type: EXPORT_FILES,
152 | // });
153 |
154 | const dir = createComponentFiles(components, path, appName, exportAppBool, zip);
155 | dispatch({
156 | type: EXPORT_FILES_SUCCESS,
157 | payload: { status: true, dir: dir[0] },
158 | });
159 | zip.generateAsync({type: "blob"}).then(blob => {
160 | FileSaver.saveAs(blob, "preducksApp.zip");
161 | }, function (err) {
162 | console.log(err);
163 | });
164 |
165 | };
166 |
167 | export const handleClose = () => ({
168 | type: HANDLE_CLOSE,
169 | payload: false,
170 | });
171 |
172 | export const handleTransform = (
173 | componentId: number,
174 | childId: number,
175 | {
176 | x, y, width, height,
177 | }: { x: number; y: number; width: number; height: number },
178 | ) => ({
179 | type: HANDLE_TRANSFORM,
180 | payload: {
181 | componentId,
182 | childId,
183 | x,
184 | y,
185 | width,
186 | height,
187 | },
188 | });
189 |
190 | export const createApplication = ({
191 | path,
192 | components = [],
193 | genOption,
194 | appName = 'dope_exported_preducks_app',
195 | exportAppBool,
196 | storeConfig,
197 | }: {
198 | path: string;
199 | components: ComponentsInt;
200 | genOption: number;
201 | appName: string;
202 | exportAppBool: boolean;
203 | storeConfig: StoreConfigInterface;
204 | }) => (dispatch: any) => {
205 | if (genOption) {
206 | exportAppBool = true;
207 | dispatch({
208 | type: CREATE_APPLICATION,
209 | });
210 | createApplicationUtil({
211 | path,
212 | appName,
213 | genOption,
214 | storeConfig,
215 | zip
216 | })
217 | .then(() => {
218 | dispatch({
219 | type: CREATE_APPLICATION_SUCCESS,
220 | });
221 | dispatch(
222 | exportFiles({
223 | appName,
224 | path,
225 | components,
226 | exportAppBool,
227 | }),
228 | );
229 | })
230 | .catch(err => dispatch({
231 | type: CREATE_APPLICATION_ERROR,
232 | payload: { status: true, err },
233 | }));
234 | }
235 | };
236 |
237 | export const openExpansionPanel = (component: ComponentInt) => ({
238 | type: OPEN_EXPANSION_PANEL,
239 | payload: { component },
240 | });
241 |
242 | export const deleteAllData = () => ({
243 | type: DELETE_ALL_DATA,
244 | });
245 |
246 | export const deleteProp = (propId: number) => (dispatch: any) => {
247 | dispatch({ type: DELETE_PROP, payload: propId });
248 | };
249 |
250 | export const addProp = (prop: PropInt) => ({
251 | type: ADD_PROP,
252 | payload: { ...prop },
253 | });
254 |
255 | export const updateHtmlAttr = ({ attr, value }: { attr: string; value: string }) => (
256 | dispatch: any,
257 | ) => {
258 | dispatch({
259 | type: UPDATE_HTML_ATTR,
260 | payload: { attr, value },
261 | });
262 | };
263 |
264 | export const updateChildrenSort = ({ newSortValues }: { newSortValues: any }) => (
265 | dispatch: any,
266 | ) => {
267 | dispatch({
268 | type: UPDATE_CHILDREN_SORT,
269 | payload: { newSortValues },
270 | });
271 | };
272 |
273 | export const addSelector = (name: string) => ({
274 | type: ADD_SELECTOR,
275 | payload: name,
276 | });
277 | export const deleteSelector = (name: string) => ({
278 | type: DELETE_SELECTOR,
279 | payload: name,
280 | });
281 |
282 | export const addActionToComponent = (name: string) => ({
283 | type: ADD_ACTION_TO_COMPONENT,
284 | payload: name,
285 | });
286 |
287 | export const deleteActionFromComponent = (name: string) => ({
288 | type: DELETE_ACTION_FROM_COMPONENT,
289 | payload: name,
290 | });
291 |
292 | export const setReducer = (reducer: ReducersInterface) => ({
293 | type: SET_REDUCER,
294 | payload: reducer,
295 | });
296 |
297 | export const deleteReducer = (name: string) => ({
298 | type: DELETE_REDUCER,
299 | payload: name,
300 | });
301 |
302 | export const setInterface = (userInterface: InterfacesInterface) => ({
303 | type: SET_INTERFACE,
304 | payload: userInterface,
305 | });
306 |
307 | export const deleteInterface = (name: string) => ({
308 | type: DELETE_INTERFACE,
309 | payload: name,
310 | });
311 |
312 | export const setState = (state: ComponentStateInterface) => ({
313 | type: SET_STATE,
314 | payload: state,
315 | });
316 |
317 | export const deleteState = (name: string) => ({
318 | type: DELETE_STATE,
319 | payload: name,
320 | });
321 |
322 |
--------------------------------------------------------------------------------
/src/components/Actions.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import Checkbox from '@material-ui/core/Checkbox';
3 | import FormControlLabel from '@material-ui/core/FormControlLabel';
4 | import Icon from '@material-ui/core/Icon';
5 | import IconButton from '@material-ui/core/IconButton';
6 | import TextField from '@material-ui/core/TextField';
7 | import Tooltip from '@material-ui/core/Tooltip';
8 | import TypeSelect from './TypeSelect';
9 | import validateInput from '../utils/validateInput.util';
10 | import ErrorMessage from './ErrorMessage';
11 |
12 | const Actions = (props: any) => {
13 | const {
14 | reducer, reducers, interfaces, setReducer,
15 | } = props;
16 | const [propertyName, setPropertyName] = useState('');
17 | const [propertyIsAsync, setPropertyIsAsync] = useState(false);
18 | const [parameterName, setParameterName] = useState('');
19 | const [parameterType, setParameterType] = useState('');
20 | const [parameterIsArray, setParameterIsArray] = useState(false);
21 | const [payloadType, setPayloadType] = useState('');
22 | const [payloadIsArray, setPayloadIsArray] = useState(false);
23 |
24 | const [nameValidation, setNameValidation] = useState(validateInput(''));
25 | const [nameIsVisible, setNameVisibility] = useState(false);
26 | const [parameterNameValidation, setParameterNameValidation] = useState(validateInput(''));
27 | const [parameterNameIsVisible, setParameterNameVisiblility] = useState(false);
28 |
29 | const handleChange = (event: Event, setter: any, setValidation: any = '') => {
30 | const target: any = event.target;
31 | setter(target.type === 'checkbox' ? target.checked : target.value);
32 | if (setValidation !== '') {
33 | const result = validateInput(target.value);
34 | setValidation(result);
35 | }
36 | };
37 |
38 | const addProperty = () => {
39 | if (nameValidation.isValid && parameterNameValidation.isValid && parameterType && payloadType) {
40 | const updatedReducer = reducers[reducer];
41 | updatedReducer.actions[nameValidation.input] = {
42 | parameter: {
43 | name: parameterNameValidation.input,
44 | type: parameterType,
45 | array: parameterIsArray,
46 | },
47 | payload: {
48 | type: payloadType,
49 | array: payloadIsArray,
50 | },
51 | async: propertyIsAsync,
52 | };
53 | setReducer({ [reducer]: updatedReducer });
54 | setPropertyName('');
55 | setPropertyIsAsync(false);
56 | setParameterName('');
57 | setParameterType('');
58 | setParameterIsArray(false);
59 | setPayloadType('');
60 | setPayloadIsArray(false);
61 |
62 | setNameVisibility(false);
63 | setParameterNameVisiblility(false);
64 | } else {
65 | setNameVisibility(true);
66 | setParameterNameVisiblility(true);
67 | }
68 | };
69 |
70 | const deleteProperty = (property: string) => {
71 | const updatedReducer = reducers[reducer];
72 | delete updatedReducer.actions[property];
73 | setReducer({ [reducer]: updatedReducer });
74 | };
75 |
76 | return (
77 |
78 |
82 | Actions
83 |
84 |
85 |
86 |
87 |
88 | name
89 | async
90 | parameter
91 | parameter type
92 | parameter array
93 | payload type
94 | payload array
95 |
96 |
97 | {reducers[reducer].actions
98 | && Object.keys(reducers[reducer].actions).map(action => (
99 |
100 | {action}
101 | {reducers[reducer].actions[action].async ? '✓' : '×'}
102 | {reducers[reducer].actions[action].parameter.name}
103 | {reducers[reducer].actions[action].parameter.type}
104 | {reducers[reducer].actions[action].parameter.array ? '✓' : '×'}
105 | {reducers[reducer].actions[action].payload.type}
106 | {reducers[reducer].actions[action].payload.array ? '✓' : '×'}
107 |
108 | deleteProperty(action)}>
111 | delete
112 |
113 |
114 |
115 | ))}
116 |
117 |
118 |
119 |
213 |
214 | );
215 | };
216 |
217 | export default Actions;
218 |
--------------------------------------------------------------------------------
/src/components/App.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import '../public/styles/index.css';
3 | import AppContainer from '../containers/AppContainer';
4 |
5 | export const App: React.SFC = () => ;
6 |
7 | export default App;
8 |
--------------------------------------------------------------------------------
/src/components/BottomPanel.tsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { connect } from 'react-redux';
3 | import { handleClose } from '../actions/components';
4 | import BottomTabs from './BottomTabs';
5 | import { ComponentInt, ComponentsInt, ChildInt } from '../utils/InterfaceDefinitions';
6 |
7 | const mapDispatchToProps = (dispatch: any) => ({
8 | handleNotificationClose: () => dispatch(handleClose()),
9 | });
10 |
11 | const mapStateToProps = (store: any) => ({
12 | focusChild: store.workspace.focusChild,
13 | components: store.workspace.components,
14 | });
15 |
16 | interface PropsInt {
17 | focusChild: ChildInt;
18 | components: ComponentsInt;
19 | focusComponent: ComponentInt;
20 | }
21 |
22 | class BottomPanel extends Component {
23 | render() {
24 | const { components, focusComponent, focusChild } = this.props;
25 |
26 | return (
27 |
28 |
33 |
34 | );
35 | }
36 | }
37 |
38 | export default connect(
39 | mapStateToProps,
40 | mapDispatchToProps,
41 | )(BottomPanel);
42 |
--------------------------------------------------------------------------------
/src/components/BottomTabs.tsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { withStyles } from '@material-ui/core/styles';
3 | import Tabs from '@material-ui/core/Tabs';
4 | import Tab from '@material-ui/core/Tab';
5 | import Tooltip from '@material-ui/core/Tooltip';
6 | import HtmlAttr from './HtmlAttr';
7 | import CodePreview from './CodePreview';
8 | import { ComponentInt, ComponentsInt, ChildInt } from '../utils/InterfaceDefinitions';
9 | import ComponentReduxSetup from './ComponentReduxSetup';
10 |
11 | interface PropsInt {
12 | focusChild: ChildInt;
13 | components: ComponentsInt;
14 | focusComponent: ComponentInt;
15 | classes: any;
16 | }
17 |
18 | // interface TreeInt {
19 | // name: string;
20 | // attributes: { [key: string]: { value: string } };
21 | // children: TreeInt[];
22 | // }
23 |
24 | const styles = (theme: any): any => ({
25 | root: {
26 | flexGrow: 1,
27 | backgroundColor: '#333333',
28 | height: '100%',
29 | color: '#fff',
30 | // boxShadow: '0 3px 6px rgba(0,0,0,0.16), 0 3px 6px rgba(0,0,0,0.23)',
31 | },
32 | tabsRoot: {
33 | borderBottom: '0.5px solid #424242',
34 | },
35 | tabsIndicator: {
36 | backgroundColor: '#1de9b6',
37 | },
38 | tabRoot: {
39 | textTransform: 'initial',
40 | minWidth: 72,
41 | fontWeight: theme.typography.fontWeightRegular,
42 | marginRight: theme.spacing.unit * 4,
43 |
44 | fontFamily: [
45 | '-apple-system',
46 | 'BlinkMacSystemFont',
47 | '"Segoe UI"',
48 | 'Roboto',
49 | '"Helvetica Neue"',
50 | 'Arial',
51 | 'sans-serif',
52 | '"Apple Color Emoji"',
53 | '"Segoe UI Emoji"',
54 | '"Segoe UI Symbol"',
55 | ].join(','),
56 | '&:hover': {
57 | color: '#1de9b6',
58 | opacity: 1,
59 | },
60 | '&$tabSelected': {
61 | color: '#33eb91',
62 | fontWeight: theme.typography.fontWeightMedium,
63 | },
64 | '&:focus': {
65 | color: '#4aedc4',
66 | },
67 | },
68 | tabSelected: {},
69 | typography: {
70 | padding: theme.spacing.unit * 3,
71 | },
72 | padding: {
73 | padding: `0 ${theme.spacing.unit * 2}px`,
74 | },
75 | });
76 |
77 | class BottomTabs extends Component {
78 | state = {
79 | value: 0,
80 | };
81 |
82 | handleChange = (event: any, value: number) => {
83 | this.setState({ value });
84 | };
85 |
86 | generateComponentTree(componentId: number, components: ComponentsInt) {
87 | const component = components.find(comp => comp.id === componentId);
88 | const tree = { name: component.title, attributes: {}, children: [] };
89 |
90 | component.childrenArray.forEach((child) => {
91 | if (child.childType === 'COMP') {
92 | tree.children.push(this.generateComponentTree(child.childComponentId, components));
93 | } else {
94 | tree.children.push({
95 | name: child.componentName,
96 | attributes: {},
97 | children: [],
98 | });
99 | }
100 | });
101 | return tree;
102 | }
103 |
104 | render() {
105 | const {
106 | classes, components, focusComponent, focusChild,
107 | } = this.props;
108 | const { value } = this.state;
109 |
110 | // display count on the tab. user can see without clicking into tab
111 | const propCount = focusComponent.props.length;
112 | const htmlAttribCount = focusComponent.childrenArray.filter(child => child.childType === 'HTML')
113 | .length;
114 |
115 | return (
116 |
117 |
121 |
126 |
130 |
135 |
136 |
140 |
145 |
146 |
147 |
148 | {value === 0 &&
}
149 | {value === 1 &&
}
150 | {value === 2 && focusChild.childType === 'HTML' &&
}
151 | {value === 2 && focusChild.childType !== 'HTML' && (
152 |
select an HTML element to view attributes
153 | )}
154 |
155 |
156 | );
157 | }
158 | }
159 |
160 | export default withStyles(styles)(BottomTabs);
161 |
--------------------------------------------------------------------------------
/src/components/CodePreview.tsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { PrismAsyncLight as SyntaxHighlighter } from 'react-syntax-highlighter';
3 | import jsx from 'react-syntax-highlighter/dist/esm/languages/prism/jsx';
4 | import { dark as style } from 'react-syntax-highlighter/dist/esm/styles/prism';
5 | import { formatter } from '../utils/formatter.util';
6 | import componentRender from '../utils/componentRender.util';
7 | import { ComponentInt, ComponentsInt } from '../utils/InterfaceDefinitions';
8 |
9 | SyntaxHighlighter.registerLanguage('jsx', jsx);
10 | type Props = {
11 | focusComponent: ComponentInt;
12 | components: ComponentsInt;
13 | };
14 |
15 | class CodePreview extends Component {
16 | render(): JSX.Element {
17 | const focusComponent: ComponentInt = this.props.focusComponent;
18 | const components: ComponentsInt = this.props.components;
19 | return (
20 |
21 |
34 | {formatter(componentRender(focusComponent, components))}
35 |
36 |
37 | );
38 | }
39 | }
40 |
41 | export default CodePreview;
42 |
--------------------------------------------------------------------------------
/src/components/ComponentReduxSetup.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { useSelector, useDispatch } from 'react-redux';
3 | import { withStyles, withTheme } from '@material-ui/core/styles';
4 | import FormControl from '@material-ui/core/FormControl';
5 | import Grid from '@material-ui/core/Grid';
6 | import Button from '@material-ui/core/Button';
7 | import Select from '@material-ui/core/Select';
8 | import InputLabel from '@material-ui/core/InputLabel';
9 | import Input from '@material-ui/core/Input';
10 | import {
11 | addSelector,
12 | deleteSelector,
13 | addActionToComponent,
14 | deleteActionFromComponent,
15 | setState,
16 | deleteState,
17 | } from '../actions/components';
18 | import DataTable from './DataTable';
19 | import { StoreInterface } from '../utils/InterfaceDefinitions';
20 |
21 | const numbersAsStrings = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'];
22 |
23 | const reservedWords = [
24 | 'break',
25 | 'case',
26 | 'catch',
27 | 'class',
28 | 'const',
29 | 'continue',
30 | 'debugger',
31 | 'default',
32 | 'delete',
33 | 'do',
34 | 'else',
35 | 'export',
36 | 'extends',
37 | 'finally',
38 | 'for',
39 | 'function',
40 | 'if',
41 | 'import',
42 | 'in',
43 | 'instanceof',
44 | 'new',
45 | 'return',
46 | 'super',
47 | 'switch',
48 | 'this',
49 | 'throw',
50 | 'try',
51 | 'typeof',
52 | 'var',
53 | 'void',
54 | 'while',
55 | 'with',
56 | 'yield',
57 | 'enum',
58 | ];
59 |
60 | const convertToOptions = choices => [
61 | ,
62 | choices.map(choice => (
63 |
64 | {choice}
65 |
66 | )),
67 | ];
68 |
69 | const ComponentReduxSetup: React.FC = (props: any): JSX.Element => {
70 | const [chosenAction, setChosenAction] = useState('');
71 | const [chosenSelector, setChosenSelector] = useState('');
72 | const [enteredName, setEnteredName] = useState('');
73 | const [enteredType, setEnteredType] = useState('');
74 | const [enteredValue, setEnteredValue] = useState('');
75 | const storeConfig = useSelector((store: StoreInterface) => store.workspace.storeConfig);
76 | const { focusComponent, classes } = props;
77 | const dispatch = useDispatch();
78 | const rowHeader = ['Actions', 'Store Selections'];
79 | let selectorOptions = [];
80 | let actionOptions = [];
81 | Object.keys(storeConfig.reducers).forEach((reducerName) => {
82 | Object.keys(storeConfig.reducers[reducerName].store).forEach((storePieceName) => {
83 | selectorOptions.push(`${reducerName}.${storePieceName}`);
84 | });
85 | Object.keys(storeConfig.reducers[reducerName].actions).forEach((actionName) => {
86 | actionOptions.push(`${reducerName}.${actionName}`);
87 | });
88 | });
89 |
90 | selectorOptions = convertToOptions(selectorOptions);
91 | actionOptions = convertToOptions(actionOptions);
92 |
93 | const handleChange = cb => e => cb(e.target.value);
94 |
95 | const handleStoreSubmit = (cb, value) => {
96 | const callback = cb;
97 | return (e) => {
98 | e.preventDefault();
99 | return dispatch(callback(value));
100 | };
101 | };
102 |
103 | const transformIntoVariableName = (string: string): string => string
104 | .replace(/[^ _$A-Za-z0-9]/g, '')
105 | .replace(/\s+(\w)/g, (match, $1) => $1.toUpperCase())
106 | .replace(/\s/g, '');
107 |
108 | const handleLocalStateSubmit = (e) => {
109 | e.preventDefault();
110 | if (numbersAsStrings.includes(enteredName[0])) {
111 | return;
112 | }
113 | if (reservedWords.includes(transformIntoVariableName(enteredName))) {
114 | return;
115 | }
116 | return dispatch(
117 | setState({
118 | name: transformIntoVariableName(enteredName),
119 | type: enteredType,
120 | initialValue: enteredValue,
121 | }),
122 | );
123 | };
124 |
125 | const editHandler = (row) => {
126 | const name = row.match(/Name: \w+/)[0].slice(6);
127 | const type = row.match(/Type: \w+/)[0].slice(6);
128 | const initialValue = row.match(/Initial Value: \w+/)[0].slice(15);
129 | dispatch(deleteState(name));
130 | setEnteredName(name);
131 | setEnteredType(type);
132 | setEnteredValue(initialValue);
133 | };
134 |
135 | const submitValueUsingAction = (title, value, onChange, onSubmit, choices) => (
136 |
137 |
163 |
164 | );
165 | return (
166 |
167 | {' '}
168 | {Object.keys(focusComponent).length < 1 ? (
169 |
170 | select a component to view its state & actions
171 |
172 | ) : (
173 |
174 | {/*
*/}
175 |
176 |
add redux connections
177 |
178 |
179 | {submitValueUsingAction(
180 | 'redux state',
181 | chosenSelector,
182 | setChosenSelector,
183 | addSelector,
184 | selectorOptions,
185 | )}
186 | dispatch(deleteSelector(name))}
190 | />
191 |
192 |
193 |
194 |
195 | {submitValueUsingAction(
196 | 'redux action',
197 | chosenAction,
198 | setChosenAction,
199 | addActionToComponent,
200 | actionOptions,
201 | )}
202 | dispatch(deleteActionFromComponent(name))}
206 | />
207 |
208 |
209 |
210 |
211 |
272 | `Name: ${state.name}. Type: ${state.type}. Initial Value: ${state.initialValue}`,
276 | )}
277 | deletePropHandler={name => dispatch(deleteState(name.match(/Name: \w+/)[0].slice(6)))}
278 | editHandler={row => editHandler(row)}
279 | />
280 |
281 | {/* */}
282 |
283 | )}
284 |
285 | );
286 | };
287 |
288 | const styles = theme => ({
289 | root: {
290 | display: 'flex',
291 | justifyContent: 'center',
292 | flexWrap: 'wrap',
293 | },
294 | chip: {
295 | color: '#eee',
296 | backgroundColor: '#333333',
297 | },
298 | column: {
299 | display: 'inline-flex',
300 | alignItems: 'baseline',
301 | },
302 | icon: {
303 | fontSize: '20px',
304 | color: '#eee',
305 | opacity: '0.7',
306 | transition: 'all .2s ease',
307 |
308 | '&:hover': {
309 | color: 'red',
310 | },
311 | },
312 | cssLabel: {
313 | color: 'white',
314 |
315 | '&$cssFocused': {
316 | color: 'green',
317 | },
318 | },
319 | cssFocused: {},
320 | input: {
321 | color: '#eee',
322 | marginBottom: '30px',
323 | width: '50%',
324 | textAlign: 'center',
325 | },
326 | light: {
327 | color: '#eee',
328 | fontSize: '14px',
329 | padding: '12px',
330 | },
331 | avatar: {
332 | color: '#eee',
333 | fontSize: '11px',
334 | },
335 | });
336 |
337 | // const Placeholder: React.FC = (props): JSX.Element => HI
;
338 | export default withStyles(styles)(ComponentReduxSetup);
339 |
--------------------------------------------------------------------------------
/src/components/DataTable.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { withStyles } from '@material-ui/core/styles';
3 | import Table from '@material-ui/core/Table';
4 | import TableBody from '@material-ui/core/TableBody';
5 | import TableCell from '@material-ui/core/TableCell';
6 | import TableHead from '@material-ui/core/TableHead';
7 | import TableRow from '@material-ui/core/TableRow';
8 | import Paper from '@material-ui/core/Paper';
9 | import DeleteIcon from '@material-ui/icons/Delete';
10 | import CreateIcon from '@material-ui/icons/Create';
11 | import IconButton from '@material-ui/core/IconButton';
12 | import uuid from 'uuid';
13 |
14 | const styles = (theme: any) => ({
15 | root: {
16 | marginTop: theme.spacing.unit * 3,
17 | margin: '10px',
18 | fontSize: '20px',
19 | borderRadius: '10px',
20 | backgroundColor: 'white',
21 | // overflowX: "auto"
22 | },
23 | table: {
24 | minWidth: 500,
25 | marginRight: '100px',
26 | fontSize: '20px',
27 | borderRadius: '10px',
28 | backgroundColor: 'white',
29 | },
30 | });
31 |
32 | /** **************************
33 | * cannot have a row header or a key in the data called "key"
34 | * ,ust have unique id
35 | * ****************************** */
36 |
37 | function dataTable(props: any) {
38 | const {
39 | classes, rowData, rowHeader, deletePropHandler, editHandler,
40 | } = props;
41 |
42 | const renderHeader = rowHeader.map((col: any, idx: number) => (
43 | {col}
44 | ));
45 |
46 | function renderRowCells(row: any) {
47 | if (!row) return;
48 | return rowHeader.map((header: string, idx: number) => (
49 |
50 | {row.toString()}
51 |
52 | ));
53 | }
54 | // style={{height: 30}}
55 | const renderRows = rowData.map((row: any) => (
56 |
57 | {renderRowCells(row)}
58 |
59 | deletePropHandler(row)}>
60 |
61 |
62 | editHandler(row)}>
63 |
64 |
65 |
66 |
67 | ));
68 |
69 | return (
70 |
71 |
72 |
73 | {renderHeader}
74 |
75 | {renderRows}
76 |
77 |
78 | );
79 | }
80 |
81 | export default withStyles(styles)(dataTable);
82 |
--------------------------------------------------------------------------------
/src/components/ErrorMessage.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 |
3 | const ErrorMessage = (props: any) => {
4 |
5 | const { validation, visible } = props;
6 |
7 | return (
8 |
12 | {validation.error || ''}
13 |
14 | );
15 |
16 | };
17 |
18 | export default ErrorMessage;
--------------------------------------------------------------------------------
/src/components/HTMLComponentPanel.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Image from '@material-ui/icons/Image';
3 | import Description from '@material-ui/icons/Description';
4 | import EditAttributes from '@material-ui/icons/EditAttributes';
5 | import Link from '@material-ui/icons/Link';
6 | import List from '@material-ui/icons/List';
7 | import LocalParking from '@material-ui/icons/LocalParking';
8 | import Chip from '@material-ui/core/Chip';
9 |
10 | interface PropsInterface {
11 | addChild: any;
12 | focusComponent: any;
13 | }
14 |
15 | const HTMLComponentPanel = (props: PropsInterface) => {
16 | const { addChild } = props;
17 |
18 | const handleCreateHTMLChild = (type: string) => {
19 | addChild({ title: type, childType: type, HTMLInfo: {} });
20 | };
21 |
22 | return (
23 |
24 | add HTML element as child
25 | }
29 | onClick={() => {
30 | handleCreateHTMLChild('image');
31 | }}
32 | variant="outlined"
33 | />
34 | }
38 | onClick={() => {
39 | handleCreateHTMLChild('form');
40 | }}
41 | variant="outlined"
42 | />
43 | }
47 | onClick={() => {
48 | handleCreateHTMLChild('button');
49 | }}
50 | variant="outlined"
51 | />
52 | }
56 | onClick={() => {
57 | handleCreateHTMLChild('link');
58 | }}
59 | variant="outlined"
60 | />
61 | }
65 | onClick={() => {
66 | handleCreateHTMLChild('list');
67 | }}
68 | variant="outlined"
69 | />
70 | }
74 | onClick={() => {
75 | handleCreateHTMLChild('paragraph');
76 | }}
77 | variant="outlined"
78 | />
79 |
80 | );
81 | };
82 |
83 | export default HTMLComponentPanel;
84 |
--------------------------------------------------------------------------------
/src/components/HtmlAttr.tsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { connect } from 'react-redux';
3 | import { withStyles } from '@material-ui/core/styles';
4 | import Grid from '@material-ui/core/Grid';
5 | import TextField from '@material-ui/core/TextField';
6 | import Paper from '@material-ui/core/Paper';
7 | import Fab from '@material-ui/core/Fab';
8 | import { updateHtmlAttr } from '../actions/components';
9 | import { HTMLelements } from '../utils/htmlElements.util';
10 | import { ComponentInt, ChildInt } from '../utils/InterfaceDefinitions';
11 |
12 | interface PropsInt {
13 | updateHtmlAttr?: any;
14 | focusComponent?: ComponentInt;
15 | classes?: any;
16 | deleteProp?: any;
17 | addProp?: any;
18 | focusChild?: ChildInt;
19 | }
20 |
21 | interface StateInt {}
22 |
23 | const styles = (theme: any): any => ({
24 | root: {
25 | display: 'flex',
26 | justifyContent: 'center',
27 | flexWrap: 'wrap',
28 | background: '#007BFF',
29 | color: 'white',
30 | borderRadius: '15px',
31 | border: '2px solid white',
32 | },
33 | cssLabel: {
34 | color: 'white',
35 | },
36 | cssFocused: {
37 | color: '#5CDB95',
38 | },
39 | input: {
40 | color: 'white',
41 | opacity: '0.7',
42 | marginBottom: '15px',
43 | },
44 | });
45 |
46 | const mapDispatchToProps = (dispatch: any) => ({
47 | updateHtmlAttr: ({ attr, value }: { attr: string; value: string }) => dispatch(updateHtmlAttr({ attr, value })),
48 | });
49 |
50 | const mapStateToProps = (store: any) => ({
51 | focusComponent: store.workspace.focusComponent,
52 | focusChild: store.workspace.focusChild,
53 | });
54 |
55 | class HtmlAttr extends Component {
56 | constructor(props) {
57 | super(props);
58 | this.state = HTMLelements[this.props.focusChild.htmlElement].attributes.reduce((acc, attr) => {
59 | acc[attr] = '';
60 | return acc;
61 | }, {});
62 | }
63 |
64 | handleSave = (attr: string) => {
65 | this.props.updateHtmlAttr({ attr, value: this.state[attr] });
66 | this.setState({
67 | [attr]: '',
68 | });
69 | };
70 |
71 | handleChange = (event: any) => {
72 | this.setState({
73 | [event.target.id]: event.target.value.trim(),
74 | });
75 | };
76 |
77 | render() {
78 | const { classes, focusChild, focusComponent } = this.props;
79 | const focusChildType = focusChild.htmlElement;
80 |
81 | const HtmlForm = HTMLelements[focusChildType].attributes.map((attr: string, i: number) => (
82 |
83 |
84 |
105 |
106 |
107 | this.handleSave(attr)}>
118 | {'save'}
119 |
120 |
121 |
122 |
123 |
124 | {focusChild.HTMLInfo[attr] ? focusChild.HTMLInfo[attr] : ' no attribute assigned'}
125 |
126 |
127 |
128 |
129 | ));
130 |
131 | return (
132 |
133 |
{`${focusChildType.toLowerCase()}`}
134 | {HtmlForm}
135 |
136 | );
137 | }
138 | }
139 |
140 | export default connect(
141 | mapStateToProps,
142 | mapDispatchToProps,
143 | )(withStyles(styles)(HtmlAttr));
144 |
--------------------------------------------------------------------------------
/src/components/HtmlChild.tsx:
--------------------------------------------------------------------------------
1 | import React, { Fragment } from 'react';
2 | import { useDispatch, useSelector } from 'react-redux';
3 | import { withStyles } from '@material-ui/core/styles';
4 | import ListItem from '@material-ui/core/ListItem';
5 | import Button from '@material-ui/core/Button';
6 | import DeleteIcon from '@material-ui/icons/Delete';
7 | import { deleteChild, changeFocusChild } from '../actions/components';
8 | import { StoreInterface } from '../utils/InterfaceDefinitions';
9 |
10 | const HtmlChild: React.FC = (props: any): JSX.Element => {
11 | const { classes, childId, color } = props;
12 | const dispatch = useDispatch();
13 | const focusChildId = useSelector((store: StoreInterface) => store.workspace.focusChild).childId;
14 | const deleteButton = (
15 |
16 | {/* shows the delete button */}
17 | {
24 | dispatch(deleteChild(childId));
25 | }}>
26 |
27 |
28 |
29 | );
30 |
31 | return (
32 | dispatch(changeFocusChild({ childId }))}
35 | className="node-html-child">
36 |
39 | {props.componentName.toLowerCase()}
40 |
41 | {deleteButton}
42 |
43 | );
44 | };
45 |
46 | export default withStyles({})(HtmlChild);
47 |
--------------------------------------------------------------------------------
/src/components/Interface.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import TextField from '@material-ui/core/TextField';
3 | import IconButton from '@material-ui/core/IconButton';
4 | import Icon from '@material-ui/core/Icon';
5 | import Checkbox from '@material-ui/core/Checkbox';
6 | import FormControlLabel from '@material-ui/core/FormControlLabel';
7 | import TypeSelect from './TypeSelect';
8 | import validateInput from '../utils/validateInput.util';
9 | import ErrorMessage from './ErrorMessage';
10 | import StoreItemHeader from './StoreItemHeader';
11 |
12 | const Interface = (props: any) => {
13 | const {
14 | thisInterface, interfaces, setInterface, deleteInterface,
15 | } = props;
16 | const [newPropertyName, setNewPropertyName] = useState('');
17 | const [newPropertyType, setNewPropertyType] = useState('');
18 | const [newPropertyIsArray, setNewPropertyIsArray] = useState(false);
19 | const [newPropertyValidation, setNewPropertyValidation] = useState(validateInput(''));
20 | const [isVisible, setVisibility] = useState(false);
21 |
22 | const handleChange = (event: Event, setter: any) => {
23 | const target: HTMLInputElement = event.target;
24 | setter(target.value);
25 | const result = validateInput(target.value);
26 | setNewPropertyValidation(result);
27 | };
28 |
29 | const addProperty = () => {
30 | if (newPropertyValidation.isValid && newPropertyType) {
31 | const updatedInterface = interfaces[thisInterface];
32 | updatedInterface[newPropertyValidation.input] = newPropertyType;
33 | setInterface({ [thisInterface]: updatedInterface });
34 | setNewPropertyName('');
35 | setNewPropertyType('');
36 | setNewPropertyIsArray(false);
37 | setVisibility(false);
38 | } else {
39 | setVisibility(true);
40 | }
41 | };
42 |
43 | const deleteProperty = (property) => {
44 | const updatedInterface = interfaces[thisInterface];
45 | delete updatedInterface[property];
46 | setInterface({ [thisInterface]: updatedInterface });
47 | };
48 |
49 | return (
50 |
51 |
52 |
53 | {interfaces[thisInterface]
54 | && Object.keys(interfaces[thisInterface]).map(property => (
55 |
56 |
66 |
67 | deleteProperty(property)}>
70 | delete
71 |
72 |
73 |
74 | ))}
75 |
76 |
77 |
131 |
132 | );
133 | };
134 |
135 | export default Interface;
136 |
--------------------------------------------------------------------------------
/src/components/Interfaces.tsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { connect } from 'react-redux';
3 | import TextField from '@material-ui/core/TextField';
4 | import IconButton from '@material-ui/core/IconButton';
5 | import Icon from '@material-ui/core/Icon';
6 | import Tooltip from '@material-ui/core/Tooltip';
7 | import * as actions from '../actions/components';
8 | import { InterfacesInterface, InputValidation } from '../utils/InterfaceDefinitions';
9 | import Interface from './Interface';
10 | import validateInput from '../utils/validateInput.util';
11 | import ErrorMessage from './ErrorMessage';
12 |
13 | const mapDispatchToProps = (dispatch: any) => ({
14 | setInterface: (myInterface: InterfacesInterface) => dispatch(actions.setInterface(myInterface)),
15 | deleteInterface: (interfaceName: string) => dispatch(actions.deleteInterface(interfaceName)),
16 | });
17 |
18 | const mapStateToProps = (store: any) => ({
19 | interfaces: store.workspace.storeConfig.interfaces,
20 | });
21 |
22 | interface PropsInterface {
23 | setInterface?: any;
24 | deleteInterface?: any;
25 | interfaces?: any;
26 | classes?: any;
27 | validateInput?: any;
28 | }
29 |
30 | interface StateInterface {
31 | newInterfaceValidation: InputValidation;
32 | newInterfaceNameInput: string;
33 | isVisible: boolean;
34 | }
35 |
36 | class Interfaces extends Component {
37 | constructor(props: PropsInterface) {
38 | super(props);
39 | this.state = {
40 | newInterfaceValidation: { isValid: false, input: '', error: '' },
41 | newInterfaceNameInput: '',
42 | isVisible: false,
43 | };
44 | }
45 |
46 | createInterface = () => {
47 | if (this.state.newInterfaceValidation.isValid) {
48 | const interfaceName = this.state.newInterfaceValidation.input;
49 | this.props.setInterface({ [interfaceName]: {} });
50 | this.setState({ newInterfaceNameInput: '' });
51 | this.setState({ isVisible: false });
52 | } else {
53 | this.setState({ isVisible: true });
54 | }
55 | };
56 |
57 | handleChange = (event: Event) => {
58 | if (event.target.value.length < 18) {
59 | const target: HTMLInputElement = event.target;
60 | const result: InputValidation = validateInput(target.value);
61 | this.setState({ newInterfaceNameInput: target.value, newInterfaceValidation: result });
62 | }
63 | };
64 |
65 | render() {
66 | return (
67 |
68 |
72 | Interfaces
73 |
74 |
75 | {this.props.interfaces
76 | && Object.keys(this.props.interfaces).map(thisInterface => (
77 |
84 | ))}
85 |
86 |
90 |
110 |
111 | );
112 | }
113 | }
114 |
115 | export default connect(
116 | mapStateToProps,
117 | mapDispatchToProps,
118 | )(Interfaces);
119 |
--------------------------------------------------------------------------------
/src/components/LeftColExpansionPanel.tsx:
--------------------------------------------------------------------------------
1 | import React, { Fragment } from 'react';
2 | import { withStyles } from '@material-ui/core/styles';
3 | import Typography from '@material-ui/core/Typography';
4 | import List from '@material-ui/core/List';
5 | import ListItem from '@material-ui/core/ListItem';
6 | import ListItemText from '@material-ui/core/ListItemText';
7 | import IconButton from '@material-ui/core/IconButton';
8 | import Grid from '@material-ui/core/Grid';
9 | import AddIcon from '@material-ui/icons/Add';
10 | import RemoveIcon from '@material-ui/icons/Remove';
11 | import Button from '@material-ui/core/Button';
12 | import Tooltip from '@material-ui/core/Tooltip';
13 | import uuid from 'uuid';
14 | import Collapse from '@material-ui/core/Collapse';
15 | import HtmlChild from './HtmlChild';
16 |
17 | export const LeftColExpansionPanel = (props: any) => {
18 | const {
19 | classes,
20 | focusComponent,
21 | component,
22 | addChild,
23 | deleteChild,
24 | changeFocusComponent,
25 | selectableChildren,
26 | components,
27 | deleteComponent,
28 | } = props;
29 | const { title, id, color } = component;
30 |
31 | function isFocused() {
32 | return focusComponent.id === id ? 'focused' : '';
33 | }
34 |
35 | const focusedStyle = {
36 | boxShadow: 'rgba(150, 150, 150, 0.2) 0 0 4px 2px',
37 | background: color,
38 | };
39 |
40 | const componentTitleDisplay = (
41 |
42 |
43 | {
47 | changeFocusComponent({ title });
48 | }}>
49 |
54 | {title}
55 |
56 | }
57 | />
58 |
59 |
60 |
61 | );
62 |
63 | const deleteComponentButton = (
64 |
65 | deleteComponent({
72 | componentId: id,
73 | stateComponents: components,
74 | })
75 | }
76 | style={{
77 | color: 'white',
78 | marginBottom: '10px',
79 | marginTop: '0px',
80 | marginLeft: '11px',
81 | padding: '4px',
82 | fontSize: '12px',
83 | borderRadius: '10px',
84 | border: '2px solid white',
85 | }}>
86 | Delete Component
87 |
88 |
89 | );
90 |
91 | const addAsChildButton = (
92 |
93 | {
96 | addChild({ title, childType: 'COMP' });
97 | }}>
98 |
99 |
100 |
101 | );
102 |
103 | const deleteChildButton = (
104 |
105 | {
108 | deleteChild(id);
109 | }}>
110 |
111 |
112 |
113 | );
114 | const HtmlChildrenOfFocusComponent = [];
115 | let thisComponentIsAChildOfFocusComponent = false;
116 | focusComponent.childrenArray.forEach((child) => {
117 | if (child.childType === 'HTML') {
118 | HtmlChildrenOfFocusComponent.push(
119 | ,
128 | );
129 | } else if (child.childComponentId === id) {
130 | thisComponentIsAChildOfFocusComponent = true;
131 | }
132 | });
133 |
134 | return (
135 |
136 |
137 |
138 | {componentTitleDisplay}
139 |
140 |
141 | {isFocused() ? HtmlChildrenOfFocusComponent :
}
142 |
143 |
144 | {id !== 1 && isFocused() ? deleteComponentButton :
}
145 |
146 |
147 |
148 | {id !== 1 && !isFocused() && selectableChildren.includes(id) ? addAsChildButton :
}
149 |
150 | {id !== 1
151 | && !isFocused()
152 | && selectableChildren.includes(id)
153 | && thisComponentIsAChildOfFocusComponent ? (
154 |
155 | {deleteChildButton}
156 |
157 | ) : (
158 |
159 | )}
160 |
161 | );
162 | };
163 |
164 | function styles(): any {
165 | return {
166 | root: {
167 | width: '100%',
168 | height: '100%',
169 | borderRadius: '10px',
170 | marginTop: 10,
171 | backgroundColor: '#4e4e4e',
172 | },
173 | light: {
174 | color: '#eee',
175 | '&:hover': {
176 | color: '#1de9b6',
177 | },
178 | },
179 | };
180 | }
181 |
182 | export default withStyles(styles)(LeftColExpansionPanel);
183 |
--------------------------------------------------------------------------------
/src/components/NewTreeDisplay.tsx:
--------------------------------------------------------------------------------
1 | import Tree from 'react-d3-tree';
2 | import React, { useEffect, useState } from 'react';
3 | import { withStyles } from '@material-ui/core/styles';
4 | import { ComponentInt, ComponentsInt, ChildInt } from '../utils/InterfaceDefinitions';
5 |
6 | interface TreeInt {
7 | name: string;
8 | attributes: { [key: string]: { value: string } };
9 | children: TreeInt[];
10 | }
11 |
12 | interface PropsInt {
13 | focusChild: ChildInt;
14 | components: ComponentsInt;
15 | focusComponent: ComponentInt;
16 | classes: any;
17 | }
18 |
19 | const styles = (theme: any): any => ({
20 | root: {
21 | flexGrow: 1,
22 | backgroundColor: '#333333',
23 | height: '100%',
24 | color: '#fff',
25 | boxShadow: '0 3px 6px rgba(0,0,0,0.16), 0 3px 6px rgba(0,0,0,0.23)',
26 | },
27 | tabsRoot: {
28 | borderBottom: '0.5px solid #424242',
29 | },
30 | tabsIndicator: {
31 | backgroundColor: '#1de9b6',
32 | },
33 | tabRoot: {
34 | textTransform: 'initial',
35 | minWidth: 72,
36 | fontWeight: theme.typography.fontWeightRegular,
37 | marginRight: theme.spacing.unit * 4,
38 |
39 | fontFamily: [
40 | '-apple-system',
41 | 'BlinkMacSystemFont',
42 | '"Segoe UI"',
43 | 'Roboto',
44 | '"Helvetica Neue"',
45 | 'Arial',
46 | 'sans-serif',
47 | '"Apple Color Emoji"',
48 | '"Segoe UI Emoji"',
49 | '"Segoe UI Symbol"',
50 | ].join(','),
51 | '&:hover': {
52 | color: '#1de9b6',
53 | opacity: 1,
54 | },
55 | '&$tabSelected': {
56 | color: '#33eb91',
57 | fontWeight: theme.typography.fontWeightMedium,
58 | },
59 | '&:focus': {
60 | color: '#4aedc4',
61 | },
62 | },
63 | tabSelected: {},
64 | typography: {
65 | padding: theme.spacing.unit * 3,
66 | },
67 | padding: {
68 | padding: `0 ${theme.spacing.unit * 2}px`,
69 | },
70 | });
71 |
72 | const TreeDisplay: React.FC = (props): JSX.Element => {
73 | const [translation, setTranslation] = useState({ x: 0, y: 0 });
74 | const [zoomLevel, setZoomLevel] = useState(1);
75 |
76 | let treeWrapper;
77 |
78 | const handleResize = () => {
79 | if (treeWrapper) {
80 | const treeSize = document.querySelector('g').getBBox();
81 | const container = treeWrapper.getBoundingClientRect();
82 | if (container.width < 600) {
83 | setTranslation({ x: container.width / 2, y: container.height / 2.3 });
84 | const containerToTreeHeightRatio = container.height / treeSize.height;
85 | setZoomLevel(containerToTreeHeightRatio);
86 | } else {
87 | setTranslation({ x: container.width / 2, y: container.height / 2.9 });
88 | }
89 | const containerToTreeWidthRatio = container.width / treeSize.width;
90 | setZoomLevel(containerToTreeWidthRatio);
91 | }
92 | };
93 |
94 | useEffect(() => {
95 | window.addEventListener('resize', handleResize);
96 | return () => {
97 | window.removeEventListener('resize', handleResize);
98 | };
99 | });
100 |
101 | useEffect(() => {
102 | handleResize();
103 | }, []);
104 |
105 | useEffect(() => {
106 | handleResize();
107 | }, [props.components, props.focusComponent]);
108 |
109 | return (
110 | (treeWrapper = node)}>
111 |
155 |
156 | );
157 | };
158 |
159 | export default withStyles(styles)(TreeDisplay);
160 |
161 | function generateComponentTree(componentId: number, components: ComponentsInt) {
162 | const component = components.find(comp => comp.id === componentId);
163 | const tree = {
164 | name: component.title,
165 | attributes: {},
166 | children: [],
167 | nodeSvgShape: createShape(65, component.color),
168 | };
169 | component.childrenArray.forEach((child) => {
170 | if (child.childType === 'COMP') {
171 | tree.children.push(generateComponentTree(child.childComponentId, components));
172 | } else {
173 | tree.children.push({
174 | name: child.componentName,
175 | attributes: {},
176 | children: [],
177 | nodeSvgShape: createShape(50, '#007BFF'),
178 | });
179 | }
180 | });
181 | return tree;
182 | }
183 |
184 | function createShape(size, color) {
185 | return {
186 | shape: 'circle',
187 | shapeProps: {
188 | r: size,
189 | fill: color,
190 | stroke: hexToHSL(color),
191 | },
192 | };
193 | }
194 |
195 | const randomColor = `rgb(${Math.floor(255 * Math.random())},${Math.floor(
196 | 255 * Math.random(),
197 | )},${Math.floor(255 * Math.random())})`;
198 |
199 | function hexToHSL(hex) {
200 | const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
201 | let r = parseInt(result[1], 16);
202 | let g = parseInt(result[2], 16);
203 | let b = parseInt(result[3], 16);
204 | (r /= 255), (g /= 255), (b /= 255);
205 | let max = Math.max(r, g, b),
206 | min = Math.min(r, g, b);
207 | let h = (max + min) / 2;
208 | let s = (max + min) / 2;
209 | const l = (max + min) / 2;
210 |
211 | if (max == min) {
212 | h = s = 0; // achromatic
213 | } else {
214 | const d = max - min;
215 | s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
216 | switch (max) {
217 | case r:
218 | h = (g - b) / d + (g < b ? 6 : 0);
219 | break;
220 | case g:
221 | h = (b - r) / d + 2;
222 | break;
223 | case b:
224 | h = (r - g) / d + 4;
225 | break;
226 | }
227 | h /= 6;
228 | }
229 | return `hsl(${h * 360},50%,50%)`;
230 | }
231 |
--------------------------------------------------------------------------------
/src/components/Reducer.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import Store from './Store';
3 | import Actions from './Actions';
4 | import StoreItemHeader from './StoreItemHeader';
5 |
6 | const Reducer = (props: any) => {
7 |
8 | const {
9 | reducer,
10 | reducers,
11 | interfaces,
12 | setReducer,
13 | deleteReducer,
14 | } = props;
15 |
16 | return (
17 |
30 | );
31 |
32 | };
33 |
34 | export default Reducer;
--------------------------------------------------------------------------------
/src/components/Reducers.tsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { connect } from 'react-redux';
3 | import TextField from '@material-ui/core/TextField';
4 | import IconButton from '@material-ui/core/IconButton';
5 | import Icon from '@material-ui/core/Icon';
6 | import Tooltip from '@material-ui/core/Tooltip';
7 | import * as actions from '../actions/components';
8 | import { ReducersInterface, InputValidation } from '../utils/InterfaceDefinitions';
9 | import Reducer from './Reducer';
10 | import validateInput from '../utils/validateInput.util';
11 | import ErrorMessage from './ErrorMessage';
12 |
13 | const mapDispatchToProps = (dispatch: any) => ({
14 | setReducer: (reducer: ReducersInterface) => dispatch(actions.setReducer(reducer)),
15 | deleteReducer: (reducer: string) => dispatch(actions.deleteReducer(reducer)),
16 | });
17 |
18 | const mapStateToProps = (store: any) => ({
19 | interfaces: store.workspace.storeConfig.interfaces,
20 | reducers: store.workspace.storeConfig.reducers,
21 | });
22 |
23 | interface PropsInterface {
24 | setReducer: any;
25 | deleteReducer: any;
26 | interfaces: any;
27 | reducers: any;
28 | }
29 |
30 | interface StateInterface {
31 | nameInput: string;
32 | validation: InputValidation;
33 | isVisible: boolean;
34 | }
35 |
36 | class Reducers extends Component {
37 | constructor(props: PropsInterface) {
38 | super(props);
39 | this.state = {
40 | nameInput: '',
41 | validation: validateInput(''),
42 | isVisible: false,
43 | };
44 | }
45 |
46 | handleChange = (event: Event) => {
47 | if (event.target.value.length < 18) {
48 | const target: HTMLInputElement = event.target;
49 | const result: InputValidation = validateInput(target.value);
50 | this.setState({ nameInput: target.value, validation: result });
51 | }
52 | };
53 |
54 | createReducer = () => {
55 | if (this.state.validation.isValid) {
56 | const reducerName = this.state.validation.input;
57 | this.props.setReducer({ [reducerName]: { store: {}, actions: {} } });
58 | this.setState({ nameInput: '' });
59 | this.setState({ isVisible: false });
60 | } else {
61 | this.setState({ isVisible: true });
62 | }
63 | };
64 |
65 | render() {
66 | return (
67 |
68 |
69 | Reducers
70 |
71 |
72 | {this.props.reducers
73 | && Object.keys(this.props.reducers).map(reducer => (
74 |
82 | ))}
83 |
84 |
85 |
105 |
106 | );
107 | }
108 | }
109 |
110 | export default connect(
111 | mapStateToProps,
112 | mapDispatchToProps,
113 | )(Reducers);
114 |
--------------------------------------------------------------------------------
/src/components/RightPanel.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Interfaces from './Interfaces';
3 | import Reducers from './Reducers';
4 | import validateInput from '../utils/validateInput.util';
5 |
6 | const RightPanel = (props: any) => {
7 | const handleChange = (event: Event, setter: any, setValidation: any = '') => {
8 | const target: any = event.target;
9 | setter(target.type === 'checkbox' ? target.checked : target.value);
10 | if (setValidation !== '') {
11 | const result = validateInput(target.value);
12 | setValidation(result);
13 | }
14 | };
15 |
16 | return (
17 |
18 |
19 |
20 |
21 | );
22 | };
23 |
24 | export default RightPanel;
25 |
--------------------------------------------------------------------------------
/src/components/SimpleModal.tsx:
--------------------------------------------------------------------------------
1 | import React, { Fragment } from 'react';
2 | import Modal from '@material-ui/core/Modal';
3 | import { withStyles } from '@material-ui/core/styles';
4 | import Typography from '@material-ui/core/Typography';
5 | import Button from '@material-ui/core/Button';
6 | import IconButton from '@material-ui/core/IconButton';
7 | import CloseIcon from '@material-ui/icons/Close';
8 |
9 | const styles = (theme: any): any => ({
10 | paper: {
11 | position: 'absolute',
12 | width: 'auto',
13 | maxWidth: '500px',
14 | height: 'auto',
15 | maxHeight: '300px',
16 | backgroundColor: '#FFCB9A',
17 | boxShadow: theme.shadows[5],
18 | padding: '4%',
19 | minWidth: '500px',
20 | minHeight: '300px',
21 | borderRadius: '10px',
22 | },
23 | button: {
24 | marginTop: '0%',
25 | height: 'auto',
26 | marginLeft: '3%',
27 | borderRadius: '4px',
28 | float: 'right',
29 | },
30 | });
31 |
32 | const SimpleModal = (props: any) => {
33 | const {
34 | classes,
35 | open,
36 | message,
37 | primBtnLabel,
38 | secBtnLabel,
39 | primBtnAction,
40 | secBtnAction,
41 | closeModal,
42 | children = null,
43 | } = props;
44 |
45 | return (
46 |
47 |
52 |
60 |
71 |
72 |
73 |
74 | {message}
75 |
76 |
{children}
77 |
78 | {secBtnLabel ? (
79 |
82 | {secBtnLabel}
83 |
84 | ) : null}
85 | {primBtnLabel ? (
86 |
89 | {primBtnLabel}
90 |
91 | ) : null}
92 |
93 |
94 |
95 |
96 | );
97 | };
98 |
99 | export default withStyles(styles)(SimpleModal);
100 |
--------------------------------------------------------------------------------
/src/components/Store.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import Checkbox from '@material-ui/core/Checkbox';
3 | import FormControlLabel from '@material-ui/core/FormControlLabel';
4 | import Icon from '@material-ui/core/Icon';
5 | import IconButton from '@material-ui/core/IconButton';
6 | import TextField from '@material-ui/core/TextField';
7 | import Tooltip from '@material-ui/core/Tooltip';
8 | import TypeSelect from './TypeSelect';
9 | import validateInput from '../utils/validateInput.util';
10 | import ErrorMessage from './ErrorMessage';
11 |
12 | const Store = (props: any) => {
13 | const {
14 | reducer, reducers, interfaces, setReducer,
15 | } = props;
16 | const [validation, setValidation] = useState(validateInput(''));
17 | const [propertyName, setPropertyName] = useState('');
18 | const [propertyType, setPropertyType] = useState('');
19 | const [propertyIsArray, setPropertyIsArray] = useState(false);
20 | const [propertyInitialValue, setPropertyInitialValue] = useState('');
21 | const [isVisible, setVisibility] = useState(false);
22 |
23 | const handleChange = (event: Event, setter: any) => {
24 | const target: HTMLInputElement = event.target;
25 | setter(target.type === 'checkbox' ? target.checked : target.value);
26 | if (target.type === 'text') {
27 | const result = validateInput(target.value);
28 | setValidation(result);
29 | }
30 | };
31 |
32 | const addProperty = () => {
33 | if (validation.isValid && propertyType && propertyInitialValue) {
34 | const updatedReducer = reducers[reducer];
35 | updatedReducer.store[validation.input] = {
36 | type: propertyType,
37 | array: propertyIsArray,
38 | initialValue: propertyInitialValue,
39 | };
40 | setReducer({ [reducer]: updatedReducer });
41 | setPropertyName('');
42 | setPropertyType('');
43 | setPropertyIsArray(false);
44 | setPropertyInitialValue('');
45 | setVisibility(false);
46 | } else {
47 | setVisibility(true);
48 | }
49 | };
50 |
51 | const deleteProperty = (property: string) => {
52 | const updatedReducer = reducers[reducer];
53 | delete updatedReducer.store[property];
54 | setReducer({ [reducer]: updatedReducer });
55 | };
56 |
57 | return (
58 |
59 |
63 | Store
64 |
65 |
66 |
67 |
68 |
69 | name
70 | type
71 | array
72 | initial
73 |
74 |
75 | {reducers[reducer].store
76 | && Object.keys(reducers[reducer].store).map(store => (
77 |
78 | {store}
79 | {reducers[reducer].store[store].type}
80 | {reducers[reducer].store[store].array ? '✓' : '×'}
81 | {reducers[reducer].store[store].initialValue}
82 |
83 | deleteProperty(store)}>
86 | delete
87 |
88 |
89 |
90 | ))}
91 |
92 |
93 |
94 |
95 |
146 |
147 | );
148 | };
149 |
150 | export default Store;
151 |
--------------------------------------------------------------------------------
/src/components/StoreItemHeader.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import Button from '@material-ui/core/Button';
3 | import IconButton from '@material-ui/core/IconButton';
4 | import Icon from '@material-ui/core/Icon';
5 |
6 | const StoreItemHeader = (props: any) => {
7 | const { storeItem, deleter } = props;
8 | const [dialogIsVisible, setDialogVisibility] = useState(false);
9 |
10 | const toggleVisibility = () => {
11 | setDialogVisibility(!dialogIsVisible);
12 | };
13 |
14 | return (
15 |
16 |
17 |
18 | deleter(storeItem)} className="delete">
19 | Delete “{storeItem}”
20 |
21 | toggleVisibility()}>
22 | Cancel
23 |
24 |
25 |
26 |
27 | {storeItem}
28 | toggleVisibility()}
31 | className="delete-store-item">
32 | delete
33 |
34 |
35 |
36 | );
37 | };
38 |
39 | export default StoreItemHeader;
40 |
--------------------------------------------------------------------------------
/src/components/TypeSelect.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import FormControl from '@material-ui/core/FormControl';
3 | import InputLabel from '@material-ui/core/InputLabel';
4 | import MenuItem from '@material-ui/core/MenuItem';
5 | import Select from '@material-ui/core/Select';
6 | import Input from '@material-ui/core/Input';
7 |
8 | const TypeSelect = (props: any) => {
9 | const {
10 | selectName, outer, interfaces, labelName, handleChange, value,
11 | } = props;
12 | const defaultItems = ['boolean', 'number', 'string'];
13 | const items = [...defaultItems, ...Object.keys(interfaces), 'any'];
14 |
15 | return (
16 |
17 | {labelName || 'type'}
18 | }>
23 | {items
24 | && items.map((item) => {
25 | if (outer !== item) {
26 | return (
27 |
28 | {item}
29 |
30 | );
31 | }
32 | })}
33 |
34 |
35 | );
36 | };
37 |
38 | export default TypeSelect;
39 |
--------------------------------------------------------------------------------
/src/components/theme.ts:
--------------------------------------------------------------------------------
1 | import { createMuiTheme } from '@material-ui/core/styles';
2 | import red from '@material-ui/core/colors/indigo';
3 |
4 | const theme = createMuiTheme({
5 | typography: {
6 | useNextVariants: true,
7 | },
8 | palette: {
9 | primary: {
10 | light: '#00e676',
11 | // main: '#33eb91',
12 | main: '#01d46d', // less blinding green
13 | dark: '#14a37f',
14 | contrastText: '#fff',
15 | },
16 | secondary: red,
17 | },
18 | });
19 |
20 | export default theme;
21 |
--------------------------------------------------------------------------------
/src/containers/AppContainer.tsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { connect } from 'react-redux';
3 | import { MuiThemeProvider } from '@material-ui/core/styles';
4 | import LinearProgress from '@material-ui/core/LinearProgress';
5 | import LeftContainer from './LeftContainer';
6 | import MainContainer from './MainContainer';
7 | import theme from '../components/theme';
8 | import { loadInitData } from '../actions/components';
9 | import { ComponentInt, ComponentsInt } from '../utils/InterfaceDefinitions';
10 |
11 | type Props = {
12 | components: ComponentsInt;
13 | focusComponent: ComponentInt;
14 | totalComponents: number;
15 | loading: boolean;
16 | selectableChildren: Array;
17 | loadInitData: any;
18 | };
19 |
20 | const mapStateToProps = (store: any) => ({
21 | components: store.workspace.components,
22 | totalComponents: store.workspace.totalComponents,
23 | focusComponent: store.workspace.focusComponent,
24 | loading: store.workspace.loading,
25 | selectableChildren: store.workspace.selectableChildren,
26 | });
27 |
28 | const mapDispatchToProps = { loadInitData };
29 |
30 | class AppContainer extends Component {
31 | state = {
32 | width: 25,
33 | rightColumnOpen: true,
34 | };
35 |
36 | componentDidMount() {
37 | this.props.loadInitData();
38 | }
39 |
40 | render(): JSX.Element {
41 | const {
42 | components, focusComponent, loading, selectableChildren, totalComponents,
43 | } = this.props;
44 | // const { width, rightColumnOpen } = this.state;
45 |
46 | // uses component childIds and parentIds arrays (numbers) to build component-filled children and parents arrays
47 | return (
48 |
49 |
55 |
56 | {loading ? (
57 |
63 |
64 |
65 | ) : null}
66 |
67 | );
68 | }
69 | }
70 |
71 | export default connect(
72 | mapStateToProps,
73 | mapDispatchToProps,
74 | )(AppContainer);
75 |
--------------------------------------------------------------------------------
/src/containers/LeftContainer.tsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { connect } from 'react-redux';
3 | import { compose } from 'redux';
4 | import TextField from '@material-ui/core/TextField';
5 | import Button from '@material-ui/core/Button';
6 | import AddIcon from '@material-ui/icons/Add';
7 | import Grid from '@material-ui/core/Grid';
8 | import { withStyles } from '@material-ui/core/styles';
9 | import GetAppIcon from '@material-ui/icons/GetApp';
10 | // import List from '@material-ui/core/List';
11 | // import ListItem from '@material-ui/core/ListItem';
12 | // import ListItemText from '@material-ui/core/ListItemText';
13 | import Fab from '@material-ui/core/Fab';
14 | import LeftColExpansionPanel from '../components/LeftColExpansionPanel';
15 | import HTMLComponentPanel from '../components/HTMLComponentPanel';
16 | import * as actions from '../actions/components';
17 | import {
18 | ComponentInt,
19 | ComponentsInt,
20 | StoreConfigInterface,
21 | StoreInterface,
22 | } from '../utils/InterfaceDefinitions';
23 | import createModal from '../utils/createModal.util';
24 | import cloneDeep from '../utils/cloneDeep';
25 |
26 |
27 | // /////// FOR TESTING ONLY//////////////////////////
28 |
29 | const dummyFilePath = '/Users/jacobrichards/Desktop/';
30 | // ///////////////////////////////////////////////////
31 |
32 | interface PropsInt {
33 | components: ComponentsInt;
34 | focusComponent: ComponentInt;
35 | selectableChildren: Array;
36 | storeConfig: StoreConfigInterface;
37 | classes: any;
38 | addComponent: any;
39 | addChild: any;
40 | deleteChild: any;
41 | changeFocusComponent: any;
42 | changeFocusChild: any;
43 | deleteComponent: any;
44 | createApp: any;
45 | deleteAllData: any;
46 | }
47 |
48 | interface StateInt {
49 | componentName: string;
50 | modal: any;
51 | genOptions: Array;
52 | genOption: number;
53 | }
54 |
55 | const mapStateToProps = (store: StoreInterface) => ({
56 | components: store.workspace.components,
57 | storeConfig: store.workspace.storeConfig,
58 | });
59 |
60 | const mapDispatchToProps = (dispatch: any) => ({
61 | addComponent: ({ title }: { title: string }) => dispatch(actions.addComponent({ title })),
62 | addChild: ({
63 | title,
64 | childType,
65 | HTMLInfo,
66 | }: {
67 | title: string;
68 | childType: string;
69 | HTMLInfo: object;
70 | }) => dispatch(actions.addChild({ title, childType, HTMLInfo })),
71 | deleteChild: (childId: number) => dispatch(actions.deleteChild(childId)),
72 | changeFocusComponent: ({ title }: { title: string }) => dispatch(actions.changeFocusComponent({ title })),
73 | changeFocusChild: ({ childId }: { childId: number }) => dispatch(actions.changeFocusChild({ childId })),
74 | deleteComponent: ({
75 | componentId,
76 | stateComponents,
77 | }: {
78 | componentId: number;
79 | stateComponents: ComponentsInt;
80 | }) => dispatch(actions.deleteComponent({ componentId, stateComponents })),
81 | deleteAllData: () => dispatch(actions.deleteAllData()),
82 | createApp: ({
83 | path,
84 | components,
85 | genOption,
86 | appName,
87 | exportAppBool,
88 | storeConfig,
89 | }: {
90 | path: string;
91 | components: ComponentsInt;
92 | genOption: number;
93 | appName: string;
94 | exportAppBool: boolean;
95 | storeConfig: StoreConfigInterface;
96 | }) => dispatch(
97 | actions.createApplication({
98 | path,
99 | components,
100 | genOption,
101 | appName,
102 | exportAppBool,
103 | storeConfig,
104 | }),
105 | ),
106 | });
107 |
108 | export class LeftContainer extends Component {
109 | state: StateInt;
110 |
111 | constructor(props: PropsInt) {
112 | super(props);
113 |
114 | this.state = {
115 | componentName: '',
116 | modal: null,
117 | genOptions: ['export components', 'export app files & components'],
118 | genOption: 0,
119 | };
120 | }
121 |
122 | chooseGenOptions = (genOption: number) => {
123 | // set option
124 | this.setState({ genOption });
125 | // closeModal
126 | this.closeModal();
127 | // Choose app dir
128 | const { components, storeConfig } = this.props;
129 | // const { genOption } = this.state;
130 | const appName = 'preducksApp';
131 | const exportAppBool = true;
132 | this.props.createApp({
133 | path: '',
134 | components,
135 | genOption,
136 | appName,
137 | exportAppBool,
138 | storeConfig,
139 | });
140 |
141 | };
142 |
143 | handleChange = (event: any) => {
144 | if (event.target.value.length < 18) {
145 | const newValue: string = event.target.value;
146 | this.setState({ componentName: newValue });
147 | }
148 | };
149 |
150 | handleAddComponent = () => {
151 | this.props.addComponent({ title: this.state.componentName });
152 | this.setState({
153 | componentName: '',
154 | });
155 | };
156 |
157 | closeModal = () => this.setState({ modal: null });
158 |
159 | clearWorkspace = () => {
160 | this.setState({
161 | modal: createModal({
162 | message: 'delete all data?',
163 | closeModal: this.closeModal,
164 | secBtnLabel: 'clear workspace',
165 | open: true,
166 | children: null,
167 | primBtnAction: null,
168 | primBtnLabel: null,
169 | secBtnAction: () => {
170 | this.props.deleteAllData();
171 | this.closeModal();
172 | },
173 | }),
174 | });
175 | };
176 |
177 | showGenerateAppModal = () => {
178 | const { chooseGenOptions } = this;
179 | chooseGenOptions(1);
180 | };
181 |
182 | render(): JSX.Element {
183 | const {
184 | components,
185 | deleteComponent,
186 | focusComponent,
187 | classes,
188 | addChild,
189 | deleteChild,
190 | changeFocusComponent,
191 | changeFocusChild,
192 | selectableChildren,
193 | } = this.props;
194 | const { componentName, modal } = this.state;
195 |
196 | const leftColExpansionPanels = cloneDeep(components)
197 | .sort((b: ComponentInt, a: ComponentInt) => b.id - a.id) // sort by id value of comp
198 | .map((component: ComponentInt, i: number) => (
199 |
213 | ));
214 |
215 | const addComponent = (
216 |
217 |
218 | {
226 | if (ev.key === 'Enter') {
227 | this.handleAddComponent();
228 | ev.preventDefault();
229 | }
230 | }}
231 | value={componentName}
232 | name="componentName"
233 | className={classes.light}
234 | InputProps={{
235 | className: classes.input,
236 | }}
237 | InputLabelProps={{
238 | className: classes.input,
239 | }}
240 | />
241 |
242 |
243 |
250 |
251 |
252 |
253 |
254 | );
255 |
256 | const clearAndExportButtons = (
257 |
262 |
268 |
281 |
282 | export project
283 |
284 |
285 |
291 |
303 | clear workspace
304 |
305 |
306 |
307 | );
308 |
309 | return (
310 |
311 | {addComponent}
312 |
{leftColExpansionPanels}
313 |
314 | {clearAndExportButtons}
315 | {modal}
316 |
317 | );
318 | }
319 | }
320 |
321 | function styles(): any {
322 | return {
323 | cssLabel: {
324 | color: 'white',
325 |
326 | '&$cssFocused': {
327 | color: 'green',
328 | },
329 | },
330 | cssFocused: {},
331 | input: {
332 | color: '#fff',
333 | opacity: '0.7',
334 | marginBottom: '10px',
335 | },
336 | underline: {
337 | color: 'white',
338 | '&::before': {
339 | color: 'white',
340 | },
341 | },
342 | button: {
343 | color: '#fff',
344 |
345 | '&:disabled': {
346 | color: 'grey',
347 | },
348 | },
349 | clearButton: {
350 | top: '96%',
351 | position: 'sticky!important',
352 | zIndex: '1',
353 |
354 | '&:disabled': {
355 | color: 'grey',
356 | backgroundColor: '#424242',
357 | },
358 | },
359 | };
360 | }
361 |
362 | export default compose(
363 | withStyles(styles),
364 | connect(
365 | mapStateToProps,
366 | mapDispatchToProps,
367 | ),
368 | )(LeftContainer);
369 |
--------------------------------------------------------------------------------
/src/containers/MainContainer.tsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { connect } from 'react-redux';
3 | import { MuiThemeProvider } from '@material-ui/core/styles';
4 | import RightPanel from '../components/RightPanel';
5 | import BottomPanel from '../components/BottomPanel';
6 | import theme from '../components/theme';
7 | import {
8 | handleTransform,
9 | changeFocusChild,
10 | changeComponentFocusChild,
11 | } from '../actions/components';
12 | import { ComponentInt, ComponentsInt } from '../utils/InterfaceDefinitions';
13 | import TreeDisplay from '../components/NewTreeDisplay';
14 |
15 | interface PropsInt {
16 | components?: ComponentsInt;
17 | focusComponent?: ComponentInt;
18 | classes?: any;
19 | focusChild?: any;
20 | }
21 |
22 | interface StateInt {
23 | draggable: boolean;
24 | toggleClass: boolean;
25 | scaleX: number;
26 | scaleY: number;
27 | x: number;
28 | y: number;
29 | modal: any;
30 | windowWidth: 0;
31 | windowHeight: 0;
32 | }
33 |
34 | const mapDispatchToProps = (dispatch: any) => ({
35 | handleTransformation: (
36 | componentId: number,
37 | childId: number,
38 | {
39 | x, y, width, height,
40 | }: { x: number; y: number; width: number; height: number },
41 | ) => dispatch(
42 | handleTransform(componentId, childId, {
43 | x,
44 | y,
45 | width,
46 | height,
47 | }),
48 | ),
49 | // openPanel: component => dispatch(openExpansionPanel(component)),
50 | changeFocusChild: ({ childId }: { childId: number }) => dispatch(changeFocusChild({ childId })),
51 | changeComponentFocusChild: ({ componentId, childId }: { componentId: number; childId: number }) => dispatch(changeComponentFocusChild({ componentId, childId })), // if u send no prms, function will delete focus child.
52 | });
53 |
54 | const mapStateToProps = (store: any) => ({
55 | focusComponent: store.workspace.focusComponent,
56 | focusChild: store.workspace.focusChild,
57 | components: store.workspace.components,
58 | });
59 |
60 | class MainContainer extends Component {
61 | constructor(props) {
62 | super(props);
63 | this.state = {
64 | draggable: false,
65 | toggleClass: true,
66 | scaleX: 1,
67 | scaleY: 1,
68 | x: 0,
69 | y: 0,
70 | modal: '',
71 | windowWidth: 0,
72 | windowHeight: 0,
73 | };
74 | this.updateWindowDimensions = this.updateWindowDimensions.bind(this);
75 | }
76 |
77 | componentDidMount() {
78 | this.updateWindowDimensions();
79 | window.addEventListener('resize', this.updateWindowDimensions);
80 | }
81 |
82 | componentWillUnmount() {
83 | window.removeEventListener('resize', this.updateWindowDimensions);
84 | }
85 |
86 | updateWindowDimensions() {
87 | this.setState({ windowWidth: window.innerWidth, windowHeight: window.innerHeight });
88 | }
89 |
90 | render() {
91 | const { modal } = this.state;
92 | const {
93 | components, focusComponent, focusChild, classes,
94 | } = this.props;
95 | const { main }: any = this;
96 |
97 | return (
98 |
99 |
100 | {modal}
101 |
102 |
103 |
108 |
109 | {this.state.windowWidth >= 800 ?
: ''}
110 |
111 | {this.state.windowWidth >= 800 ?
: ''}
112 |
113 |
114 | );
115 | }
116 | }
117 |
118 | export default connect(
119 | mapStateToProps,
120 | mapDispatchToProps,
121 | )(MainContainer);
122 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import 'babel-polyfill';
2 | import React from 'react';
3 | import ReactDOM from 'react-dom';
4 | import { Provider } from 'react-redux';
5 | import App from './components/App.tsx';
6 | import store from './store';
7 |
8 | ReactDOM.render(
9 |
10 |
11 | ,
12 | document.getElementById('app'),
13 | );
14 |
--------------------------------------------------------------------------------
/src/localStorage.js:
--------------------------------------------------------------------------------
1 | import localforage from 'localforage';
2 |
3 | const resetLocalForage = () => {
4 | // use this in place of saveState to fix any writing errors by refreshing localForage.
5 | localforage.setItem('state-v1.0.1', {});
6 | };
7 |
8 | export const saveState = (state) => {
9 | localforage.setItem('state-v1.0.1', state, (err) => {
10 | if (err) {
11 | console.log(`error saving state: ${err}`, state);
12 | }
13 | });
14 | };
15 | export const loadState = () => localforage.getItem('state-v1.0.1', (err) => {
16 | if (err) {
17 | console.log(`error loading state: ${err}`);
18 | }
19 | });
20 |
--------------------------------------------------------------------------------
/src/public/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Will Napier
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/src/public/README.md:
--------------------------------------------------------------------------------
1 | # preducks-website
2 | Website for preducks
3 |
--------------------------------------------------------------------------------
/src/public/_headers:
--------------------------------------------------------------------------------
1 | [[headers]]
2 | for = "*.js"
3 | [headers.values]
4 | Cache-Control = "public, max-age=604800"
--------------------------------------------------------------------------------
/src/public/icons/mac/preducks.icns:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/preducks/1ef20cbca94cfebdc62ef99a86292269a7275052/src/public/icons/mac/preducks.icns
--------------------------------------------------------------------------------
/src/public/icons/png/128x128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/preducks/1ef20cbca94cfebdc62ef99a86292269a7275052/src/public/icons/png/128x128.png
--------------------------------------------------------------------------------
/src/public/icons/png/16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/preducks/1ef20cbca94cfebdc62ef99a86292269a7275052/src/public/icons/png/16x16.png
--------------------------------------------------------------------------------
/src/public/icons/png/24x24.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/preducks/1ef20cbca94cfebdc62ef99a86292269a7275052/src/public/icons/png/24x24.png
--------------------------------------------------------------------------------
/src/public/icons/png/256x256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/preducks/1ef20cbca94cfebdc62ef99a86292269a7275052/src/public/icons/png/256x256.png
--------------------------------------------------------------------------------
/src/public/icons/png/32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/preducks/1ef20cbca94cfebdc62ef99a86292269a7275052/src/public/icons/png/32x32.png
--------------------------------------------------------------------------------
/src/public/icons/png/48x48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/preducks/1ef20cbca94cfebdc62ef99a86292269a7275052/src/public/icons/png/48x48.png
--------------------------------------------------------------------------------
/src/public/icons/png/512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/preducks/1ef20cbca94cfebdc62ef99a86292269a7275052/src/public/icons/png/512x512.png
--------------------------------------------------------------------------------
/src/public/icons/png/64x64.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/preducks/1ef20cbca94cfebdc62ef99a86292269a7275052/src/public/icons/png/64x64.png
--------------------------------------------------------------------------------
/src/public/icons/png/96x96.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/preducks/1ef20cbca94cfebdc62ef99a86292269a7275052/src/public/icons/png/96x96.png
--------------------------------------------------------------------------------
/src/public/icons/win/preducks.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/preducks/1ef20cbca94cfebdc62ef99a86292269a7275052/src/public/icons/win/preducks.ico
--------------------------------------------------------------------------------
/src/public/images/TreeImageComponent.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/preducks/1ef20cbca94cfebdc62ef99a86292269a7275052/src/public/images/TreeImageComponent.png
--------------------------------------------------------------------------------
/src/public/images/file structure photo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/preducks/1ef20cbca94cfebdc62ef99a86292269a7275052/src/public/images/file structure photo.png
--------------------------------------------------------------------------------
/src/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
11 | preducks
12 |
13 |
14 |
15 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/src/reducers/componentReducer.ts:
--------------------------------------------------------------------------------
1 | import { ComponentInt, ChildInt, ApplicationStateInt } from '../utils/InterfaceDefinitions';
2 |
3 | import * as types from '../actionTypes';
4 |
5 | import {
6 | addComponent,
7 | addChild,
8 | deleteChild,
9 | deleteComponent,
10 | changeFocusComponent,
11 | changeComponentFocusChild,
12 | changeFocusChild,
13 | exportFilesSuccess,
14 | exportFilesError,
15 | handleClose,
16 | handleTransform,
17 | openExpansionPanel,
18 | addProp,
19 | deleteProp,
20 | updateHtmlAttr,
21 | updateChildrenSort,
22 | addSelector,
23 | deleteSelector,
24 | addActionToComponent,
25 | deleteActionFromComponent,
26 | setReducer,
27 | deleteReducer,
28 | // renameReducer,
29 | setInterface,
30 | deleteInterface,
31 | // renameInterface,
32 | setState,
33 | deleteState,
34 | // renameState,
35 | } from '../utils/componentReducer.util';
36 | import cloneDeep from '../utils/cloneDeep';
37 |
38 | const appComponent: ComponentInt = {
39 | id: 1,
40 | stateful: false,
41 | componentState: [],
42 | title: 'App',
43 | color: '#FF6D00',
44 | props: [],
45 | nextPropId: 1,
46 | childrenArray: [],
47 | nextChildId: 1,
48 | focusChildId: 0,
49 | selectors: [],
50 | actions: [],
51 | };
52 |
53 | const initialApplicationFocusChild: ChildInt = {
54 | childId: 0,
55 | componentName: null,
56 | childType: null,
57 | childSort: 0,
58 | childComponentId: 0,
59 | color: null,
60 | htmlElement: null,
61 | HTMLInfo: null,
62 | };
63 |
64 | const initialApplicationState: ApplicationStateInt = {
65 | totalComponents: 1,
66 | nextId: 2,
67 | successOpen: false,
68 | errorOpen: false,
69 | focusComponent: appComponent,
70 | selectableChildren: [],
71 | ancestors: [],
72 | initialApplicationFocusChild,
73 | focusChild: cloneDeep(initialApplicationFocusChild),
74 | components: [appComponent],
75 | appDir: '',
76 | loading: false,
77 | storeConfig: { interfaces: {}, reducers: {} },
78 | };
79 |
80 | const componentReducer = (state = initialApplicationState, action: any) => {
81 | // console.log(action.type);
82 | switch (action.type) {
83 | case types.LOAD_INIT_DATA:
84 | // return { ...state };
85 | return {
86 | ...state,
87 | ...action.payload.data,
88 | loading: false,
89 | appDir: '',
90 | successOpen: false,
91 | errorOpen: false,
92 | };
93 | case types.ADD_COMPONENT:
94 | return addComponent(state, action.payload);
95 | case types.ADD_CHILD:
96 | return addChild(state, action.payload);
97 | case types.DELETE_CHILD:
98 | return deleteChild(state, action.payload);
99 | case types.DELETE_COMPONENT:
100 | return deleteComponent(state, action.payload);
101 | case types.CHANGE_FOCUS_COMPONENT:
102 | return changeFocusComponent(state, action.payload);
103 | case types.CHANGE_FOCUS_CHILD:
104 | return changeFocusChild(state, action.payload);
105 | case types.CHANGE_COMPONENT_FOCUS_CHILD:
106 | return changeComponentFocusChild(state, action.payload);
107 | case types.CREATE_APPLICATION:
108 | case types.EXPORT_FILES:
109 | return { ...state, loading: true };
110 | case types.EXPORT_FILES_SUCCESS:
111 | return exportFilesSuccess(state, action.payload);
112 | case types.CREATE_APPLICATION_ERROR:
113 | case types.EXPORT_FILES_ERROR:
114 | return exportFilesError(state, action.payload);
115 | case types.HANDLE_CLOSE:
116 | return handleClose(state, action.payload);
117 | case types.HANDLE_TRANSFORM:
118 | return handleTransform(state, action.payload);
119 | case types.OPEN_EXPANSION_PANEL:
120 | return openExpansionPanel(state, action.payload);
121 | case types.DELETE_ALL_DATA:
122 | return initialApplicationState;
123 | case types.ADD_PROP:
124 | return addProp(state, action.payload);
125 | case types.DELETE_PROP:
126 | return deleteProp(state, action.payload);
127 | case types.UPDATE_HTML_ATTR:
128 | return updateHtmlAttr(state, action.payload);
129 | case types.UPDATE_CHILDREN_SORT:
130 | return updateChildrenSort(state, action.payload);
131 | case types.ADD_SELECTOR:
132 | return addSelector(state, action.payload);
133 | case types.DELETE_SELECTOR:
134 | return deleteSelector(state, action.payload);
135 | case types.ADD_ACTION_TO_COMPONENT:
136 | return addActionToComponent(state, action.payload);
137 | case types.DELETE_ACTION_FROM_COMPONENT:
138 | return deleteActionFromComponent(state, action.payload);
139 | case types.SET_REDUCER:
140 | return setReducer(state, action.payload);
141 | case types.DELETE_REDUCER:
142 | return deleteReducer(state, action.payload);
143 | case types.SET_INTERFACE:
144 | return setInterface(state, action.payload);
145 | case types.DELETE_INTERFACE:
146 | return deleteInterface(state, action.payload);
147 | // case RENAME_INTERFACE:
148 | // return renameInterface(state, action.payload);
149 | case types.SET_STATE:
150 | return setState(state, action.payload);
151 | case types.DELETE_STATE:
152 | return deleteState(state, action.payload);
153 | // case RENAME_STATE:
154 | // return renameState(state, action.payload);
155 | default:
156 | return state;
157 | }
158 | };
159 |
160 | export default componentReducer;
161 |
--------------------------------------------------------------------------------
/src/reducers/index.js:
--------------------------------------------------------------------------------
1 | import { combineReducers } from 'redux';
2 |
3 | import componentReducer from './componentReducer.ts';
4 |
5 | const reducers = combineReducers({
6 | workspace: componentReducer,
7 | });
8 |
9 | export default reducers;
10 |
--------------------------------------------------------------------------------
/src/store.js:
--------------------------------------------------------------------------------
1 | import throttle from 'lodash.throttle';
2 | import { createStore, applyMiddleware } from 'redux';
3 | import thunk from 'redux-thunk';
4 | import reducers from './reducers/index';
5 | import { saveState } from './localStorage';
6 |
7 | const store = createStore(reducers, applyMiddleware(thunk));
8 |
9 | store.subscribe(throttle(() => saveState(store.getState()), 1000));
10 |
11 | export default store;
12 |
--------------------------------------------------------------------------------
/src/utils/InterfaceDefinitions.ts:
--------------------------------------------------------------------------------
1 | export interface PropInt {
2 | id: number;
3 | key: string;
4 | value: string;
5 | required: boolean;
6 | type: string;
7 | }
8 |
9 | export interface ChildInt {
10 | childId: number;
11 | childSort: number;
12 | childType: string;
13 | childComponentId: number;
14 | componentName: string;
15 | color: string | null; // maybe optional instead, look up null vs undefined
16 | htmlElement: string | null; // maybe should be optional instead
17 | HTMLInfo: { [index: string]: string }; // replace with HTMLinfo specifics
18 | }
19 |
20 | export interface ChildrenInt extends Array {}
21 |
22 | export interface ComponentStateInterface {
23 | name: string;
24 | type: string;
25 | initialValue: any;
26 | }
27 |
28 | export interface ComponentInt {
29 | id: number;
30 | stateful: boolean;
31 | componentState: ComponentStateInterface[];
32 | title: string;
33 | color: string;
34 | props: PropInt[];
35 | nextPropId: number;
36 | childrenArray: ChildInt[];
37 | nextChildId: number;
38 | focusChildId: number;
39 | selectors: string[];
40 | actions: string[];
41 | }
42 |
43 | export interface ComponentsInt extends Array {}
44 |
45 | export interface ApplicationStateInt {
46 | totalComponents: number;
47 | nextId: number;
48 | successOpen: boolean;
49 | errorOpen: boolean;
50 | focusComponent: ComponentInt;
51 | selectableChildren: number[];
52 | ancestors: number[];
53 | initialApplicationFocusChild: ChildInt;
54 | focusChild: ChildInt | ChildInt[] | { [key: string]: ChildInt };
55 | components: ComponentsInt;
56 | appDir: string;
57 | loading: boolean;
58 | storeConfig: StoreConfigInterface;
59 | }
60 |
61 | export interface StoreInterface {
62 | workspace: ApplicationStateInt;
63 | }
64 |
65 | export interface ActionConfigInterface {
66 | parameter: { name: string; type: string; array: boolean };
67 | payload: { type: string; array: boolean };
68 | async: boolean;
69 | }
70 |
71 | export interface StateConfigInterface {
72 | type: string;
73 | array: boolean;
74 | initialValue: any;
75 | }
76 |
77 | export interface InterfacesInterface {
78 | [key: string]: { [key: string]: string };
79 | }
80 |
81 | export interface ReducersInterface {
82 | [key: string]: {
83 | store: { [key: string]: StateConfigInterface };
84 | actions: { [key: string]: ActionConfigInterface };
85 | };
86 | }
87 |
88 | export interface StoreConfigInterface {
89 | interfaces: InterfacesInterface;
90 | reducers: ReducersInterface;
91 | }
92 |
93 | export interface InputValidation {
94 | isValid: boolean,
95 | input: string,
96 | error?: string,
97 | }
--------------------------------------------------------------------------------
/src/utils/cloneDeep.ts:
--------------------------------------------------------------------------------
1 | // index signatures
2 | function cloneDeep(value: { [key: string]: T } | T[] | T): { [key: string]: T } | T[] | T {
3 | if (Array.isArray(value)) {
4 | const result: any[] = [];
5 | value.forEach((el) => {
6 | if (typeof el === 'object') {
7 | result.push(cloneDeep(el));
8 | } else {
9 | result.push(el);
10 | }
11 | });
12 | return result;
13 | }
14 | if (typeof value === 'object' && value !== null) {
15 | const result: { [key: string]: any } = {};
16 | Object.keys(value).forEach((key) => {
17 | if (typeof value[key] === 'object') {
18 | result[key] = cloneDeep(value[key]);
19 | } else {
20 | result[key] = value[key];
21 | }
22 | });
23 | return result;
24 | }
25 | return value;
26 | }
27 |
28 | export default cloneDeep;
29 |
--------------------------------------------------------------------------------
/src/utils/colors.util.ts:
--------------------------------------------------------------------------------
1 | const colors: Array = [
2 | '#E27D60',
3 | '#E3AFBC',
4 | '#E8A87C',
5 | '#C38D9E',
6 | '#41B3A3',
7 | '#D12FA2',
8 | '#F64C72',
9 | '#DAAD86',
10 | '#8EE4AF',
11 | '#5CDB95',
12 | '#7395AE', // light grey
13 | '#b90061',
14 | '#AFD275',
15 | '#45A29E',
16 | '#D79922',
17 | '#C5CBE3', // lightish grey
18 | '#FFCB9A',
19 | '#E98074',
20 | '#8860D0',
21 | '#5AB9EA',
22 | '#5860E9',
23 | '#84CEEB',
24 | '#61892F',
25 | ];
26 | const getColor = (portion: number): string => colors[Math.floor(Math.random() * portion * colors.length)];
27 |
28 | export default getColor;
29 |
--------------------------------------------------------------------------------
/src/utils/componentRender.util.ts:
--------------------------------------------------------------------------------
1 | import {
2 | ComponentInt,
3 | ComponentsInt,
4 | ChildInt,
5 | ChildrenInt,
6 | PropInt,
7 | ComponentStateInterface,
8 | } from './InterfaceDefinitions';
9 | import cloneDeep from './cloneDeep';
10 | import store from '../store';
11 |
12 | const componentRender = (component: ComponentInt, components: ComponentsInt) => {
13 | const {
14 | childrenArray,
15 | title,
16 | props,
17 | selectors,
18 | actions,
19 | componentState,
20 | }: {
21 | childrenArray: ChildrenInt;
22 | title: string;
23 | props: PropInt[];
24 | selectors: string[];
25 | actions: string[];
26 | componentState: ComponentStateInterface[];
27 | } = component;
28 | function typeSwitcher(type: string) {
29 | switch (type) {
30 | case 'string':
31 | return 'string';
32 | case 'number':
33 | return 'number';
34 | case 'object':
35 | return 'object';
36 | case 'array':
37 | return 'any[]';
38 | case 'boolean':
39 | return 'boolean';
40 | case 'function':
41 | return '() => any';
42 | case 'node':
43 | return 'string';
44 | case 'element':
45 | return 'string';
46 | case 'tuple':
47 | return '[any]';
48 | case 'enum':
49 | return '{}';
50 | case 'any':
51 | return 'any';
52 | default:
53 | return 'any';
54 | }
55 | }
56 |
57 | function propDrillTextGenerator(child: ChildInt) {
58 | // probably don't need this
59 | if (child.childType === 'COMP') {
60 | return components
61 | .find((c: any) => c.id === child.childComponentId)
62 | .props.map((prop: PropInt) => `${prop.key}={${prop.value}}`)
63 | .join(' ');
64 | }
65 | if (child.childType === 'HTML') {
66 | const keys: string[] = Object.keys(child.HTMLInfo);
67 | return keys.map(key => `${key}={${htmlAttrSanitizer(child.HTMLInfo[key])}}`).join(' ');
68 | }
69 | return '';
70 | }
71 |
72 | function htmlAttrSanitizer(element: string) {
73 | return `'${element}'`;
74 | }
75 | function componentNameGenerator(child: ChildInt) {
76 | if (child.childType === 'HTML') {
77 | switch (child.componentName) {
78 | case 'Image':
79 | return 'img';
80 | case 'Form':
81 | return 'form';
82 | case 'Button':
83 | return 'button';
84 | case 'Link':
85 | return 'a';
86 | case 'List':
87 | return 'ul';
88 | case 'Paragraph':
89 | return 'p';
90 | default:
91 | return 'div';
92 | }
93 | } else {
94 | return child.componentName;
95 | }
96 | }
97 |
98 | const toImport = [];
99 | if (selectors.length) {
100 | toImport.push('useSelector');
101 | }
102 | if (actions.length) {
103 | toImport.push('useDispatch');
104 | }
105 |
106 | const importFromReactReduxText = toImport.length
107 | ? `import {${toImport.join(',')}} from 'react-redux'`
108 | : '';
109 |
110 | const actionsToImport = {};
111 | actions.forEach((action) => {
112 | const [reducer, actionName] = action.split('.');
113 | if (!actionsToImport[reducer]) {
114 | actionsToImport[reducer] = [actionName];
115 | } else {
116 | actionsToImport[reducer].push(actionName);
117 | }
118 | });
119 |
120 | const actionsText = Object.keys(actionsToImport).map(
121 | reducer => `import {${actionsToImport[reducer].join(',')}} from '../actions/${reducer}Actions';`,
122 | );
123 |
124 | const reservedTypeScriptTypes = [
125 | 'string',
126 | 'boolean',
127 | 'number',
128 | 'any',
129 | 'string[]',
130 | 'boolean[]',
131 | 'number[]',
132 | 'any[]',
133 | ];
134 |
135 | const listOfInterfaces = componentState.reduce((interfaces, current) => {
136 | if (!reservedTypeScriptTypes.includes(current.type) && !interfaces.includes(current.type)) {
137 | interfaces.push(current.type);
138 | }
139 | return interfaces;
140 | }, []);
141 |
142 | const state = store.getState().workspace.storeConfig.reducers;
143 | const useSelectorCalls = selectors.length
144 | ? selectors
145 | .map((selector) => {
146 | const selectorStrings = selector.split('.');
147 | const variableName = selectorStrings[0] + selectorStrings[1][0].toUpperCase() + selectorStrings[1].slice(1);
148 | const properties = selector.split('.');
149 | let returnType;
150 | if (properties.length === 2) {
151 | returnType = state[properties[0]].store[properties[1]].type;
152 | if (state[properties[0]].store[properties[1]].array) {
153 | returnType += '[]';
154 | }
155 | } else {
156 | returnType = 'any';
157 | }
158 | if (
159 | !reservedTypeScriptTypes.includes(returnType)
160 | && !listOfInterfaces.includes(returnType)
161 | ) {
162 | listOfInterfaces.push(
163 | returnType.indexOf('[') !== -1
164 | ? returnType.slice(0, returnType.length - 2)
165 | : returnType,
166 | );
167 | }
168 | return `const ${variableName} = useSelector(state => state.${selector});`;
169 | })
170 | .join('\n')
171 | : '';
172 |
173 | const interfacesToImport = listOfInterfaces.length
174 | ? `import {${listOfInterfaces.join(', ')}} from '../Interfaces'`
175 | : '';
176 |
177 | const importsText = `${
178 | componentState.length ? "import React, {useState} from 'react'" : "import React from 'react'"
179 | };
180 | ${[
181 | ...new Set(
182 | childrenArray
183 | .filter(child => child.childType !== 'HTML')
184 | .map(child => `import ${child.componentName} from './${child.componentName}';`),
185 | ),
186 | ].join('\n')}
187 | ${importFromReactReduxText}
188 | ${interfacesToImport}
189 | ${toImport.includes('useSelector') ? "import {StoreInterface} from '../reducers/index'" : ''}
190 | ${actions.length ? actionsText.join('\n') : ''}
191 | \n\n`;
192 |
193 | const childrenToRender = `
194 | ${cloneDeep(childrenArray)
195 | .sort((a: ChildInt, b: ChildInt) => a.childSort - b.childSort)
196 | .map(
197 | (child: ChildInt) => `<${componentNameGenerator(child)} ${propDrillTextGenerator(child)}/>`,
198 | )
199 | .join('\n')}` + '
';
200 |
201 | const useStateCalls = componentState.length
202 | ? componentState
203 | .map((pieceOfState: ComponentStateInterface) => {
204 | const initialValue = pieceOfState.type === 'string'
205 | ? `'${pieceOfState.initialValue}'`
206 | : pieceOfState.initialValue;
207 | return `const [${
208 | pieceOfState.name
209 | }, set${pieceOfState.name[0].toUpperCase()}${pieceOfState.name.slice(1)}] = useState<${
210 | pieceOfState.type
211 | }>(${initialValue});`;
212 | })
213 | .join('\n')
214 | : '';
215 |
216 | const functionalComponentBody = `
217 | const ${title}:React.FC = (props: any):JSX.Element => {
218 | ${useStateCalls}
219 | ${useSelectorCalls}
220 | ${actions.length ? 'const dispatch = useDispatch();' : ''}
221 | return (${childrenToRender});
222 | }
223 | export default ${title};`;
224 | return importsText + functionalComponentBody;
225 | };
226 |
227 | export default componentRender;
228 |
--------------------------------------------------------------------------------
/src/utils/createComponentFiles.util.ts:
--------------------------------------------------------------------------------
1 | import { formatter } from './formatter.util';
2 | import componentRender from './componentRender.util';
3 |
4 | const createComponentFiles = (
5 | components: any,
6 | path: string,
7 | appName: string,
8 | exportAppBool: boolean,
9 | zip: any,
10 | ) => {
11 | components.forEach((component: any) => {
12 | zip.file(
13 | `src/components/${component.title}.tsx`,
14 | formatter(componentRender(component, components)),
15 | );
16 | });
17 | return path;
18 | };
19 |
20 | export default createComponentFiles;
21 |
--------------------------------------------------------------------------------
/src/utils/createModal.util.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import SimpleModal from '../components/SimpleModal';
3 |
4 | type Props = {
5 | closeModal: any;
6 | primBtnAction: any;
7 | secBtnAction: any;
8 | open: boolean;
9 | children: any;
10 | message: string;
11 | primBtnLabel: any;
12 | secBtnLabel: any;
13 | };
14 |
15 | const createModal = ({
16 | message,
17 | closeModal,
18 | primBtnLabel,
19 | primBtnAction,
20 | secBtnAction = null,
21 | secBtnLabel = null,
22 | children = null,
23 | open = true,
24 | }: Props) => (
25 |
33 | {children}
34 |
35 | );
36 |
37 | export default createModal;
38 |
--------------------------------------------------------------------------------
/src/utils/createReduxFiles.util.ts:
--------------------------------------------------------------------------------
1 | import { formatter } from './formatter.util';
2 | import { StoreConfigInterface } from './InterfaceDefinitions';
3 |
4 | const createSharedInterfaces = (
5 | path: string,
6 | appName: string,
7 | storeConfig: StoreConfigInterface,
8 | zip: any,
9 | ): any => {
10 | const filePath: string = 'src/Interfaces.ts';
11 | const interfaceObj = storeConfig.interfaces;
12 | const interfaceObjKeys = Object.keys(interfaceObj);
13 | if (interfaceObjKeys.length === 0) return;
14 | let data = '';
15 | interfaceObjKeys.forEach((interfaceName) => {
16 | let curInterface = `export interface ${interfaceName} {\n`;
17 | Object.keys(interfaceObj[interfaceName]).forEach((property) => {
18 | const curType = interfaceObj[interfaceName][property];
19 | curInterface += `${property}: ${curType};\n`;
20 | });
21 | curInterface += '}\n\n';
22 | data += curInterface;
23 | });
24 |
25 | zip.file(filePath, formatter(data));
26 | };
27 |
28 | function createActionFiles(path, appName, storeConfig, reducerName, zip) {
29 | // CREATE ACTION TYPES STUFF //////////////
30 | const reducerObj = storeConfig.reducers[reducerName];
31 | const actionTypesFile: string = `src/actions/${reducerName}ActionTypes.ts`;
32 | const actions = Object.keys(reducerObj.actions); // action names in a list
33 |
34 | let actionInterfacesText = '';
35 | let actionCreatorsText = '';
36 | const actionInterfaceNames = [];
37 | if (actions.length === 0) return;
38 | actions.forEach((actionName, i) => {
39 | // loop through all actions and generate all text in actionTypes
40 | // and actions that depends on it.
41 | const curActionObj = reducerObj.actions[actionName];
42 | const actionInterfaceName = `${actionName}ActionInterface`;
43 | actionInterfaceNames.push(actionInterfaceName);
44 |
45 | // add an action interface
46 | const bracketsIfPayloadIsAnArray = curActionObj.payload.array ? '[]' : '';
47 | const payloadType = curActionObj.payload.type;
48 | const curActionInterface = `export interface ${actionInterfaceName}{\n
49 | type: ${reducerName}ActionTypes.${actionName};
50 | payload?: ${payloadType}${bracketsIfPayloadIsAnArray};
51 | }\n\n`;
52 | actionInterfacesText += curActionInterface;
53 |
54 | // add boilerplate for this action creator function
55 | let curActionCreatorText;
56 | const bracketsIfParamIsAnArray = curActionObj.parameter.array ? '[]' : '';
57 | const paramWithColonIfParamExists = curActionObj.parameter.name
58 | ? `${curActionObj.parameter.name} : `
59 | : '';
60 | if (curActionObj.async) {
61 | curActionCreatorText = `export const ${actionName} = (${paramWithColonIfParamExists}${curActionObj.parameter.type}${bracketsIfParamIsAnArray}) => {
62 | return async (dispatch: Dispatch) => {
63 | // your code here ! add your own payload to the dispatched action.
64 | dispatch<${actionInterfaceName}>({
65 | type: ${reducerName}ActionTypes.${actionName}
66 | });
67 | };
68 | };`;
69 | } else {
70 | curActionCreatorText = `export const ${actionName} =
71 | (${paramWithColonIfParamExists}${curActionObj.parameter.type}${bracketsIfParamIsAnArray}) :
72 | ${actionInterfaceName} => {
73 | // your code here ! add your own payload to the dispatched action.
74 | return {
75 | type: ${reducerName}ActionTypes.${actionName}
76 | };
77 | };`;
78 | }
79 |
80 | actionCreatorsText += curActionCreatorText;
81 | });
82 |
83 | // import all shared interfaces
84 | const interfaceNameArray = Object.keys(storeConfig.interfaces);
85 | const interfacesImportText = interfaceNameArray.length
86 | ? `import {${interfaceNameArray.toString()}} from '../Interfaces';\n\n`
87 | : '';
88 | // exoirt an enum with all actions
89 | const actionTypesEnumText = `export enum ${reducerName}ActionTypes{${actions.toString()}};\n\n`;
90 | const typeGuardText = `export type ${reducerName}ActionInterfaceUnion = ${actionInterfaceNames.join(
91 | '|',
92 | )};\n\n`;
93 |
94 | const firstFormat = formatter(interfacesImportText + actionInterfacesText);
95 |
96 | const secondFormat = formatter(typeGuardText);
97 |
98 | zip.file(actionTypesFile, firstFormat + actionTypesEnumText + secondFormat);
99 |
100 | // // ///// ACTIONS STUFF /////////////////////////////
101 |
102 | const actionsFile: string = `src/actions/${reducerName}Actions.ts`;
103 | // import dispatch, import the action types enum, and import ALL action interfaces
104 | const actionsImportText = `import {Dispatch} from 'redux';
105 | import {${reducerName}ActionTypes, ${actionInterfaceNames.join(',')}}
106 | from './${reducerName}ActionTypes'\n`;
107 | zip.file(actionsFile, formatter(actionsImportText + interfacesImportText + actionCreatorsText));
108 | }
109 |
110 | function createReducerFiles(path, appName, storeConfig, reducerName, zip) {
111 | const reducerFile: string = `src/reducers/${reducerName}Reducer.ts`;
112 | const currentReducerStoreSlice = storeConfig.reducers[reducerName].store;
113 | const currentReducerStoreSliceKeys = Object.keys(currentReducerStoreSlice);
114 | const numberOfActions = Object.keys(storeConfig.reducers[reducerName].actions).length;
115 | let importText = numberOfActions
116 | ? `import {${reducerName}ActionInterfaceUnion, ${reducerName}ActionTypes} from '../actions/${reducerName}ActionTypes'\n`
117 | : '';
118 | const interfaceNameArray = Object.keys(storeConfig.interfaces);
119 | importText += interfaceNameArray.length
120 | ? `import {${interfaceNameArray.toString()}} from '../Interfaces';\n\n`
121 | : '';
122 |
123 | let storeSliceInterfaceText = `export interface ${reducerName}StoreSliceInterface {`;
124 | const initialState = {};
125 | currentReducerStoreSliceKeys.forEach((storeSlicePropertyName) => {
126 | // build out interfaces for this reducer's store slices first!
127 | const storeSlicePropObj = currentReducerStoreSlice[storeSlicePropertyName];
128 | const bracketsIfPropIsAnArray = storeSlicePropObj.array ? '[]' : '';
129 | storeSliceInterfaceText += `${storeSlicePropertyName}: ${storeSlicePropObj.type}${bracketsIfPropIsAnArray};\n`;
130 | try {
131 | const replacedStr = storeSlicePropObj.initialValue
132 | .replace(/(\w+) ?(:)/g, (wholeMatch, groupOne, groupTwo) => `"${groupOne}"${groupTwo}`)
133 | .replace(/'/g, '"');
134 | initialState[storeSlicePropertyName] = JSON.parse(replacedStr);
135 | } catch (e) {
136 | initialState[storeSlicePropertyName] = storeSlicePropObj.initialValue;
137 | }
138 | });
139 | storeSliceInterfaceText += '};\n\n';
140 | const initialStateText = `const initialState: ${reducerName}StoreSliceInterface = ${JSON.stringify(
141 | initialState,
142 | )}\n\n`;
143 |
144 | let reducerText = `export const ${reducerName}Reducer =
145 | (state: ${reducerName}StoreSliceInterface = initialState, action: ${
146 | numberOfActions ? `${reducerName}ActionInterfaceUnion` : 'any'
147 | }) => {
148 | switch(action.type){\n`;
149 |
150 | Object.keys(storeConfig.reducers[reducerName].actions).forEach((actionTypeName) => {
151 | reducerText += `case ${reducerName}ActionTypes.${actionTypeName}:
152 | // your logic here!
153 | return state;\n`;
154 | });
155 | reducerText += `default:
156 | return state;
157 | }
158 | };`;
159 |
160 | zip.file(
161 | reducerFile,
162 | formatter(importText + storeSliceInterfaceText + initialStateText + reducerText),
163 | );
164 | }
165 |
166 | const createActionsAndStoresForEachReducer = (
167 | // creates an Interfaces.tsx that all other redux files will import from.
168 | path: string,
169 | appName: string,
170 | storeConfig: StoreConfigInterface,
171 | zip,
172 | ): void => {
173 | const rootReducerFile: string = 'src/reducers/index.ts';
174 | let rootReducerImportsText = "import {combineReducers} from 'redux';\n";
175 | let storeInterfaceText = 'export interface StoreInterface {\n';
176 | let combineReducersText = 'export const reducers = combineReducers({\n';
177 |
178 | const reducerNamesArray = Object.keys(storeConfig.reducers);
179 | if (reducerNamesArray.length === 0) return;
180 | reducerNamesArray.forEach((reducerName) => {
181 | rootReducerImportsText += `import {${reducerName}Reducer, ${reducerName}StoreSliceInterface } from './${reducerName}Reducer';\n`;
182 | storeInterfaceText += `${reducerName}: ${reducerName}StoreSliceInterface;\n`;
183 | combineReducersText += `${reducerName}: ${reducerName}Reducer,\n`;
184 |
185 | createActionFiles(path, appName, storeConfig, reducerName, zip);
186 | createReducerFiles(path, appName, storeConfig, reducerName, zip);
187 | });
188 | rootReducerImportsText += '\n';
189 | storeInterfaceText += '}\n\n';
190 | combineReducersText += '});\n';
191 |
192 | zip.file(
193 | rootReducerFile,
194 | formatter(rootReducerImportsText + storeInterfaceText + combineReducersText),
195 | );
196 | };
197 |
198 | export const createReduxFiles = async (
199 | // CALL THIS FUNCTION FROM OUTSIDE AND IT WILL DO THE WHOLE REDUX SETUP.
200 | // creates an Interfaces.tsx that all other redux files will import from.
201 | path: string,
202 | appName: string,
203 | storeConfig: StoreConfigInterface,
204 | zip: any,
205 | ): Promise => {
206 | createSharedInterfaces(path, appName, storeConfig, zip);
207 | createActionsAndStoresForEachReducer(path, appName, storeConfig, zip);
208 | return null;
209 | };
210 |
--------------------------------------------------------------------------------
/src/utils/dummyData.ts:
--------------------------------------------------------------------------------
1 | import { StoreConfigInterface } from './InterfaceDefinitions';
2 |
3 | export const dummyComponent = {
4 | id: 1,
5 | stateful: false,
6 | title: 'App',
7 | color: '#FF6D00',
8 | props: [],
9 | nextPropId: 2,
10 | componentState: [{ name: 'hej', type: 'string', initialValue: 'hej' }],
11 | childrenArray: [
12 | {
13 | childId: 4,
14 | childSort: 4,
15 | childType: 'COMP',
16 | childComponentId: 7,
17 | componentName: 'Board',
18 | color: null,
19 | htmlElement: null,
20 | HTMLInfo: {},
21 | },
22 | {
23 | childId: 7,
24 | childSort: 7,
25 | childType: 'HTML',
26 | childComponentId: null,
27 | componentName: 'Image',
28 | color: null,
29 | htmlElement: 'Image',
30 | HTMLInfo: {},
31 | },
32 | ],
33 | nextChildId: 10,
34 | focusChildId: 7,
35 | focusChild: {
36 | childId: 7,
37 | childSort: 7,
38 | childType: 'HTML',
39 | childComponentId: null,
40 | componentName: 'Image',
41 | color: null,
42 | htmlElement: 'Image',
43 | HTMLInfo: {},
44 | },
45 | selectors: ['reducer1.property1', 'reducer2.property2'],
46 | actions: ['action1, action2'],
47 | };
48 |
49 | export const dummyAllComponents = [
50 | {
51 | id: 1,
52 | stateful: false,
53 | title: 'App',
54 | color: '#FF6D00',
55 | props: [],
56 | nextPropId: 2,
57 | childrenArray: [
58 | {
59 | childId: 4,
60 | childSort: 4,
61 | childType: 'COMP',
62 | childComponentId: 7,
63 | componentName: 'Board',
64 | color: null,
65 | htmlElement: null,
66 | HTMLInfo: {},
67 | },
68 | {
69 | childId: 7,
70 | childSort: 7,
71 | childType: 'HTML',
72 | childComponentId: null,
73 | componentName: 'Image',
74 | color: null,
75 | htmlElement: 'Image',
76 | HTMLInfo: {},
77 | },
78 | ],
79 | nextChildId: 10,
80 | focusChildId: 7,
81 | focusChild: {
82 | childId: 7,
83 | childSort: 7,
84 | childType: 'HTML',
85 | childComponentId: null,
86 | componentName: 'Image',
87 | color: null,
88 | htmlElement: 'Image',
89 | HTMLInfo: {},
90 | },
91 | selectors: [],
92 | actions: [],
93 | },
94 | {
95 | id: 7,
96 | stateful: false,
97 | title: 'Board',
98 | color: '#E3AFBC',
99 | props: [
100 | {
101 | id: 1,
102 | key: 'isGameOver',
103 | value: 'false',
104 | required: true,
105 | type: 'boolean',
106 | },
107 | ],
108 | nextPropId: 2,
109 | childrenArray: [
110 | {
111 | childId: 2,
112 | childSort: 2,
113 | childType: 'COMP',
114 | childComponentId: 8,
115 | componentName: 'Box',
116 | color: null,
117 | htmlElement: null,
118 | HTMLInfo: {},
119 | },
120 | {
121 | childId: 6,
122 | childSort: 6,
123 | childType: 'COMP',
124 | childComponentId: 8,
125 | componentName: 'Box',
126 | color: null,
127 | htmlElement: null,
128 | HTMLInfo: {},
129 | },
130 | {
131 | childId: 5,
132 | childSort: 5,
133 | childType: 'COMP',
134 | childComponentId: 8,
135 | componentName: 'Box',
136 | color: null,
137 | htmlElement: null,
138 | HTMLInfo: {},
139 | },
140 | ],
141 | nextChildId: 8,
142 | focusChildId: 0,
143 | focusChild: {
144 | childId: 5,
145 | childSort: 5,
146 | childType: 'COMP',
147 | childComponentId: 8,
148 | componentName: 'Box',
149 | color: null,
150 | htmlElement: null,
151 | HTMLInfo: {},
152 | },
153 | selectors: [],
154 | actions: [],
155 | },
156 | {
157 | id: 8,
158 | stateful: false,
159 | title: 'Box',
160 | color: '#8860D0',
161 | props: [
162 | {
163 | id: 1,
164 | key: 'gameState',
165 | value: '["","","","","","","","",""]',
166 | required: true,
167 | type: 'array',
168 | },
169 | ],
170 | nextPropId: 2,
171 | childrenArray: [],
172 | nextChildId: 1,
173 | focusChildId: -1,
174 | selectors: ['reducer1.property1', 'reducer2.property2', 'reducer3.property3'],
175 | actions: ['delet', 'add', 'chaeng'],
176 | },
177 | ];
178 |
179 | const storeConfigTicTacToe: StoreConfigInterface = {
180 | interfaces: {},
181 | reducers: {
182 | game: {
183 | store: {
184 | boxVals: { type: 'string', array: true, initialValue: ['', '', '', '', '', '', '', ''] },
185 | isGameOver: { type: 'boolean', array: false, initialValue: false },
186 | },
187 | actions: {
188 | toggle: {
189 | parameter: { name: 'boxId', type: 'number', array: false },
190 | payload: { type: 'number', array: false },
191 | async: false,
192 | },
193 | },
194 | },
195 | },
196 | };
197 |
198 | const storeConfigTTTMultiReducer: StoreConfigInterface = {
199 | interfaces: {
200 | user: { name: 'string', password: 'string' },
201 | userAndHiScore: { user: 'user', hiScore: 'number' },
202 | },
203 | reducers: {
204 | game: {
205 | store: {
206 | boxVals: { type: 'string', array: true, initialValue: ['', '', '', '', '', '', '', ''] },
207 | isGameOver: { type: 'boolean', array: false, initialValue: false },
208 | },
209 | actions: {
210 | toggleBox: {
211 | parameter: { name: 'boxId', type: 'number', array: false },
212 | payload: { type: 'number', array: false },
213 | async: false,
214 | },
215 | },
216 | },
217 | user: {
218 | store: {
219 | otherUsers: {
220 | type: 'user',
221 | array: true,
222 | initialValue: [{ name: 'bob', password: 'dole' }],
223 | },
224 | name: { type: 'string', array: false, initialValue: 'dan' },
225 | score: { type: 'number', array: false, initialValue: 12 },
226 | },
227 | actions: {
228 | addPoint: {
229 | parameter: { name: '', type: '', array: false },
230 | payload: { type: 'number', array: false },
231 | async: false,
232 | },
233 | fetchHighScores: {
234 | parameter: { name: '', type: '', array: false },
235 | payload: { type: 'boolean', array: false },
236 | async: true,
237 | },
238 | },
239 | },
240 | },
241 | };
242 |
243 | // example for todo app
244 | const storeConfigTodo: StoreConfigInterface = {
245 | // config at the global level for redux store/actions
246 | interfaces: {
247 | todo: { id: 'number', title: 'string', completed: 'boolean' },
248 | },
249 | reducers: {
250 | todos: {
251 | store: {
252 | todoArray: { type: 'todo', array: true, initialValue: [] },
253 | allCompleted: { type: 'boolean', array: false, initialValue: false },
254 | },
255 | actions: {
256 | fetchTodos: {
257 | parameter: { name: '', type: '', array: false },
258 | payload: { type: 'todo', array: true },
259 | async: true,
260 | },
261 | deleteTodo: {
262 | parameter: { name: 'id', type: 'number', array: false },
263 | payload: { type: 'number', array: false },
264 | async: false,
265 | },
266 | },
267 | },
268 | },
269 | };
270 |
271 | // module.exports = {
272 | // dummyComponent,
273 | // dummyAllComponents,
274 | // storeConfigTTTMultiReducer,
275 | // storeConfigTicTacToe,
276 | // storeConfigTodo,
277 | // };
278 |
--------------------------------------------------------------------------------
/src/utils/formatter.util.ts:
--------------------------------------------------------------------------------
1 | import { format } from 'prettier/standalone.js';
2 | import parserTypescript from 'prettier/parser-typescript';
3 |
4 | export const formatter = text => format(
5 | text,
6 | {
7 | singleQuote: true,
8 | trailingComma: 'es5',
9 | bracketSpacing: true,
10 | jsxBracketSameLine: true,
11 | parser: 'typescript',
12 | plugins: [parserTypescript],
13 | },
14 | (err) => {
15 | if (err) {
16 | console.log('error formatting code !', err.message);
17 | }
18 | },
19 | );
20 |
--------------------------------------------------------------------------------
/src/utils/getSelectable.util.ts:
--------------------------------------------------------------------------------
1 | import { ComponentInt, ComponentsInt, ChildInt } from './InterfaceDefinitions';
2 |
3 | interface getSelectableInt {
4 | [key: string]: Array;
5 | }
6 |
7 | function getSelectable(newFocusComponent: ComponentInt, components: ComponentsInt) {
8 | const focusComponentId = newFocusComponent.id;
9 | const componentsToCheck = components
10 | .map((comp: ComponentInt) => comp.id)
11 | .filter((id: number) => id !== focusComponentId);
12 | return findAncestors(components, [focusComponentId], componentsToCheck);
13 | }
14 |
15 | function findAncestors(
16 | components: ComponentsInt,
17 | currentCompArr: number[],
18 | componentsToCheck: number[],
19 | ancestors: Array = [],
20 | ): getSelectableInt {
21 | if (!currentCompArr.length) {
22 | return {
23 | ancestors,
24 | selectableChildren: componentsToCheck,
25 | };
26 | }
27 |
28 | const newAncestors: Array = [];
29 |
30 | for (let i = 0; i < components.length; i++) {
31 | if (componentsToCheck.includes(components[i].id)) {
32 | const myChildren = components[i].childrenArray.map(
33 | (child: ChildInt) => child.childComponentId,
34 | );
35 |
36 | const found = currentCompArr.filter((comp: any) => myChildren.includes(comp));
37 |
38 | if (found.length) {
39 | ancestors.push(components[i].id);
40 | newAncestors.push(components[i].id);
41 |
42 | const indexToDelete = componentsToCheck.findIndex((c: number) => c === components[i].id);
43 |
44 | componentsToCheck.splice(indexToDelete, 1);
45 | }
46 | }
47 | }
48 | return findAncestors(components, newAncestors, componentsToCheck, ancestors);
49 | }
50 |
51 | export default getSelectable;
52 |
--------------------------------------------------------------------------------
/src/utils/htmlElements.util.ts:
--------------------------------------------------------------------------------
1 | // HTML elements that the user can choose from
2 | interface htmlElementInt {
3 | width: number;
4 | height: number;
5 | attributes: Array;
6 | }
7 |
8 | interface htmlElementsInt {
9 | [key: string]: htmlElementInt;
10 | }
11 |
12 | const HTMLelements: htmlElementsInt = {
13 | image: {
14 | width: 100,
15 | height: 100,
16 | attributes: ['className', 'id', 'src'],
17 | },
18 | button: {
19 | width: 75,
20 | height: 28,
21 | attributes: ['className', 'id', 'text'],
22 | },
23 | form: {
24 | width: 150,
25 | height: 150,
26 | attributes: ['className', 'id', 'text'],
27 | },
28 | paragraph: {
29 | width: 250,
30 | height: 75,
31 | attributes: ['className', 'id', 'text'],
32 | },
33 | list: {
34 | width: 75,
35 | height: 75,
36 | attributes: ['className', 'id', 'text'],
37 | },
38 | link: {
39 | width: 50,
40 | height: 50,
41 | attributes: ['className', 'id', 'href', 'text'],
42 | },
43 | };
44 |
45 | export { HTMLelements };
46 |
--------------------------------------------------------------------------------
/src/utils/validateInput.util.ts:
--------------------------------------------------------------------------------
1 | const validateInput = (input: string = '') => {
2 | let inputValue = input
3 | .replace(/\s([a-z])/gi, el => el.toUpperCase())
4 | .replace(/\s/g, '');
5 | let error;
6 | const regex = /^([a-z$_]{1})[a-z0-9$_]+$/i;
7 | if (inputValue === '') {
8 | return {
9 | isValid: false,
10 | input: inputValue,
11 | }
12 | }
13 | if (regex.test(inputValue)) {
14 | if (reservedWords.indexOf(inputValue) === -1) {
15 | return {
16 | isValid: true,
17 | input: inputValue,
18 | };
19 | }
20 | error = `“${inputValue}” is a reserved word`;
21 | }
22 | return {
23 | isValid: false,
24 | input: inputValue,
25 | error: error || `Invalid input “${inputValue}”`,
26 | };
27 | }
28 |
29 | const reservedWords = [
30 | 'abstract',
31 | 'arguments',
32 | 'await',
33 | 'boolean',
34 | 'break',
35 | 'byte',
36 | 'case',
37 | 'catch',
38 | 'char',
39 | 'class',
40 | 'const',
41 | 'continue',
42 | 'debugger',
43 | 'default',
44 | 'delete',
45 | 'do',
46 | 'double',
47 | 'else',
48 | 'enum',
49 | 'eval',
50 | 'export',
51 | 'extends',
52 | 'false',
53 | 'final',
54 | 'finally',
55 | 'float',
56 | 'for',
57 | 'function',
58 | 'goto',
59 | 'if',
60 | 'implements',
61 | 'import',
62 | 'in',
63 | 'instanceof',
64 | 'int',
65 | 'interface',
66 | 'let',
67 | 'long',
68 | 'native',
69 | 'new',
70 | 'null',
71 | 'package',
72 | 'private',
73 | 'protected',
74 | 'public',
75 | 'return',
76 | 'short',
77 | 'static',
78 | 'super',
79 | 'switch',
80 | 'synchronized',
81 | 'this',
82 | 'throw',
83 | 'throws',
84 | 'transient',
85 | 'true',
86 | 'try',
87 | 'typeof',
88 | 'var',
89 | 'void',
90 | 'volatile',
91 | 'while',
92 | 'with',
93 | 'yield',
94 | // Not reserved, but not suggested:
95 | 'Array',
96 | 'Date',
97 | 'eval',
98 | 'function',
99 | 'hasOwnProperty',
100 | 'Infinity',
101 | 'isFinite',
102 | 'isNaN',
103 | 'isPrototypeOf',
104 | 'length',
105 | 'Math',
106 | 'NaN',
107 | 'name',
108 | 'Number',
109 | 'Object',
110 | 'prototype',
111 | 'String',
112 | 'toString',
113 | 'undefined',
114 | 'valueOf',
115 | ];
116 |
117 | export default validateInput;
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "outDir": "./build/",
4 | "sourceMap": true,
5 | "noImplicitAny": false,
6 | "module": "commonjs",
7 | "target": "es6",
8 | "jsx": "react",
9 | "lib": ["es6", "dom", "es2017"],
10 | "allowSyntheticDefaultImports": true,
11 | "moduleResolution": "node",
12 | "baseUrl": "./src"
13 | },
14 | "include": [
15 | "./src/**/*",
16 | "__tests__/reducer.test.js",
17 | "__tests__/puppeteer.test.js"
18 | ]
19 | }
20 |
--------------------------------------------------------------------------------
/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": ["tslint:recommended", "tslint-react", "tslint-config-prettier"],
3 | "tslint.autoFixOnSave": true,
4 | "linterOptions": {
5 | "exclude": ["config/**/*.js", "node_modules/**/*.ts"]
6 | },
7 | "rules": {
8 | "quotemark": [true, "single", "avoid-escape", "avoid-template", "jsx-double"],
9 | "jsx-boolean-value": false,
10 | "jsx-no-lambda": false,
11 | "jsx-no-multiline-js": false,
12 | "object-literal-sort-keys": false,
13 | "member-ordering": false,
14 | "no-console": false,
15 | "ordered-imports": false,
16 | "comment-format": false
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/webpack.common.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const HtmlWebpackPlugin = require('html-webpack-plugin');
3 | const ExtractTextPlugin = require('extract-text-webpack-plugin');
4 | const CopyWebpackPlugin = require('copy-webpack-plugin');
5 |
6 | const SRC_DIR = path.join(__dirname, 'src');
7 |
8 | const indexCss = new ExtractTextPlugin('index.css');
9 |
10 | const argv = require('minimist')(process.argv.slice(2));
11 |
12 | const targetOption = argv.target;
13 | // const output = targetOption === 'web' ? 'build/web' : 'build/electron';
14 | const BUILD_DIR = path.join(__dirname, 'build');
15 |
16 | const options = {
17 | entry: { index: ['babel-polyfill', './index.js'] },
18 | target: targetOption,
19 | output: {
20 | path: BUILD_DIR,
21 | filename: 'js/[name].js',
22 | pathinfo: false,
23 | },
24 | node: {
25 | fs: 'empty',
26 | __dirname: false,
27 | __filename: false,
28 | },
29 | context: SRC_DIR,
30 | resolve: {
31 | extensions: ['.ts', '.tsx', '.js', '.jsx'],
32 | },
33 | plugins: [
34 | indexCss,
35 | new HtmlWebpackPlugin({
36 | title: 'app',
37 | filename: 'index.html',
38 | template: 'public/index.html',
39 | chunks: ['index'],
40 | }),
41 | new CopyWebpackPlugin([{ from: './public/', to: '' }]),
42 | ],
43 | module: {
44 | rules: [
45 | { test: /\.ts$/, exclude: /node-modules/, loader: 'babel-loader' },
46 | { test: /\.tsx$/, exclude: /node-modules/, loader: 'babel-loader' },
47 | {
48 | test: /\.(js|jsx)$/,
49 | exclude: /node_modules/,
50 | use: ['babel-loader'],
51 | },
52 | {
53 | test: /index.css$/,
54 | use: indexCss.extract({
55 | use: 'css-loader',
56 | }),
57 | },
58 | ],
59 | },
60 | };
61 | module.exports = options;
62 |
--------------------------------------------------------------------------------
/webpack.dev.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable linebreak-style */
2 | const path = require('path');
3 | const webpack = require('webpack');
4 | const merge = require('webpack-merge');
5 |
6 | const argv = require('minimist')(process.argv.slice(2));
7 |
8 | const common = require('./webpack.common.js');
9 |
10 | const targetOption = argv.target;
11 | const output = targetOption === 'web' ? 'build/web' : 'build/electron';
12 | const BUILD_DIR = path.join(__dirname, output);
13 |
14 | module.exports = merge(common, {
15 | devServer: {
16 | contentBase: BUILD_DIR,
17 | hot: true,
18 | compress: true,
19 | },
20 | mode: 'development',
21 | devtool: 'eval-source-map',
22 | plugins: [new webpack.HotModuleReplacementPlugin()],
23 | watch: true,
24 | });
25 |
--------------------------------------------------------------------------------
/webpack.prod.js:
--------------------------------------------------------------------------------
1 | const merge = require('webpack-merge');
2 | const common = require('./webpack.common.js');
3 |
4 | module.exports = merge(common, {
5 | mode: 'production',
6 | optimization: {
7 | usedExports: true,
8 | },
9 | });
10 |
--------------------------------------------------------------------------------