The response has been limited to 50k tokens of the smallest files in the repo. You can remove this limitation by removing the max tokens filter.
├── .babelrc
├── .eslintrc.json
├── .github
    ├── CODE_OF_CONDUCT.md
    ├── CONTRIBUTING.md
    ├── ISSUE_TEMPLATE
    │   ├── Bug_report.md
    │   └── Feature_request.md
    └── PULL_REQUEST_TEMPLATE.md
├── .gitignore
├── .npmignore
├── .travis.yml
├── LICENSE.md
├── README.md
├── __mocks__
    └── electron.js
├── assets
    ├── dragging.gif
    ├── export.gif
    └── hierarchy.gif
├── docs
    ├── assets
    │   ├── Aug-19-2018 13-43-20.gif
    │   ├── BTPhoto.jpg
    │   ├── IMG-1710.jpeg
    │   ├── LadyB-007.jpg
    │   ├── dragging.gif
    │   ├── export.gif
    │   ├── hierarchy.gif
    │   ├── proto-logo.png
    │   ├── react-proto-logo.png
    │   ├── screen-shot-2.png
    │   └── sprites.svg
    ├── index.html
    └── style.css
├── electron-builder.yml
├── index.js
├── main.js
├── package-lock.json
├── package.json
├── postcss.config.js
├── src
    ├── actionTypes
    │   └── index.js
    ├── actions
    │   └── components.js
    ├── components
    │   ├── App.jsx
    │   ├── Info.jsx
    │   ├── KonvaStage.jsx
    │   ├── LeftColExpansionPanel.jsx
    │   ├── MainContainerHeader.jsx
    │   ├── Props.jsx
    │   ├── Rectangle.jsx
    │   ├── RightTabs.jsx
    │   ├── SimpleModal.jsx
    │   ├── SnackbarContentWrapper.jsx
    │   ├── Snackbars.jsx
    │   ├── SortableComponent.jsx
    │   ├── TransformerComponent.jsx
    │   ├── __tests__
    │   │   └── App.test.js
    │   └── theme.js
    ├── config
    │   └── index.js
    ├── containers
    │   ├── AppContainer.jsx
    │   ├── LeftContainer.jsx
    │   ├── MainContainer.jsx
    │   └── RightContainer.jsx
    ├── index.js
    ├── localStorage.js
    ├── public
    │   ├── icons
    │   │   ├── mac
    │   │   │   └── icon.icns
    │   │   ├── png
    │   │   │   ├── 1024x1024.png
    │   │   │   ├── 128x128.png
    │   │   │   ├── 16x16.png
    │   │   │   ├── 24x24.png
    │   │   │   ├── 256x256.png
    │   │   │   ├── 32x32.png
    │   │   │   ├── 48x48.png
    │   │   │   ├── 512x512.png
    │   │   │   └── 64x64.png
    │   │   └── win
    │   │   │   └── icon.ico
    │   ├── images
    │   │   └── .gitkeep
    │   ├── index.html
    │   └── styles
    │   │   └── style.css
    ├── reducers
    │   ├── componentReducer.js
    │   └── index.js
    ├── setupTests.js
    ├── store.js
    └── utils
    │   ├── colors.util.js
    │   ├── componentReducer.util.js
    │   ├── componentRender.util.js
    │   ├── convertIdsToObjs.util.js
    │   ├── createApplication.util.js
    │   ├── createFiles.util.js
    │   ├── createModal.util.js
    │   └── setSelectableParents.util.js
├── webpack.config.development.js
├── webpack.config.production.js
└── yarn.lock


/.babelrc:
--------------------------------------------------------------------------------
 1 | {
 2 |   "presets": [
 3 |     [
 4 |       "env",
 5 |       {
 6 |         "modules": false
 7 |       }
 8 |     ],
 9 |     "react",
10 |     "stage-0"
11 |   ],
12 |   "plugins": [
13 |     "transform-es2015-modules-commonjs"
14 |   ]
15 | }


--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "extends": [
 3 |     "plugin:react/recommended",
 4 |     "airbnb-base"
 5 |   ],
 6 |   "parserOptions": {
 7 |     "ecmaFeatures": {
 8 |       "jsx": true
 9 |     },
10 |     "ecmaVersion": 2018,
11 |     "sourceType": "module"
12 |   },
13 |   "plugins": [
14 |     "import",
15 |     "react",
16 |     "jest",
17 |     "jsx-a11y",
18 |     "babel"
19 |   ],
20 |   "parser": "babel-eslint",
21 |   "env": {
22 |     "browser": true,
23 |     "node": true,
24 |     "es6": true,
25 |     "jest": true
26 |   },
27 |   "rules": {
28 |     "class-methods-use-this": "off"
29 |   }
30 | }


--------------------------------------------------------------------------------
/.github/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
 1 | # REACT PROTO CODE OF CONDUCT 1.0
 2 | 
 3 | ### Introduction
 4 | 
 5 | >Following these guidelines helps to communicate that you respect the time of the developers managing and developing this open 
 6 | source project. In return, we ensure to reciprocate that respect in addressing your issue, assessing changes, and helping you 
 7 | finalize your pull requests. If you are new here also checkout our [Contribution Guidelines](CONTRIBUTING.md)
 8 | 
 9 | ### Our Pledge
10 | 
11 | In the interest of fostering an open and welcoming environment, we as
12 | contributors and maintainers pledge to making participation in our project and
13 | our community a harassment-free experience for everyone, regardless of age, body
14 | size, disability, ethnicity, gender identity and expression, level of experience,
15 | nationality, personal appearance, race, religion, or sexual identity and
16 | orientation.
17 | 
18 | ### Our Standards
19 | 
20 | Examples of behavior that contributes to creating a positive environment
21 | include:
22 | 
23 | * Using welcoming and inclusive language
24 | * Being respectful of differing viewpoints and experiences
25 | * Gracefully accepting constructive criticism
26 | * Focusing on what is best for the community
27 | * Showing empathy towards other community members
28 | 
29 | Examples of unacceptable behavior by participants include:
30 | 
31 | * The use of sexualized language or imagery and unwelcome sexual attention or
32 | advances
33 | * Trolling, insulting/derogatory comments, and personal or political attacks
34 | * Public or private harassment
35 | * Publishing others' private information, such as a physical or electronic
36 |   address, without explicit permission
37 | * Other conduct which could reasonably be considered inappropriate in a
38 |   professional setting
39 | 
40 | ### Our Responsibilities
41 | 
42 | Project maintainers are responsible for clarifying the standards of acceptable
43 | behavior and are expected to take appropriate and fair corrective action in
44 | response to any instances of unacceptable behavior.
45 | 
46 | Project maintainers have the right and responsibility to remove, edit, or
47 | reject comments, commits, code, wiki edits, issues, and other contributions
48 | that are not aligned to this Code of Conduct, or to ban temporarily or
49 | permanently any contributor for other behaviors that they deem inappropriate,
50 | threatening, offensive, or harmful.
51 | 
52 | ### Scope
53 | 
54 | This Code of Conduct applies both within project spaces and in public spaces
55 | when an individual is representing the project or its community. Examples of
56 | representing a project or community include using an official project e-mail
57 | address, posting via an official social media account, or acting as an appointed
58 | representative at an online or offline event. Representation of a project may be
59 | further defined and clarified by project maintainers.
60 | 
61 | ### Enforcement
62 | 
63 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
64 | reported by contacting the project team at [INSERT EMAIL ADDRESS]. All
65 | complaints will be reviewed and investigated and will result in a response that
66 | is deemed necessary and appropriate to the circumstances. The project team is
67 | obligated to maintain confidentiality with regard to the reporter of an incident.
68 | Further details of specific enforcement policies may be posted separately.
69 | 
70 | Project maintainers who do not follow or enforce the Code of Conduct in good
71 | faith may face temporary or permanent repercussions as determined by other
72 | members of the project's leadership.
73 | 
74 | ### Attribution
75 | 
76 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
77 | available at [http://contributor-covenant.org/version/1/4][version]
78 | 
79 | [homepage]: http://contributor-covenant.org
80 | [version]: http://contributor-covenant.org/version/1/4/


--------------------------------------------------------------------------------
/.github/CONTRIBUTING.md:
--------------------------------------------------------------------------------
 1 | # React-Proto Contributing Guide 1.0
 2 | 
 3 | > First off, thank you for considering making a contribution to [React Proto](https://github.com/React-Proto/react-proto). 
 4 | It's people like you that inspire our commitment to open-source.
 5 | 
 6 | The goal of this document is to create a contribution process that:
 7 | 
 8 | * Encourages new contributions.
 9 | * Encourages contributors to remain involved.
10 | * Creates a transparent decision making process that makes it clear how contributors can be involved in decision making.
11 | 
12 | ## Vocabulary
13 | 
14 | * A **Contributor** is any individual creating or commenting on an issue or pull request.
15 | * A **Committer** is a subset of contributors who have been given write access to the repository.
16 | 
17 | # Logging Issues
18 | 
19 | Log an issue for any question or problem you might have. When in doubt, log an issue, and
20 | any additional policies about what to include will be provided in the responses. The only
21 | exception is security dislosures which should be sent privately.
22 | 
23 | Committers may direct you to another repository, ask for additional clarifications, and
24 | add appropriate metadata before the issue is addressed.
25 | 
26 | Please be courteous and respectful. Every participant is expected to follow the
27 | project's Code of Conduct.
28 | 
29 | Keep an open mind! Improving documentation, bug triaging, or writing tutorials are all examples of helpful contributions that 
30 | mean less work for you.
31 | 
32 | # Contributions
33 | 
34 | ### Ground Rules
35 | 
36 | > Responsibilities
37 | > * Ensure cross-platform compatibility for every change that's accepted. Windows, Mac, Debian & Ubuntu Linux.
38 | > * Ensure that code that goes into core meets all requirements and pass tests. 
39 | > * Create issues for any major changes and enhancements that you wish to make. Discuss things transparently and get community 
40 | feedback.
41 | > * Keep feature versions as small as possible, preferably one new feature per version.
42 | > * Be welcoming to newcomers and encourage diverse new contributors from all backgrounds. Check our [CODE OF
43 | CONDUCT](CODE_OF_CONDUCT.md)
44 | 
45 | 
46 | 
47 | 
48 | 
49 | Any change to resources in this repository must be through pull requests. This applies to all changes
50 | to documentation, code, binary files, etc. Even long term committers and Project Maintainers must use
51 | pull requests.
52 | 
53 | No pull request can be merged without being reviewed.
54 | 
55 | For non-trivial contributions, pull requests should sit for at least 36 hours to ensure that
56 | contributors in other timezones have time to review. Consideration should also be given to
57 | weekends and other holiday periods to ensure active committers all have reasonable time to
58 | become involved in the discussion and review process if they wish.
59 | 
60 | The default for each contribution is that it is accepted once no committer has an objection.
61 | During review committers may also request that a specific contributor who is most versed in a
62 | particular area gives a "LGTM" before the PR can be merged. There is no additional "sign off"
63 | process for contributions to land. Once all issues brought by committers are addressed it can
64 | be landed by any committer.
65 | 
66 | In the case of an objection being raised in a pull request by another committer, all involved
67 | committers should seek to arrive at a consensus by way of addressing concerns being expressed
68 | by discussion, compromise on the proposed change, or withdrawal of the proposed change.
69 | 
70 | If a contribution is controversial and committers cannot agree about how to get it to land
71 | or if it should land then it should be escalated to any of the Project Maintainers, 
72 | [@refinedblessing](https://github.com/refinedblessing) , [@erikguntner](https://github.com/erikguntner) , 
73 | [brianwtaylor](https://github.com/brianwtaylor). 
74 | Project Maintainers should regularly discuss pending contributions in order to find a resolution. It is expected that only a
75 | small minority of issues be brought to the Project Maintainers for resolution and that discussion and compromise among 
76 | committers be the default resolution mechanism.
77 | 


--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/Bug_report.md:
--------------------------------------------------------------------------------
 1 | ---
 2 | name: "\U0001F41BBug report"
 3 | about: Something isn't working right
 4 | ---
 5 | 
 6 | ### React-Prot Version: x.x.x
 7 | 
 8 | <!--- Provide the exact version of React-Proto in which you see the bug.  -->
 9 | 
10 | ### Details
11 | 
12 | <!--- Provide a more detailed introduction to the issue itself, and why you consider it to be a bug.  How has this bug affected 
13 | you? What were you trying to accomplish? -->
14 | 
15 | ### Expected Behavior
16 | 
17 | <!--- Tell us what should happen -->
18 | 
19 | ### Actual Behavior
20 | 
21 | <!--- Tell us what happens instead -->
22 | 
23 | ### Possible Fix
24 | 
25 | <!--- Not obligatory, but suggest a fix or reason for the bug -->
26 | 
27 | <details><summary>Additional Info</summary>
28 | 
29 | ### Your Environment
30 | 
31 | <!-- Include as many relevant details about the environment you experienced the bug in -->
32 | 
33 | - Environment name and version (e.g. Windows 10, node.js 5.4):
34 | - Operating System and version (Mac or Linux):
35 | - Useful link to screenshot or any other information:
36 | 
37 | ### Steps to Reproduce
38 | 
39 | <!-- or an unambiguous set of steps to reproduce this bug -->
40 | 
41 | <!-- include code to reproduce, if relevant -->
42 | 
43 | 1.  first...
44 | 2.
45 | 3.
46 | 4.
47 | 
48 | ### Stack Trace
49 | 
50 | <!-- If an error is thrown, provide the stack trace here -->
51 | 
52 | </details>
53 | 


--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/Feature_request.md:
--------------------------------------------------------------------------------
 1 | ---
 2 | name: "\U0001F680Feature request"
 3 | about: Suggest an idea for React-Proto
 4 | ---
 5 | 
 6 | ### Description
 7 | 
 8 | <!--- Provide a detailed description of the change or addition you are proposing -->
 9 | 
10 | ### Why
11 | 
12 | <!--- Why is this change important to you? How would you use it? -->
13 | 
14 | <!--- How can it benefit other users? -->
15 | 
16 | ### Possible Implementation & Open Questions
17 | 
18 | <!--- Not obligatory, but suggest an idea for implementing addition or change -->
19 | 
20 | <!--- What still needs to be discussed -->
21 | 
22 | ### Is this something you're interested in working on?
23 | 
24 | <!--- Yes or no -->
25 | 
26 | 


--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
 1 | <!--- Provide a general summary of your changes in the Title above -->
 2 | 
 3 | ## Description
 4 | 
 5 | <!--- Describe your changes in detail -->
 6 | 
 7 | <!--- Why is this change required? What problem does it solve? -->
 8 | 
 9 | <!--- If it fixes an open issue, please link to the issue here. -->
10 | 
11 | ## How Has This Been Tested?
12 | 
13 | <!--- Please describe in detail how you tested your changes. -->
14 | 
15 | <!--- Include details of your testing environment, tests ran to see how -->
16 | 
17 | ## Types of changes
18 | 
19 | <!--- What types of changes does your code introduce? Put an `x` in all the boxes that apply: -->
20 | 
21 | - [ ] Bug fix (non-breaking change which fixes an issue)
22 | - [ ] New feature (non-breaking change which adds functionality)
23 | - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
24 | - [ ] This change requires a documentation update
25 | 
26 | ## Checklist:
27 | 
28 | <!--- Go over all the following points, and put an `x` in all the boxes that apply. -->
29 | 
30 | <!--- If you're unsure about any of these, don't hesitate to ask. We're here to help! -->
31 | 
32 | - [ ] I have read the **CONTRIBUTING** document and have signed (or will sign) the CLA.
33 | - [ ] I have updated/added documentation affected by my changes.
34 | - [ ] I have added tests to cover my changes.
35 | 


--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
  1 | # Created by https://www.gitignore.io/api/node,linux,macos,windows,visualstudio,yarn
  2 | 
  3 | ### Linux ###
  4 | *~
  5 | 
  6 | # temporary files which can be created if a process still has a handle open of a deleted file
  7 | .fuse_hidden*
  8 | 
  9 | # KDE directory preferences
 10 | .directory
 11 | 
 12 | # Linux trash folder which might appear on any partition or disk
 13 | .Trash-*
 14 | 
 15 | # .nfs files are created when an open file is removed but is still being accessed
 16 | .nfs*
 17 | 
 18 | ### macOS ###
 19 | # General
 20 | .DS_Store
 21 | .AppleDouble
 22 | .LSOverride
 23 | 
 24 | # Icon must end with two \r
 25 | Icon
 26 | 
 27 | # Thumbnails
 28 | ._*
 29 | 
 30 | # Files that might appear in the root of a volume
 31 | .DocumentRevisions-V100
 32 | .fseventsd
 33 | .Spotlight-V100
 34 | .TemporaryItems
 35 | .Trashes
 36 | .VolumeIcon.icns
 37 | .com.apple.timemachine.donotpresent
 38 | 
 39 | # Directories potentially created on remote AFP share
 40 | .AppleDB
 41 | .AppleDesktop
 42 | Network Trash Folder
 43 | Temporary Items
 44 | .apdisk
 45 | 
 46 | ### Node ###
 47 | # Logs
 48 | logs
 49 | *.log
 50 | npm-debug.log*
 51 | yarn-debug.log*
 52 | yarn-error.log*
 53 | 
 54 | # Runtime data
 55 | pids
 56 | *.pid
 57 | *.seed
 58 | *.pid.lock
 59 | 
 60 | # Directory for instrumented libs generated by jscoverage/JSCover
 61 | lib-cov
 62 | 
 63 | # Coverage directory used by tools like istanbul
 64 | coverage
 65 | 
 66 | # nyc test coverage
 67 | .nyc_output
 68 | 
 69 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
 70 | .grunt
 71 | 
 72 | # Bower dependency directory (https://bower.io/)
 73 | bower_components
 74 | 
 75 | # node-waf configuration
 76 | .lock-wscript
 77 | 
 78 | # Compiled binary addons (https://nodejs.org/api/addons.html)
 79 | build/Release
 80 | 
 81 | # Dependency directories
 82 | node_modules/
 83 | jspm_packages/
 84 | dist/
 85 | build/
 86 | release-builds/
 87 | 
 88 | # TypeScript v1 declaration files
 89 | typings/
 90 | 
 91 | # Optional npm cache directory
 92 | .npm
 93 | 
 94 | # Optional eslint cache
 95 | .eslintcache
 96 | 
 97 | # Optional REPL history
 98 | .node_repl_history
 99 | 
100 | # Output of 'npm pack'
101 | *.tgz
102 | 
103 | # Yarn Integrity file
104 | .yarn-integrity
105 | 
106 | # dotenv environment variables file
107 | .env
108 | 
109 | # parcel-bundler cache (https://parceljs.org/)
110 | .cache
111 | 
112 | # next.js build output
113 | .next
114 | 
115 | # nuxt.js build output
116 | .nuxt
117 | 
118 | # vuepress build output
119 | .vuepress/dist
120 | 
121 | # Serverless directories
122 | .serverless
123 | 
124 | ### Windows ###
125 | # Windows thumbnail cache files
126 | Thumbs.db
127 | ehthumbs.db
128 | ehthumbs_vista.db
129 | 
130 | # Dump file
131 | *.stackdump
132 | 
133 | # Folder config file
134 | [Dd]esktop.ini
135 | 
136 | # Recycle Bin used on file shares
137 | $RECYCLE.BIN/
138 | 
139 | # Windows Installer files
140 | *.cab
141 | *.msi
142 | *.msix
143 | *.msm
144 | *.msp
145 | 
146 | # Windows shortcuts
147 | *.lnk
148 | 
149 | #!! ERROR: yarn is undefined. Use list command to see defined gitignore types !!#
150 | 
151 | ### VisualStudio ###
152 | ## Ignore Visual Studio temporary files, build results, and
153 | ## files generated by popular Visual Studio add-ons.
154 | ##
155 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
156 | 
157 | #VSCode
158 | .vscode/
159 | 
160 | # User-specific files
161 | *.suo
162 | *.user
163 | *.userosscache
164 | *.sln.docstates
165 | 
166 | # User-specific files (MonoDevelop/Xamarin Studio)
167 | *.userprefs
168 | 
169 | # Build results
170 | [Dd]ebug/
171 | [Dd]ebugPublic/
172 | [Rr]elease/
173 | [Rr]eleases/
174 | x64/
175 | x86/
176 | bld/
177 | [Bb]in/
178 | [Oo]bj/
179 | [Ll]og/
180 | 
181 | # Visual Studio 2015/2017 cache/options directory
182 | .vs/
183 | # Uncomment if you have tasks that create the project's static files in wwwroot
184 | #wwwroot/
185 | 
186 | # Visual Studio 2017 auto generated files
187 | Generated\ Files/
188 | 
189 | # MSTest test Results
190 | [Tt]est[Rr]esult*/
191 | [Bb]uild[Ll]og.*
192 | 
193 | # NUNIT
194 | *.VisualState.xml
195 | TestResult.xml
196 | 
197 | # Build Results of an ATL Project
198 | [Dd]ebugPS/
199 | [Rr]eleasePS/
200 | dlldata.c
201 | 
202 | # Benchmark Results
203 | BenchmarkDotNet.Artifacts/
204 | 
205 | # .NET Core
206 | project.lock.json
207 | project.fragment.lock.json
208 | artifacts/
209 | 
210 | # StyleCop
211 | StyleCopReport.xml
212 | 
213 | # Files built by Visual Studio
214 | *_i.c
215 | *_p.c
216 | *_i.h
217 | *.ilk
218 | *.meta
219 | *.obj
220 | *.iobj
221 | *.pch
222 | *.pdb
223 | *.ipdb
224 | *.pgc
225 | *.pgd
226 | *.rsp
227 | *.sbr
228 | *.tlb
229 | *.tli
230 | *.tlh
231 | *.tmp
232 | *.tmp_proj
233 | *.vspscc
234 | *.vssscc
235 | .builds
236 | *.pidb
237 | *.svclog
238 | *.scc
239 | 
240 | # Chutzpah Test files
241 | _Chutzpah*
242 | 
243 | # Visual C++ cache files
244 | ipch/
245 | *.aps
246 | *.ncb
247 | *.opendb
248 | *.opensdf
249 | *.sdf
250 | *.cachefile
251 | *.VC.db
252 | *.VC.VC.opendb
253 | 
254 | # Visual Studio profiler
255 | *.psess
256 | *.vsp
257 | *.vspx
258 | *.sap
259 | 
260 | # Visual Studio Trace Files
261 | *.e2e
262 | 
263 | # TFS 2012 Local Workspace
264 | $tf/
265 | 
266 | # Guidance Automation Toolkit
267 | *.gpState
268 | 
269 | # ReSharper is a .NET coding add-in
270 | _ReSharper*/
271 | *.[Rr]e[Ss]harper
272 | *.DotSettings.user
273 | 
274 | # JustCode is a .NET coding add-in
275 | .JustCode
276 | 
277 | # TeamCity is a build add-in
278 | _TeamCity*
279 | 
280 | # DotCover is a Code Coverage Tool
281 | *.dotCover
282 | 
283 | # AxoCover is a Code Coverage Tool
284 | .axoCover/*
285 | !.axoCover/settings.json
286 | 
287 | # Visual Studio code coverage results
288 | *.coverage
289 | *.coveragexml
290 | 
291 | # NCrunch
292 | _NCrunch_*
293 | .*crunch*.local.xml
294 | nCrunchTemp_*
295 | 
296 | # MightyMoose
297 | *.mm.*
298 | AutoTest.Net/
299 | 
300 | # Web workbench (sass)
301 | .sass-cache/
302 | 
303 | # Installshield output folder
304 | [Ee]xpress/
305 | 
306 | # DocProject is a documentation generator add-in
307 | DocProject/buildhelp/
308 | DocProject/Help/*.HxT
309 | DocProject/Help/*.HxC
310 | DocProject/Help/*.hhc
311 | DocProject/Help/*.hhk
312 | DocProject/Help/*.hhp
313 | DocProject/Help/Html2
314 | DocProject/Help/html
315 | 
316 | # Click-Once directory
317 | publish/
318 | 
319 | # Publish Web Output
320 | *.[Pp]ublish.xml
321 | *.azurePubxml
322 | # Note: Comment the next line if you want to checkin your web deploy settings,
323 | # but database connection strings (with potential passwords) will be unencrypted
324 | *.pubxml
325 | *.publishproj
326 | 
327 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
328 | # checkin your Azure Web App publish settings, but sensitive information contained
329 | # in these scripts will be unencrypted
330 | PublishScripts/
331 | 
332 | # NuGet Packages
333 | *.nupkg
334 | # The packages folder can be ignored because of Package Restore
335 | **/[Pp]ackages/*
336 | # except build/, which is used as an MSBuild target.
337 | !**/[Pp]ackages/build/
338 | # Uncomment if necessary however generally it will be regenerated when needed
339 | #!**/[Pp]ackages/repositories.config
340 | # NuGet v3's project.json files produces more ignorable files
341 | *.nuget.props
342 | *.nuget.targets
343 | 
344 | # Microsoft Azure Build Output
345 | csx/
346 | *.build.csdef
347 | 
348 | # Microsoft Azure Emulator
349 | ecf/
350 | rcf/
351 | 
352 | # Windows Store app package directories and files
353 | AppPackages/
354 | BundleArtifacts/
355 | Package.StoreAssociation.xml
356 | _pkginfo.txt
357 | *.appx
358 | 
359 | # Visual Studio cache files
360 | # files ending in .cache can be ignored
361 | *.[Cc]ache
362 | # but keep track of directories ending in .cache
363 | !*.[Cc]ache/
364 | 
365 | # Others
366 | ClientBin/
367 | ~$*
368 | *.dbmdl
369 | *.dbproj.schemaview
370 | *.jfm
371 | *.pfx
372 | *.publishsettings
373 | orleans.codegen.cs
374 | 
375 | # Including strong name files can present a security risk
376 | # (https://github.com/github/gitignore/pull/2483#issue-259490424)
377 | #*.snk
378 | 
379 | # Since there are multiple workflows, uncomment next line to ignore bower_components
380 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
381 | #bower_components/
382 | 
383 | # RIA/Silverlight projects
384 | Generated_Code/
385 | 
386 | # Backup & report files from converting an old project file
387 | # to a newer Visual Studio version. Backup files are not needed,
388 | # because we have git ;-)
389 | _UpgradeReport_Files/
390 | Backup*/
391 | UpgradeLog*.XML
392 | UpgradeLog*.htm
393 | ServiceFabricBackup/
394 | *.rptproj.bak
395 | 
396 | # SQL Server files
397 | *.mdf
398 | *.ldf
399 | *.ndf
400 | 
401 | # Business Intelligence projects
402 | *.rdl.data
403 | *.bim.layout
404 | *.bim_*.settings
405 | *.rptproj.rsuser
406 | 
407 | # Microsoft Fakes
408 | FakesAssemblies/
409 | 
410 | # GhostDoc plugin setting file
411 | *.GhostDoc.xml
412 | 
413 | # Node.js Tools for Visual Studio
414 | .ntvs_analysis.dat
415 | 
416 | # Visual Studio 6 build log
417 | *.plg
418 | 
419 | # Visual Studio 6 workspace options file
420 | *.opt
421 | 
422 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
423 | *.vbw
424 | 
425 | # Visual Studio LightSwitch build output
426 | **/*.HTMLClient/GeneratedArtifacts
427 | **/*.DesktopClient/GeneratedArtifacts
428 | **/*.DesktopClient/ModelManifest.xml
429 | **/*.Server/GeneratedArtifacts
430 | **/*.Server/ModelManifest.xml
431 | _Pvt_Extensions
432 | 
433 | # Paket dependency manager
434 | .paket/paket.exe
435 | paket-files/
436 | 
437 | # FAKE - F# Make
438 | .fake/
439 | 
440 | # JetBrains Rider
441 | .idea/
442 | *.sln.iml
443 | 
444 | # CodeRush
445 | .cr/
446 | 
447 | # Python Tools for Visual Studio (PTVS)
448 | __pycache__/
449 | *.pyc
450 | 
451 | # Cake - Uncomment if you are using it
452 | # tools/**
453 | # !tools/packages.config
454 | 
455 | # Tabs Studio
456 | *.tss
457 | 
458 | # Telerik's JustMock configuration file
459 | *.jmconfig
460 | 
461 | # BizTalk build output
462 | *.btp.cs
463 | *.btm.cs
464 | *.odx.cs
465 | *.xsd.cs
466 | 
467 | # OpenCover UI analysis results
468 | OpenCover/
469 | 
470 | # Azure Stream Analytics local run output
471 | ASALocalRun/
472 | 
473 | # MSBuild Binary and Structured Log
474 | *.binlog
475 | 
476 | # NVidia Nsight GPU debugger configuration file
477 | *.nvuser
478 | 
479 | # MFractors (Xamarin productivity tool) working folder
480 | .mfractor/
481 | 
482 | # DMG File
483 | React-Proto.dmg
484 | installers/
485 | 
486 | # End of https://www.gitignore.io/api/node,linux,macos,windows,visualstudio,yarn
487 | 


--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
  1 | # Created by https://www.gitignore.io/api/node,linux,macos,windows,visualstudio,yarn
  2 | 
  3 | ### Linux ###
  4 | *~
  5 | 
  6 | # temporary files which can be created if a process still has a handle open of a deleted file
  7 | .fuse_hidden*
  8 | 
  9 | # KDE directory preferences
 10 | .directory
 11 | 
 12 | # Linux trash folder which might appear on any partition or disk
 13 | .Trash-*
 14 | 
 15 | # .nfs files are created when an open file is removed but is still being accessed
 16 | .nfs*
 17 | 
 18 | ### macOS ###
 19 | # General
 20 | .DS_Store
 21 | .AppleDouble
 22 | .LSOverride
 23 | build/
 24 | 
 25 | # Icon must end with two \r
 26 | Icon
 27 | 
 28 | # Thumbnails
 29 | ._*
 30 | 
 31 | # Files that might appear in the root of a volume
 32 | .DocumentRevisions-V100
 33 | .fseventsd
 34 | .Spotlight-V100
 35 | .TemporaryItems
 36 | .Trashes
 37 | .VolumeIcon.icns
 38 | .com.apple.timemachine.donotpresent
 39 | 
 40 | # Directories potentially created on remote AFP share
 41 | .AppleDB
 42 | .AppleDesktop
 43 | Network Trash Folder
 44 | Temporary Items
 45 | .apdisk
 46 | 
 47 | ### Node ###
 48 | # Logs
 49 | logs
 50 | *.log
 51 | npm-debug.log*
 52 | yarn-debug.log*
 53 | yarn-error.log*
 54 | 
 55 | # Runtime data
 56 | pids
 57 | *.pid
 58 | *.seed
 59 | *.pid.lock
 60 | 
 61 | # Directory for instrumented libs generated by jscoverage/JSCover
 62 | lib-cov
 63 | 
 64 | # Coverage directory used by tools like istanbul
 65 | coverage
 66 | 
 67 | # nyc test coverage
 68 | .nyc_output
 69 | 
 70 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
 71 | .grunt
 72 | 
 73 | # Bower dependency directory (https://bower.io/)
 74 | bower_components
 75 | 
 76 | # node-waf configuration
 77 | .lock-wscript
 78 | 
 79 | # Compiled binary addons (https://nodejs.org/api/addons.html)
 80 | build/Release
 81 | 
 82 | # Dependency directories
 83 | node_modules/
 84 | jspm_packages/
 85 | dist/
 86 | release-builds/
 87 | 
 88 | # TypeScript v1 declaration files
 89 | typings/
 90 | 
 91 | # Optional npm cache directory
 92 | .npm
 93 | 
 94 | # Optional eslint cache
 95 | .eslintcache
 96 | 
 97 | # Optional REPL history
 98 | .node_repl_history
 99 | 
100 | # Output of 'npm pack'
101 | *.tgz
102 | 
103 | # Yarn Integrity file
104 | .yarn-integrity
105 | 
106 | # dotenv environment variables file
107 | .env
108 | 
109 | # parcel-bundler cache (https://parceljs.org/)
110 | .cache
111 | 
112 | # next.js build output
113 | .next
114 | 
115 | # nuxt.js build output
116 | .nuxt
117 | 
118 | # vuepress build output
119 | .vuepress/dist
120 | 
121 | # Serverless directories
122 | .serverless
123 | 
124 | ### Windows ###
125 | # Windows thumbnail cache files
126 | Thumbs.db
127 | ehthumbs.db
128 | ehthumbs_vista.db
129 | 
130 | # Dump file
131 | *.stackdump
132 | 
133 | # Folder config file
134 | [Dd]esktop.ini
135 | 
136 | # Recycle Bin used on file shares
137 | $RECYCLE.BIN/
138 | 
139 | # Windows Installer files
140 | *.cab
141 | *.msi
142 | *.msix
143 | *.msm
144 | *.msp
145 | 
146 | # Windows shortcuts
147 | *.lnk
148 | 
149 | #!! ERROR: yarn is undefined. Use list command to see defined gitignore types !!#
150 | 
151 | ### VisualStudio ###
152 | ## Ignore Visual Studio temporary files, build results, and
153 | ## files generated by popular Visual Studio add-ons.
154 | ##
155 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
156 | 
157 | # User-specific files
158 | *.suo
159 | *.user
160 | *.userosscache
161 | *.sln.docstates
162 | 
163 | # User-specific files (MonoDevelop/Xamarin Studio)
164 | *.userprefs
165 | 
166 | # Build results
167 | [Dd]ebug/
168 | [Dd]ebugPublic/
169 | [Rr]elease/
170 | [Rr]eleases/
171 | x64/
172 | x86/
173 | bld/
174 | [Bb]in/
175 | [Oo]bj/
176 | [Ll]og/
177 | 
178 | # Visual Studio 2015/2017 cache/options directory
179 | .vs/
180 | # Uncomment if you have tasks that create the project's static files in wwwroot
181 | #wwwroot/
182 | 
183 | # Visual Studio 2017 auto generated files
184 | Generated\ Files/
185 | 
186 | # MSTest test Results
187 | [Tt]est[Rr]esult*/
188 | [Bb]uild[Ll]og.*
189 | 
190 | # NUNIT
191 | *.VisualState.xml
192 | TestResult.xml
193 | 
194 | # Build Results of an ATL Project
195 | [Dd]ebugPS/
196 | [Rr]eleasePS/
197 | dlldata.c
198 | 
199 | # Benchmark Results
200 | BenchmarkDotNet.Artifacts/
201 | 
202 | # .NET Core
203 | project.lock.json
204 | project.fragment.lock.json
205 | artifacts/
206 | 
207 | # StyleCop
208 | StyleCopReport.xml
209 | 
210 | # Files built by Visual Studio
211 | *_i.c
212 | *_p.c
213 | *_i.h
214 | *.ilk
215 | *.meta
216 | *.obj
217 | *.iobj
218 | *.pch
219 | *.pdb
220 | *.ipdb
221 | *.pgc
222 | *.pgd
223 | *.rsp
224 | *.sbr
225 | *.tlb
226 | *.tli
227 | *.tlh
228 | *.tmp
229 | *.tmp_proj
230 | *.vspscc
231 | *.vssscc
232 | .builds
233 | *.pidb
234 | *.svclog
235 | *.scc
236 | 
237 | # Chutzpah Test files
238 | _Chutzpah*
239 | 
240 | # Visual C++ cache files
241 | ipch/
242 | *.aps
243 | *.ncb
244 | *.opendb
245 | *.opensdf
246 | *.sdf
247 | *.cachefile
248 | *.VC.db
249 | *.VC.VC.opendb
250 | 
251 | # Visual Studio profiler
252 | *.psess
253 | *.vsp
254 | *.vspx
255 | *.sap
256 | 
257 | # Visual Studio Trace Files
258 | *.e2e
259 | 
260 | # TFS 2012 Local Workspace
261 | $tf/
262 | 
263 | # Guidance Automation Toolkit
264 | *.gpState
265 | 
266 | # ReSharper is a .NET coding add-in
267 | _ReSharper*/
268 | *.[Rr]e[Ss]harper
269 | *.DotSettings.user
270 | 
271 | # JustCode is a .NET coding add-in
272 | .JustCode
273 | 
274 | # TeamCity is a build add-in
275 | _TeamCity*
276 | 
277 | # DotCover is a Code Coverage Tool
278 | *.dotCover
279 | 
280 | # AxoCover is a Code Coverage Tool
281 | .axoCover/*
282 | !.axoCover/settings.json
283 | 
284 | # Visual Studio code coverage results
285 | *.coverage
286 | *.coveragexml
287 | 
288 | # NCrunch
289 | _NCrunch_*
290 | .*crunch*.local.xml
291 | nCrunchTemp_*
292 | 
293 | # MightyMoose
294 | *.mm.*
295 | AutoTest.Net/
296 | 
297 | # Web workbench (sass)
298 | .sass-cache/
299 | 
300 | # Installshield output folder
301 | [Ee]xpress/
302 | 
303 | # DocProject is a documentation generator add-in
304 | DocProject/buildhelp/
305 | DocProject/Help/*.HxT
306 | DocProject/Help/*.HxC
307 | DocProject/Help/*.hhc
308 | DocProject/Help/*.hhk
309 | DocProject/Help/*.hhp
310 | DocProject/Help/Html2
311 | DocProject/Help/html
312 | 
313 | # Click-Once directory
314 | publish/
315 | 
316 | # Publish Web Output
317 | *.[Pp]ublish.xml
318 | *.azurePubxml
319 | # Note: Comment the next line if you want to checkin your web deploy settings,
320 | # but database connection strings (with potential passwords) will be unencrypted
321 | *.pubxml
322 | *.publishproj
323 | 
324 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
325 | # checkin your Azure Web App publish settings, but sensitive information contained
326 | # in these scripts will be unencrypted
327 | PublishScripts/
328 | 
329 | # NuGet Packages
330 | *.nupkg
331 | # The packages folder can be ignored because of Package Restore
332 | **/[Pp]ackages/*
333 | # except build/, which is used as an MSBuild target.
334 | !**/[Pp]ackages/build/
335 | # Uncomment if necessary however generally it will be regenerated when needed
336 | #!**/[Pp]ackages/repositories.config
337 | # NuGet v3's project.json files produces more ignorable files
338 | *.nuget.props
339 | *.nuget.targets
340 | 
341 | # Microsoft Azure Build Output
342 | csx/
343 | *.build.csdef
344 | 
345 | # Microsoft Azure Emulator
346 | ecf/
347 | rcf/
348 | 
349 | # Windows Store app package directories and files
350 | AppPackages/
351 | BundleArtifacts/
352 | Package.StoreAssociation.xml
353 | _pkginfo.txt
354 | *.appx
355 | 
356 | # Visual Studio cache files
357 | # files ending in .cache can be ignored
358 | *.[Cc]ache
359 | # but keep track of directories ending in .cache
360 | !*.[Cc]ache/
361 | 
362 | # Others
363 | ClientBin/
364 | ~$*
365 | *.dbmdl
366 | *.dbproj.schemaview
367 | *.jfm
368 | *.pfx
369 | *.publishsettings
370 | orleans.codegen.cs
371 | 
372 | # Including strong name files can present a security risk
373 | # (https://github.com/github/gitignore/pull/2483#issue-259490424)
374 | #*.snk
375 | 
376 | # Since there are multiple workflows, uncomment next line to ignore bower_components
377 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
378 | #bower_components/
379 | 
380 | # RIA/Silverlight projects
381 | Generated_Code/
382 | 
383 | # Backup & report files from converting an old project file
384 | # to a newer Visual Studio version. Backup files are not needed,
385 | # because we have git ;-)
386 | _UpgradeReport_Files/
387 | Backup*/
388 | UpgradeLog*.XML
389 | UpgradeLog*.htm
390 | ServiceFabricBackup/
391 | *.rptproj.bak
392 | 
393 | # SQL Server files
394 | *.mdf
395 | *.ldf
396 | *.ndf
397 | 
398 | # Business Intelligence projects
399 | *.rdl.data
400 | *.bim.layout
401 | *.bim_*.settings
402 | *.rptproj.rsuser
403 | 
404 | # Microsoft Fakes
405 | FakesAssemblies/
406 | 
407 | # GhostDoc plugin setting file
408 | *.GhostDoc.xml
409 | 
410 | # Node.js Tools for Visual Studio
411 | .ntvs_analysis.dat
412 | 
413 | # Visual Studio 6 build log
414 | *.plg
415 | 
416 | # Visual Studio 6 workspace options file
417 | *.opt
418 | 
419 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
420 | *.vbw
421 | 
422 | # Visual Studio LightSwitch build output
423 | **/*.HTMLClient/GeneratedArtifacts
424 | **/*.DesktopClient/GeneratedArtifacts
425 | **/*.DesktopClient/ModelManifest.xml
426 | **/*.Server/GeneratedArtifacts
427 | **/*.Server/ModelManifest.xml
428 | _Pvt_Extensions
429 | 
430 | # Paket dependency manager
431 | .paket/paket.exe
432 | paket-files/
433 | 
434 | # FAKE - F# Make
435 | .fake/
436 | 
437 | # JetBrains Rider
438 | .idea/
439 | *.sln.iml
440 | 
441 | # CodeRush
442 | .cr/
443 | 
444 | # Python Tools for Visual Studio (PTVS)
445 | __pycache__/
446 | *.pyc
447 | 
448 | # Cake - Uncomment if you are using it
449 | # tools/**
450 | # !tools/packages.config
451 | 
452 | # Tabs Studio
453 | *.tss
454 | 
455 | # Telerik's JustMock configuration file
456 | *.jmconfig
457 | 
458 | # BizTalk build output
459 | *.btp.cs
460 | *.btm.cs
461 | *.odx.cs
462 | *.xsd.cs
463 | 
464 | # OpenCover UI analysis results
465 | OpenCover/
466 | 
467 | # Azure Stream Analytics local run output
468 | ASALocalRun/
469 | 
470 | # MSBuild Binary and Structured Log
471 | *.binlog
472 | 
473 | # NVidia Nsight GPU debugger configuration file
474 | *.nvuser
475 | 
476 | # MFractors (Xamarin productivity tool) working folder
477 | .mfractor/
478 | 
479 | # DMG File
480 | React-Proto.dmg
481 | installers/
482 | assets/
483 | .git/
484 | 
485 | # End of https://www.gitignore.io/api/node,linux,macos,windows,visualstudio,yarn


--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 |   - "8"
4 |   - "10"


--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
 1 | MIT License
 2 | 
 3 | Copyright (c) 2018 React-Proto
 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 | # React-Proto [![Build Status](https://travis-ci.com/React-Proto/react-proto.svg?branch=master)](https://travis-ci.com/React-Proto/react-proto) [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT) 
  2 | 
  3 | <img src="https://github.com/React-Proto/react-proto/blob/master/src/public/icons/png/64x64.png"/>
  4 |  
  5 | [React-Proto](https://react-proto.github.io/react-proto/) is a React application prototyping tool for developers and designers.
  6 | 
  7 | React-Proto allows the user to visualize/setup their application architecture upfront and eject this architecture as application files either into a previous project or a new create-react-app project or a starter template from any repository.
  8 | 
  9 | Download for [MacOS](https://github.com/React-Proto/react-proto/releases/download/v1.0.0/React-Proto-1.0.0.dmg), [Windows](https://github.com/React-Proto/react-proto/releases/download/v1.0.0/React-Proto.Web.Setup.1.0.0.exe), [Linux](https://github.com/React-Proto/react-proto/releases/download/v1.0.0/react-proto_1.0.0_amd64.deb).
 10 | * Mac users only: for now you might need to go to your security settings to allow the app run on your system as we do not have an Apple license yet.
 11 | 
 12 | If you find any issues, [file issue](https://github.com/React-Proto/react-proto/issues)
 13 | 
 14 | ## How To Use
 15 | 
 16 | - Application can be run from cli  using ```react-proto``` command or by clicking on the application icon.
 17 | 
 18 | - To start a new project, either import a mockup or start with a blank stage.
 19 | 
 20 | - Add components you would like to create using the input, then drag the component frame into place and resize accordingly.
 21 | 
 22 | <img src="https://github.com/React-Proto/react-proto/blob/master/assets/dragging.gif"/>
 23 | 
 24 | - While building, you can use the icons in the toolbar to zoom, toggle draggability of the stage, update or remove an image, collapse the left container, and export your files.
 25 | 
 26 | - For each component you have the ability to define whether your component will have state, the color of the frame component, and the ability to apply a parent component.
 27 | 
 28 | - If you place a container around other components and can no longer access them, you can use the layer buttons in the corresponding dropdown menu to change layer order down or up.
 29 | 
 30 | <img src="https://github.com/React-Proto/react-proto/blob/master/assets/hierarchy.gif"/>
 31 | 
 32 | - In the right container, the props tab allows you to define props in key value pairs, as well as the necessary prop type.
 33 | 
 34 | - Once you are finished, you can use the export button in the toolbar to choose from three options of how to export your files: 
 35 |   1. Import your files into an existing project. Just choose the path where you would like to create your components folder.
 36 |   2. Use create-react-app to start a new project (the project will be under the "proto_app").
 37 |   3. Clone your favorite Github repo to start a project with your favorite starter files.
 38 | 
 39 | <img src="https://github.com/React-Proto/react-proto/blob/master/assets/export.gif"/>
 40 | 
 41 | - Lastly, start building!
 42 | 
 43 | ## Authors
 44 | 
 45 | [Blessing E Ebowe](https://www.linkedin.com/in/blessingebowe/) [@refinedblessing](https://github.com/refinedblessing)
 46 | 
 47 | [Brian Taylor](https://www.linkedin.com/in/brianwtaylor/) [@brianwtaylor](https://github.com/brianwtaylor)
 48 | 
 49 | [Erik Guntner](https://www.linkedin.com/in/erik-guntner-9aa324b9/) [@erikguntner](https://github.com/erikguntner)
 50 | 
 51 | ## Running Your Own Version
 52 | 
 53 | - **Fork** and **Clone** Repository.
 54 | 
 55 | - Open project directory
 56 | 
 57 | ``` bash
 58 | cd react-proto
 59 | ```
 60 | 
 61 | - Install dependencies
 62 | 
 63 | ``` bash
 64 | yarn install
 65 | ```
 66 | 
 67 | - Run application
 68 | 
 69 | ``` bash
 70 | yarn start
 71 | ```
 72 | 
 73 | - For development experience...
 74 | 
 75 | ``` bash
 76 | yarn dev
 77 | ```
 78 | 
 79 | - and on another terminal
 80 | 
 81 | ``` bash
 82 | yarn electron
 83 | ```
 84 | 
 85 | ## Linting
 86 | 
 87 | ``` bash
 88 | yarn linter
 89 | ```
 90 | 
 91 | ## Testing
 92 | 
 93 | ```bash
 94 | yarn test
 95 | ```
 96 | 
 97 | ## Built With
 98 | 
 99 | - [React](https://reactjs.org/) - Framework for building user interaces.
100 | - [Redux](https://redux.js.org/) - Predictable state container for JavaScript apps.
101 | - [Electron](https://electronjs.org/) - Cross-platform desktop apps with HTML, CSS and JS.
102 | - [KonvaJS](https://konvajs.github.io/) - HTML5 2d canvas library for desktop and mobile applications.
103 | - [React-Sortable-Tree](https://github.com/frontend-collective/react-sortable-tree#options) - Drag-and-drop sortable component for nested data and hierarchies.
104 | 
105 | ## Acknowledgments
106 | 
107 | ### Logo Design
108 | 
109 | [Clariz Mariano](www.clarizmariano.com) [@havengoer](https://github.com/havengoer)
110 | 
111 | [Joe Thel](https://www.linkedin.com/in/joe-thel/) [@fakemonster](https://github.com/fakemonster)
112 | 
113 | 
114 | ## License
115 | 
116 | This project is licensed under the MIT License - see the [LICENSE.md](https://github.com/React-Proto/react-proto/blob/master/LICENSE.md) file for details.
117 | 


--------------------------------------------------------------------------------
/__mocks__/electron.js:
--------------------------------------------------------------------------------
1 | 
2 | const ipcRenderer = {
3 |   on: jest.fn(),
4 | };
5 | 
6 | export default ipcRenderer;
7 | 


--------------------------------------------------------------------------------
/assets/dragging.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/React-Proto/react-proto/e5e988c7afd961891e3dd8afc2c5c7bf813ae534/assets/dragging.gif


--------------------------------------------------------------------------------
/assets/export.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/React-Proto/react-proto/e5e988c7afd961891e3dd8afc2c5c7bf813ae534/assets/export.gif


--------------------------------------------------------------------------------
/assets/hierarchy.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/React-Proto/react-proto/e5e988c7afd961891e3dd8afc2c5c7bf813ae534/assets/hierarchy.gif


--------------------------------------------------------------------------------
/docs/assets/Aug-19-2018 13-43-20.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/React-Proto/react-proto/e5e988c7afd961891e3dd8afc2c5c7bf813ae534/docs/assets/Aug-19-2018 13-43-20.gif


--------------------------------------------------------------------------------
/docs/assets/BTPhoto.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/React-Proto/react-proto/e5e988c7afd961891e3dd8afc2c5c7bf813ae534/docs/assets/BTPhoto.jpg


--------------------------------------------------------------------------------
/docs/assets/IMG-1710.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/React-Proto/react-proto/e5e988c7afd961891e3dd8afc2c5c7bf813ae534/docs/assets/IMG-1710.jpeg


--------------------------------------------------------------------------------
/docs/assets/LadyB-007.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/React-Proto/react-proto/e5e988c7afd961891e3dd8afc2c5c7bf813ae534/docs/assets/LadyB-007.jpg


--------------------------------------------------------------------------------
/docs/assets/dragging.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/React-Proto/react-proto/e5e988c7afd961891e3dd8afc2c5c7bf813ae534/docs/assets/dragging.gif


--------------------------------------------------------------------------------
/docs/assets/export.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/React-Proto/react-proto/e5e988c7afd961891e3dd8afc2c5c7bf813ae534/docs/assets/export.gif


--------------------------------------------------------------------------------
/docs/assets/hierarchy.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/React-Proto/react-proto/e5e988c7afd961891e3dd8afc2c5c7bf813ae534/docs/assets/hierarchy.gif


--------------------------------------------------------------------------------
/docs/assets/proto-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/React-Proto/react-proto/e5e988c7afd961891e3dd8afc2c5c7bf813ae534/docs/assets/proto-logo.png


--------------------------------------------------------------------------------
/docs/assets/react-proto-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/React-Proto/react-proto/e5e988c7afd961891e3dd8afc2c5c7bf813ae534/docs/assets/react-proto-logo.png


--------------------------------------------------------------------------------
/docs/assets/screen-shot-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/React-Proto/react-proto/e5e988c7afd961891e3dd8afc2c5c7bf813ae534/docs/assets/screen-shot-2.png


--------------------------------------------------------------------------------
/docs/assets/sprites.svg:
--------------------------------------------------------------------------------
 1 | <svg aria-hidden="true" style="position: absolute; width: 0; height: 0; overflow: hidden;" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
 2 | <defs>
 3 | <symbol id="icon-github" viewBox="0 0 32 32">
 4 | <title>github</title>
 5 | <path d="M16 0.395c-8.836 0-16 7.163-16 16 0 7.069 4.585 13.067 10.942 15.182 0.8 0.148 1.094-0.347 1.094-0.77 0-0.381-0.015-1.642-0.022-2.979-4.452 0.968-5.391-1.888-5.391-1.888-0.728-1.849-1.776-2.341-1.776-2.341-1.452-0.993 0.11-0.973 0.11-0.973 1.606 0.113 2.452 1.649 2.452 1.649 1.427 2.446 3.743 1.739 4.656 1.33 0.143-1.034 0.558-1.74 1.016-2.14-3.554-0.404-7.29-1.777-7.29-7.907 0-1.747 0.625-3.174 1.649-4.295-0.166-0.403-0.714-2.030 0.155-4.234 0 0 1.344-0.43 4.401 1.64 1.276-0.355 2.645-0.532 4.005-0.539 1.359 0.006 2.729 0.184 4.008 0.539 3.054-2.070 4.395-1.64 4.395-1.64 0.871 2.204 0.323 3.831 0.157 4.234 1.026 1.12 1.647 2.548 1.647 4.295 0 6.145-3.743 7.498-7.306 7.895 0.574 0.497 1.085 1.47 1.085 2.963 0 2.141-0.019 3.864-0.019 4.391 0 0.426 0.288 0.925 1.099 0.768 6.354-2.118 10.933-8.113 10.933-15.18 0-8.837-7.164-16-16-16z"></path>
 6 | </symbol>
 7 | <symbol id="icon-appleinc" viewBox="0 0 32 32">
 8 | <title>appleinc</title>
 9 | <path d="M24.734 17.003c-0.040-4.053 3.305-5.996 3.454-6.093-1.88-2.751-4.808-3.127-5.851-3.171-2.492-0.252-4.862 1.467-6.127 1.467-1.261 0-3.213-1.43-5.28-1.392-2.716 0.040-5.221 1.579-6.619 4.012-2.822 4.897-0.723 12.151 2.028 16.123 1.344 1.944 2.947 4.127 5.051 4.049 2.026-0.081 2.793-1.311 5.242-1.311s3.138 1.311 5.283 1.271c2.18-0.041 3.562-1.981 4.897-3.931 1.543-2.255 2.179-4.439 2.216-4.551-0.048-0.022-4.252-1.632-4.294-6.473zM20.705 5.11c1.117-1.355 1.871-3.235 1.665-5.11-1.609 0.066-3.559 1.072-4.713 2.423-1.036 1.199-1.942 3.113-1.699 4.951 1.796 0.14 3.629-0.913 4.747-2.264z"></path>
10 | </symbol>
11 | <symbol id="icon-windows8" viewBox="0 0 32 32">
12 | <title>windows8</title>
13 | <path d="M0.011 16l-0.011-9.752 12-1.63v11.382zM14 4.328l15.996-2.328v14h-15.996zM30 18l-0.004 14-15.996-2.25v-11.75zM12 29.495l-11.99-1.644-0.001-9.851h11.991z"></path>
14 | </symbol>
15 | <symbol id="icon-linkedin" viewBox="0 0 32 32">
16 | <title>linkedin</title>
17 | <path d="M29 0h-26c-1.65 0-3 1.35-3 3v26c0 1.65 1.35 3 3 3h26c1.65 0 3-1.35 3-3v-26c0-1.65-1.35-3-3-3zM12 26h-4v-14h4v14zM10 10c-1.106 0-2-0.894-2-2s0.894-2 2-2c1.106 0 2 0.894 2 2s-0.894 2-2 2zM26 26h-4v-8c0-1.106-0.894-2-2-2s-2 0.894-2 2v8h-4v-14h4v2.481c0.825-1.131 2.087-2.481 3.5-2.481 2.488 0 4.5 2.238 4.5 5v9z"></path>
18 | </symbol>
19 | </defs>
20 | </svg>
21 | 


--------------------------------------------------------------------------------
/docs/index.html:
--------------------------------------------------------------------------------
  1 | <!DOCTYPE html>
  2 | <html lang="en">
  3 | 
  4 | <head>
  5 |   <meta charset="UTF-8">
  6 |   <meta name="viewport" content="width=device-width, initial-scale=1.0">
  7 |   <meta http-equiv="X-UA-Compatible" content="ie=edge">
  8 |   <link rel="stylesheet" href="style.css">
  9 |   <link href="https://fonts.googleapis.com/css?family=Roboto:300,400,700" rel="stylesheet">
 10 |   <!-- Place this tag in your head or just before your close body tag. -->
 11 |   <script async defer src="https://buttons.github.io/buttons.js"></script>
 12 |   <title>React Proto</title>
 13 | </head>
 14 | 
 15 | <body>
 16 |   <nav class="nav">
 17 |     <div class="nav-logo">
 18 |       <img src="./assets/proto-logo.png" alt="" srcset="">
 19 |     </div>
 20 |     <ul class="nav-list">
 21 |       <li class="nav-item">
 22 |         <a href="#about" class="nav-link">About</a>
 23 |       </li>
 24 |       <li class="nav-item">
 25 |         <a href="#team" class="nav-link">Team</a>
 26 |       </li>
 27 |       <li class="nav-item">
 28 |         <a href="https://github.com/React-Proto/react-proto" target="_blank">
 29 |           <svg class="nav-icon">
 30 |             <use xlink:href="assets/sprites.svg#icon-github"></use>
 31 |           </svg>
 32 |         </a>
 33 |       </li>
 34 |     </ul>
 35 |   </nav>
 36 | 
 37 |   <section class="hero">
 38 |     <div class="hero-content">
 39 |       <h1 class="hero-title">React Proto</h1>
 40 |       <h3 class="hero-subtitle">React prototyping for designers and developers</h3>
 41 |       <div class="buttons">
 42 |         <a href="https://github.com/React-Proto/react-proto">
 43 |           <button class="btn-primary hero-btn">Checkout our Github</button>
 44 |         </a>
 45 |         <a href="#download">
 46 |           <button class="btn-primary hero-btn">Download React Proto</button>
 47 |         </a>
 48 |       </div>
 49 |       <div class="github-buttons">
 50 |         <!-- Place this tag where you want the button to render. -->
 51 |         <a class="github-button" href="https://github.com/React-Proto/react-proto" aria-label="Follow on GitHub">Follow</a>
 52 |         <!-- Place this tag where you want the button to render. -->
 53 |         <a class="github-button" href="https://github.com/React-Proto/react-proto/" data-icon="octicon-eye" aria-label="Watch ntkme/github-buttons on GitHub">Watch</a>
 54 |         <!-- Place this tag where you want the button to render. -->
 55 |         <a class="github-button" href="https://github.com/React-Proto/react-proto/" data-icon="octicon-star" aria-label="Star ntkme/github-buttons on GitHub">Star</a>
 56 |       </div>
 57 |     </div>
 58 |     <div class="hero-image">
 59 |       <img src="./assets/screen-shot-2.png" alt="" srcset="">
 60 |     </div>
 61 |   </section>
 62 | 
 63 |   <section class="about" id="about">
 64 |     <div class="about-container">
 65 |       <div class="about-details">
 66 |         <div class="about-detail">
 67 |           <h3 class="detail-title">Quick Prototyping</h3>
 68 |           <p class="detail-desc">
 69 |             Quickly create, drag, and resize components to create a visual representaion of your application
 70 |         </div>
 71 |       </div>
 72 |       <div class="about-image">
 73 |         <img src="assets/dragging.gif" alt="">
 74 |       </div>
 75 |     </div>
 76 |     <div class="about-container">
 77 |       <div class="about-details">
 78 |         <div class="about-detail">
 79 |           <h3 class="detail-title">Define component hierarchy </h3>
 80 |           <p class="detail-desc">
 81 |             Define parent and child components along with props and state
 82 |           </p>
 83 |         </div>
 84 |       </div>
 85 |       <div class="about-image">
 86 |         <img src="assets/hierarchy.gif" alt="">
 87 |       </div>
 88 |     </div>
 89 |     <div class="about-container">
 90 |       <div class="about-details">
 91 |         <div class="about-detail">
 92 |           <h3 class="detail-title">Export Files</h3>
 93 |           <p class="detail-desc">
 94 |             Inject files into an existing project, start a new project using create-react-app, or clone your favorite github boilerplate
 95 |           </p>
 96 |         </div>
 97 |       </div>
 98 |       <div class="about-image">
 99 |         <img src="assets/export.gif" alt="">
100 |       </div>
101 |     </div>
102 |   </section>
103 | 
104 |   <section class="download" id="download">
105 |     <h1 class="section-title">Download React Proto</h1>
106 |     <div class="download-buttons">
107 |       <a href="https://github.com/React-Proto/react-proto/releases/download/v1.0.0/React-Proto-1.0.0.dmg">
108 |         <button class="btn-primary">Download for Mac</button>
109 |       </a>
110 |       <a href="https://github.com/React-Proto/react-proto/releases/download/v1.0.0/React-Proto.Web.Setup.1.0.0.exe">
111 |         <button class="btn-primary">Download for Windows</button>
112 |       </a>
113 |       <a href="https://github.com/React-Proto/react-proto/releases/download/v1.0.0/react-proto_1.0.0_amd64.deb">
114 |         <button class="btn-primary">Download for Linux</button>
115 |       </a>
116 |     </div>
117 |   </section>
118 | 
119 |   <section class="team" id="team">
120 |     <h1 class="section-title">Meet Our Team</h1>
121 |     <div class="team-profiles">
122 |       <div class="team-profile">
123 |         <div class="team-images blessing">
124 |           <!-- <img src="./assets/IMG-1710.jpeg" alt="" srcset=""> -->
125 |         </div>
126 |         <h5 class="team-name">Blessing Ebowe</h5>
127 |         <div class="team-links">
128 |           <a href="https://github.com/refinedblessing" target="_blank">
129 |             <svg class="team-icon">
130 |               <use xlink:href="assets/sprites.svg#icon-github"></use>
131 |             </svg>
132 |           </a>
133 |           <a href="https://www.linkedin.com/in/blessingebowe/" target="_blank">
134 |             <svg class="team-icon">
135 |               <use xlink:href="assets/sprites.svg#icon-linkedin"></use>
136 |             </svg>
137 |           </a>
138 |         </div>
139 |       </div>
140 | 
141 |       <div class="team-profile">
142 |         <div class="team-images erik">
143 |           <!-- <img src="./assets/IMG-1710.jpeg" alt="" srcset=""> -->
144 |         </div>
145 |         <h5 class="team-name">Erik Guntner</h5>
146 |         <div class="team-links">
147 |           <a href="https://github.com/erikguntner" target="_blank">
148 |             <svg class="team-icon">
149 |               <use xlink:href="assets/sprites.svg#icon-github"></use>
150 |             </svg>
151 |           </a>
152 |           <a href="https://www.linkedin.com/in/erik-guntner-9aa324b9/" target="_blank">
153 |             <svg class="team-icon">
154 |               <use xlink:href="assets/sprites.svg#icon-linkedin"></use>
155 |             </svg>
156 |           </a>
157 |         </div>
158 |       </div>
159 | 
160 |       <div class="team-profile">
161 |         <div class="team-images brian">
162 |           <!-- <img src="./assets/BTPhoto.jpg" alt="" srcset=""> -->
163 |         </div>
164 |         <h5 class="team-name">Brian Taylor</h5>
165 |         <div class="team-links">
166 |           <a href="https://github.com/brianwtaylor" target="_blank">
167 |             <svg class="team-icon ">
168 |               <use xlink:href="assets/sprites.svg#icon-github "></use>
169 |             </svg>
170 |           </a>
171 |           <a href="https://www.linkedin.com/in/brianwtaylor/" target="_blank">
172 |             <svg class="team-icon">
173 |               <use xlink:href="assets/sprites.svg#icon-linkedin"></use>
174 |             </svg>
175 |           </a>
176 |         </div>
177 |       </div>
178 |     </div>
179 |   </section>
180 | </body>
181 | 
182 | </html>
183 | 


--------------------------------------------------------------------------------
/docs/style.css:
--------------------------------------------------------------------------------
  1 | :root {
  2 |   --color-primary: #00e676;
  3 |   --color-primary-light: #1de9b6;
  4 |   --color-primary-dark: #14a37f;
  5 |   --color-secondary: #f44336;
  6 |   --color-grey-light-1: #faf9f9;
  7 |   --color-grey-light-2: #f4f2f2;
  8 |   --color-grey-light-3: #f0eeee;
  9 |   --color-grey-light-4: #eee;
 10 |   /* --color-grey-dark-1: #333;
 11 |   --color-grey-dark-2: #777;
 12 |   --color-grey-dark-3: #999d; */
 13 |   --color-dary-grey-1: #212121;
 14 |   --color-dark-grey-2: #303030;
 15 |   --border-bottom: 1px solid var(--color-primary);
 16 |   --shadow-dark: 0 2rem 6rem rgba(0, 0, 0, 0.3);
 17 |   --shadow-light: 3px 3px 3rem rgba(0, 0, 0, 0.4);
 18 |   --line: 1px solid var(--color-grey-light-2);
 19 | }
 20 | 
 21 | * {
 22 |   margin: 0;
 23 |   padding: 0;
 24 | }
 25 | 
 26 | *,
 27 | *::before,
 28 | *::after {
 29 |   box-sizing: inherit;
 30 | }
 31 | 
 32 | html {
 33 |   box-sizing: border-box;
 34 |   font-size: 62.5%;
 35 | }
 36 | 
 37 | body {
 38 |   font-family: "Open sans", sans-serif;
 39 |   font-weight: 400;
 40 |   line-height: 1.6;
 41 | }
 42 | 
 43 | /* ********************************* */
 44 | /* BUTTONS */
 45 | /* ********************************* */
 46 | 
 47 | .btn-primary {
 48 |   border: 1px solid var(--color-primary-light);
 49 |   font-family: inherit;
 50 |   background: transparent;
 51 |   font-weight: 300;
 52 |   color: var(--color-primary-light);
 53 |   font-size: 1.7rem;
 54 |   padding: 1.2rem 2rem;
 55 |   transition: all 0.2s ease;
 56 | }
 57 | 
 58 | .btn-primary:hover {
 59 |   background-color: var(--color-primary-light);
 60 |   color: var(--color-grey-light-1);
 61 |   cursor: pointer;
 62 | }
 63 | 
 64 | .section-title {
 65 |   font-weight: 300;
 66 |   font-size: 4rem;
 67 |   text-decoration: underline var(--color-secondary);
 68 |   margin-bottom: 4rem;
 69 | }
 70 | 
 71 | .hero-btn:not(:last-child) {
 72 |  margin-bottom: 2rem;
 73 | }
 74 | 
 75 | 
 76 | /* ********************************* */
 77 | /* NAV */
 78 | /* ********************************* */
 79 | 
 80 | 
 81 | .nav {
 82 |   width: 100%;
 83 |   display: flex;
 84 |   padding: 2rem;
 85 |   justify-content: start;
 86 |   align-items: center;
 87 |   position: relative;
 88 |   background-color: var(--color-dary-grey-1);
 89 |   /* background-color: var(--color-primary); */
 90 | }
 91 | 
 92 | .nav-logo {
 93 |   margin-right: auto;
 94 | }
 95 | 
 96 | .nav-logo > img {
 97 | 
 98 |   width: 4rem;
 99 |   height: 4rem;
100 | }
101 | 
102 | .nav-list {
103 |   display: flex;
104 |   list-style: none;
105 | }
106 | 
107 | .nav-item {
108 |   font-size: 1.5rem;
109 | }
110 | 
111 | .nav-item:not(:last-child) {
112 |   padding-right: 6rem;
113 | }
114 | 
115 | .nav-logo {
116 |   text-decoration: none;
117 |   color: var(--color-grey-light-4);
118 | }
119 | 
120 | .nav-link {
121 |   text-decoration: none;
122 |   color: var(--color-grey-light-4);
123 |   transition: all 0.3s ease;
124 |   padding-bottom: 5px;
125 | }
126 | .nav-link:hover {
127 |   color: var(--color-primary-light);
128 |   box-shadow: var(--shadow-light);
129 |   cursor: pointer;
130 |   border-bottom: 4px solid var(--color-secondary);
131 | }
132 | 
133 | .nav-icon {
134 |   height: 2.5rem;
135 |   width: 2.5rem;
136 |   fill: #fff;
137 | }
138 | 
139 | .nav-icon:hover {
140 |   fill: var(--color-primary-light);
141 |   box-shadow: var(--shadow-light);
142 |   cursor: pointer;
143 | }
144 | 
145 | /* ********************************* */
146 | /* NAV */
147 | /* ********************************* */
148 | 
149 | .hero {
150 |   height: 87vh;
151 |   /* background-image: url(assets/what-the-hex-dark.png); */
152 |   background-color: var(--color-grey-light-1);
153 |   background-size: cover;
154 |   background-position: center;
155 |   /* clip-path: polygon(0 0, 100% 0, 100% 93%, 50% 100%, 0 93%); */
156 |   display: flex;
157 |   justify-content: center;
158 |   align-items: center;
159 |   padding: 8rem;
160 | }
161 | 
162 | .hero-content {
163 |   width: 50%;
164 |   display: flex;
165 |   flex-direction: column; 
166 |   justify-content: center;
167 |   align-items: flex-start;
168 |   margin-right: 2rem;
169 |   animation: fadeIn .5s linear;
170 | }
171 | 
172 | .hero-title {
173 |   font-size: 7rem;
174 |   font-weight: 400;
175 |   text-decoration: underline var(--color-secondary);
176 |   color: var(--color-dary-grey-1);
177 | }
178 | 
179 | .hero-image {
180 |   width: 70%;
181 |   /* background-color: green; */
182 |   display: flex;
183 |   flex: 1 1 auto; 
184 |   justify-content: center;
185 |   align-items: center;
186 | }
187 | 
188 | .hero-image img {
189 |   width: 100%;
190 |   box-shadow: var(--shadow-light);
191 | }
192 | 
193 | .hero-subtitle {
194 |   font-size: 3rem;
195 |   font-weight: 300;
196 |   margin-bottom: 3rem; 
197 |   /* text-align: center; */
198 |   color: var(--color-dary-grey-1);
199 | }
200 | 
201 | @media screen and (max-width: 992px) {
202 |   .hero-image {
203 |     display: none;
204 |   }
205 | 
206 |   .hero-title {
207 |     font-size: 4rem;
208 |   }
209 | 
210 |   .hero-subtitle {
211 |     font-size: 2rem;
212 |   }
213 | }
214 | 
215 | 
216 | /* ********************************* */
217 | /* ABOUT */
218 | /* ********************************* */
219 | 
220 | .about {
221 |  margin: 7rem 10rem;
222 | }
223 | 
224 | .about-container {
225 |   width: 100%;
226 |   display: flex;
227 | }
228 | 
229 | .about-container:not(:last-child) {
230 |   margin-bottom: 5rem;
231 | }
232 | 
233 | .about-details {
234 |   width: 50%;
235 |   height: 400px;
236 |   display: flex;
237 |   flex-direction: column;
238 |   margin-right: 2rem;
239 |   justify-content: center;
240 |   /* background-color: coral; */
241 | }
242 | 
243 | .about-detail:not(:last-child) {
244 |   margin-bottom: 2rem;
245 | }
246 | 
247 | .about-detail {
248 |  padding-top: 1.5rem;
249 | }
250 | 
251 | 
252 | @media screen and (max-width: 992px) {
253 |   .about-container {
254 |     display: flex;
255 |     flex-direction: column;
256 |   }
257 | 
258 | }
259 | 
260 | 
261 | .detail-icon {
262 |   fill: var(--color-primary-light);
263 |   height: 3rem;
264 |   width: 3rem;
265 | }
266 | 
267 | .detail-title {
268 |   font-size: 3.5rem;
269 |   font-weight: 400;
270 |   text-decoration: underline var(--color-primary-light);
271 | }
272 | 
273 | .detail-desc {
274 |   font-size: 1.6rem;
275 |   color: var(--color-dark-grey-2);
276 | }
277 | 
278 | .about-image {
279 |   width: 70%;
280 |   /* background-color: green; */
281 |   display: flex;
282 |   flex: 1 1 auto; 
283 |   justify-content: center;
284 |   align-items: center;
285 | }
286 | 
287 | .about-image img {
288 |   width: 100%;
289 |   box-shadow: var(--shadow-light);
290 | }
291 | 
292 | /* ********************************* */
293 | /* DOWNLOAD */
294 | /* ********************************* */
295 | 
296 | .download {
297 |   display: flex;
298 |   flex-direction: column;
299 |   align-items: center;
300 |   background-color: var(--color-grey-light-1);
301 |   padding: 8rem 0;
302 | }
303 | 
304 | .buttons {
305 |   display: flex;
306 |   flex-direction: column;
307 |   margin-bottom: 2rem;
308 | }
309 | 
310 | .btn-primary:not(:last-child) {
311 |   margin-right: 2rem;
312 | }
313 | 
314 | 
315 | /* ********************************* */
316 | /* TEAM */
317 | /* ********************************* */
318 | 
319 | .team {
320 |   display: flex;
321 |   flex-direction: column;
322 |   justify-content: center;
323 |   align-items: center;
324 |   padding: 4rem 0;
325 |   margin-top: 2rem;
326 | }
327 | 
328 | .team:not(:last-child) {
329 |   margin-right: 2rem;
330 | }
331 | 
332 | .team-profiles {
333 |   display: flex;
334 |   justify-content: center;
335 | }
336 | 
337 | .team-profile:not(:last-child) {
338 |   margin-right: 2rem;
339 | }
340 | 
341 | .team-profile {
342 |   display: flex;
343 |   flex-direction: column;
344 |   align-items: center;
345 | }
346 | 
347 | .team-image {
348 |   width: 30rem;
349 |   /* background-color: green; */
350 |   display: flex;
351 |   flex: 1 1 auto; 
352 |   justify-content: center;
353 |   align-items: center;
354 | }
355 | 
356 | .team-profile:not(:last-child) {
357 |   margin-right: 4rem;
358 | }
359 | 
360 | .erik {
361 |   background-image: url(assets/IMG-1710.jpeg);
362 | }
363 | 
364 | 
365 | .brian {
366 |   background-image: url(assets/BTPhoto.jpg);
367 | }
368 | 
369 | .blessing {
370 |   background-image: url(assets/LadyB-007.jpg);
371 | }
372 | 
373 | .team-images {
374 |   width: 30rem;
375 |   height: 30rem;
376 |   border-radius: 50%;
377 |   background-size: cover;
378 |   background-position: center;
379 |   display: flex;
380 |   flex: 1 1 auto; 
381 |   justify-content: center;
382 |   align-items: center;
383 | }
384 | 
385 | .team-image img {
386 |   width: 100%;
387 | }
388 | 
389 | .team-name {
390 |   font-weight: 400;
391 |   font-size: 2rem;
392 | }
393 | 
394 | .team-icon {
395 |   height: 3rem;
396 |   width: 3rem;
397 |   transition: all .2s ease;
398 |   margin-right: 1rem;
399 | }
400 | 
401 | .team-icon:hover {
402 |   fill: var(--color-primary-light);
403 | }
404 | 
405 | 
406 | a {
407 |   text-decoration: none;
408 | }
409 | 
410 | /* ********************************* */
411 | /* KEYFRAMES */
412 | /* ********************************* */
413 | 
414 | @keyframes fadeIn {
415 |   from {
416 |     transform: translateY(50px);
417 |     opacity: 0;
418 |   }
419 |   to {
420 |     transform: translateY(0);
421 |     opacity: 1;
422 |   }
423 | }
424 | 
425 | @media screen and (max-width: 800px) {
426 |   .team-profiles {
427 |     flex-direction: column;
428 |   }
429 | 
430 |   .team-profile:not(:last-child) {
431 |     margin-bottom: 4rem;
432 |   }
433 | }
434 | 
435 | @media screen and (max-width: 450px) {
436 | 
437 |   .nav-item:not(:last-child) {
438 |     padding-right: 1rem;
439 |   }
440 | 
441 |   .hero {
442 |     padding: 1rem;
443 |   }
444 | 
445 |   .hero-image {
446 |     display: none;
447 |   }
448 | 
449 |   .hero-title {
450 |     font-size: 3rem;
451 |   }
452 | 
453 |   .hero-subtitle {
454 |     font-size: 1.5rem;
455 |   }
456 | 
457 |   .btn-primary {
458 |     font-size: 1.5rem;
459 |     padding: 5px;
460 |   }
461 | 
462 |   .about {
463 |     margin: 5rem 5rem;
464 |     display: flex;
465 |     flex-direction: column;
466 |     justify-content: center;
467 |     align-items: center;
468 |    }
469 | 
470 |    .about-image {
471 |      width: 100%;
472 |    }
473 | 
474 |    .about-container {
475 |     display: flex;
476 |     flex-direction: column;
477 |     justify-content: center;
478 |     align-items: center;
479 |    };
480 | 
481 |   .section-title {
482 |     font-size: 2rem;
483 |   }
484 | 
485 |   .team-profiles {
486 |     display: flex;
487 |     flex-direction: column;
488 |   }
489 | 
490 |   .team-profile:not(:last-child) {
491 |     margin-bottom: 4rem;
492 |   }
493 | }


--------------------------------------------------------------------------------
/electron-builder.yml:
--------------------------------------------------------------------------------
 1 | directories:
 2 |   output: dist
 3 |   buildResources: build
 4 | appId: com.eevee.react-proto
 5 | copyright: Copyright © 2018
 6 | linux:
 7 |   target:
 8 |     - AppImage
 9 |     - deb
10 |   maintainer: blessingebowe@gmail.com
11 | mac:
12 |   category: public.app-category.developer-tools
13 |   target: dmg
14 | nsis:
15 |   createStartMenuShortcut: true
16 |   createDesktopShortcut: true
17 |   runAfterFinish: true
18 | win:
19 |   target: nsis-web
20 | files:
21 |   - main.js
22 |   - build
23 | productName: React-Proto
24 | dmg:
25 |   contents:
26 |     - x: 110
27 |       'y': 150
28 |     - x: 240
29 |       'y': 150
30 |       type: link
31 |       path: /Applications
32 | electronVersion: 2.0.7
33 | 


--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
 1 | #!/usr/bin/env node
 2 | const util = require('util');
 3 | const program = require('commander');
 4 | const execFile = util.promisify(require('child_process').execFile);
 5 | const { Spinner } = require('cli-spinner');
 6 | 
 7 | const spinner = new Spinner('running app... %s');
 8 | spinner.setSpinnerString('|/-\\');
 9 | 
10 | program
11 |   .version('1.0.0', '-v, --version, -V')
12 |   .description('An application for prototyping React application.');
13 | 
14 | program
15 |   .command('start')
16 |   .description('Start-up react-proto app')
17 |   .action(() => {
18 |     spinner.start();
19 |     execFile('npm', ['start'])
20 |       .catch(err => console.log(err));
21 |   });
22 | 
23 | program.parse(process.argv);
24 | 


--------------------------------------------------------------------------------
/main.js:
--------------------------------------------------------------------------------
  1 | const {
  2 |   app,
  3 |   BrowserWindow,
  4 |   Menu,
  5 |   shell,
  6 |   dialog,
  7 |   ipcMain,
  8 | } = require('electron');
  9 | 
 10 | const isDev = process.env.NODE_ENV === 'development' || process.env.NODE_ENV === 'test';
 11 | 
 12 | // Keep a global reference of the window object, if you don't, the window will
 13 | // be closed automatically when the JavaScript object is garbage collected.
 14 | let mainWindow;
 15 | 
 16 | // Open image file
 17 | function openFile() {
 18 |   // Opens file dialog looking for markdown
 19 |   const files = dialog.showOpenDialog(mainWindow, {
 20 |     properties: ['openFile'],
 21 |     filters: [{
 22 |       name: 'Images', extensions: ['jpeg', 'jpg', 'png', 'gif', 'pdf'],
 23 |     }],
 24 |   });
 25 | 
 26 |   // if no files
 27 |   if (!files) return;
 28 |   const file = files[0];
 29 | 
 30 |   // Send fileContent to renderer
 31 |   mainWindow.webContents.send('new-file', file);
 32 | }
 33 | 
 34 | // Choose directory
 35 | ipcMain.on('choose_app_dir', (event) => {
 36 |   const directory = dialog.showOpenDialog(mainWindow, {
 37 |     properties: ['openDirectory'],
 38 |   });
 39 | 
 40 |   if (!directory) return;
 41 |   event.sender.send('app_dir_selected', directory[0]);
 42 | });
 43 | 
 44 | ipcMain.on('view_app_dir', (event, appDir) => {
 45 |   shell.openItem(appDir);
 46 | });
 47 | 
 48 | // Update file
 49 | ipcMain.on('update-file', () => {
 50 |   openFile();
 51 | });
 52 | 
 53 | const createWindow = () => {
 54 |   // Create the browser window.
 55 |   // eslint-disable-next-line
 56 |   const { width, height } = require('electron').screen.getPrimaryDisplay().size;
 57 |   mainWindow = new BrowserWindow({
 58 |     width,
 59 |     height,
 60 |   });
 61 | 
 62 |   // and load the index.html of the app.
 63 |   mainWindow.loadURL(`file://${__dirname}/build/index.html`);
 64 | 
 65 |   const template = [{
 66 |     label: 'File',
 67 |     submenu: [{
 68 |       label: 'Open File',
 69 |       accelerator: process.platform === 'darwin' ? 'Cmd+O' : 'Ctrl+Shift+O',
 70 |       click() {
 71 |         openFile();
 72 |       },
 73 |     }],
 74 |   },
 75 |   {
 76 |     label: 'Edit',
 77 |     submenu: [
 78 |       { role: 'undo' },
 79 |       { role: 'redo' },
 80 |       { type: 'separator' },
 81 |       { role: 'cut' },
 82 |       { role: 'copy' },
 83 |       { role: 'paste' },
 84 |       { role: 'pasteandmatchstyle' },
 85 |       { role: 'delete' },
 86 |       { role: 'selectall' },
 87 |     ],
 88 |   },
 89 |   {
 90 |     label: 'View',
 91 |     submenu: [
 92 |       { role: 'reload' },
 93 |       { role: 'forcereload' },
 94 |       { role: 'toggledevtools' },
 95 |       { type: 'separator' },
 96 |       { role: 'resetzoom' },
 97 |       { role: 'zoomin' },
 98 |       { role: 'zoomout' },
 99 |       { type: 'separator' },
100 |       { role: 'togglefullscreen' },
101 |     ],
102 |   },
103 |   {
104 |     role: 'window',
105 |     submenu: [
106 |       { role: 'minimize' },
107 |       { role: 'close' },
108 |     ],
109 |   },
110 |   {
111 |     role: 'help',
112 |     submenu: [{
113 |       label: 'Learn More',
114 |       click() {
115 |         shell.openExternal('https://electronjs.org');
116 |       },
117 |     }],
118 |   },
119 |   {
120 |     label: 'Developer',
121 |     submenu: [{
122 |       label: 'Toggle Developer Tools',
123 |       accelerator: process.platform === 'darwin' ? 'Alt+Command+I' : 'Ctrl+Shift+I',
124 |       click() {
125 |         mainWindow.webContents.toggleDevTools();
126 |       },
127 |     }],
128 |   },
129 |   ];
130 | 
131 |   if (process.platform === 'darwin') {
132 |     template.unshift({
133 |       label: app.getName(),
134 |       submenu: [
135 |         { role: 'about' },
136 |         { type: 'separator' },
137 |         { role: 'services', submenu: [] },
138 |         { type: 'separator' },
139 |         { role: 'hide' },
140 |         { role: 'hideothers' },
141 |         { role: 'unhide' },
142 |         { type: 'separator' },
143 |         { role: 'quit' },
144 |       ],
145 |     });
146 | 
147 |     // Edit menu
148 |     template[2].submenu.push({
149 |       type: 'separator',
150 |     }, {
151 |       label: 'Speech',
152 |       submenu: [{ role: 'startspeaking' }, { role: 'stopspeaking' }],
153 |     });
154 | 
155 |     // Window menu
156 |     template[4].submenu = [
157 |       { role: 'close' },
158 |       { role: 'minimize' },
159 |       { role: 'zoom' },
160 |       { type: 'separator' },
161 |       { role: 'front' },
162 |     ];
163 |   }
164 | 
165 |   const menu = Menu.buildFromTemplate(template);
166 |   Menu.setApplicationMenu(menu);
167 | 
168 |   // Emitted when the window is closed.
169 |   mainWindow.on('closed', () => {
170 |     // Dereference the window object, usually you would store windows
171 |     // in an array if your app supports multi windows, this is the time
172 |     // when you should delete the corresponding element.
173 |     mainWindow = null;
174 |   });
175 | };
176 | 
177 | // This method will be called when Electron has finished
178 | // initialization and is ready to create browser windows.
179 | // Some APIs can only be used after this event occurs.
180 | app.on('ready', () => {
181 |   if (isDev) {
182 |     const {
183 |       default: installExtension,
184 |       REACT_DEVELOPER_TOOLS,
185 |       REDUX_DEVTOOLS,
186 |     } = require('electron-devtools-installer');
187 | 
188 |     installExtension([REACT_DEVELOPER_TOOLS, REDUX_DEVTOOLS])
189 |       .then(() => {
190 |         createWindow();
191 |       })
192 |       .catch(err => err);
193 |   } else {
194 |     createWindow();
195 |   }
196 | });
197 | 
198 | // Quit when all windows are closed.
199 | app.on('window-all-closed', () => {
200 |   // On OS X it is common for applications and their menu bar
201 |   // to stay active until the user quits explicitly with Cmd + Q
202 |   if (process.platform !== 'darwin') {
203 |     app.quit();
204 |   }
205 | });
206 | 
207 | app.on('activate', () => {
208 |   // On OS X it's common to re-create a window in the app when the
209 |   // dock icon is clicked and there are no other windows open.
210 |   if (mainWindow === null) {
211 |     createWindow();
212 |   }
213 | });
214 | 


--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
  1 | {
  2 |   "name": "react-proto",
  3 |   "version": "1.0.0",
  4 |   "description": "An application for prototyping React components.",
  5 |   "main": "main.js",
  6 |   "homepage": "https://cs-eevee.github.io/react-proto/",
  7 |   "repository": {
  8 |     "type": "git",
  9 |     "url": "https://github.com/CS-Eevee/react-proto"
 10 |   },
 11 |   "build": {
 12 |     "appId": "com.eevee.react-proto",
 13 |     "copyright": "Copyright © 2018",
 14 |     "linux": {
 15 |       "target": [
 16 |         "AppImage",
 17 |         "deb"
 18 |       ],
 19 |       "maintainer": "blessingebowe@gmail.com"
 20 |     },
 21 |     "mac": {
 22 |       "category": "public.app-category.developer-tools",
 23 |       "target": "dmg"
 24 |     },
 25 |     "nsis": {
 26 |       "createStartMenuShortcut": true,
 27 |       "createDesktopShortcut": true,
 28 |       "runAfterFinish": true
 29 |     },
 30 |     "win": {
 31 |       "target": "nsis-web"
 32 |     },
 33 |     "files": [
 34 |       "main.js",
 35 |       "build"
 36 |     ],
 37 |     "productName": "React-Proto",
 38 |     "dmg": {
 39 |       "contents": [
 40 |         {
 41 |           "x": 110,
 42 |           "y": 150
 43 |         },
 44 |         {
 45 |           "x": 240,
 46 |           "y": 150,
 47 |           "type": "link",
 48 |           "path": "/Applications"
 49 |         }
 50 |       ]
 51 |     }
 52 |   },
 53 |   "scripts": {
 54 |     "prestart": "cross-env NODE_ENV=production webpack --config webpack.config.production.js",
 55 |     "start": "cross-env NODE_ENV=production electron .",
 56 |     "dev": "cross-env NODE_ENV=development webpack --config webpack.config.development.js",
 57 |     "electron": "cross-env NODE_ENV=development electron .",
 58 |     "build": "cross-env NODE_ENV=production webpack --config webpack.config.production.js",
 59 |     "build-bin": "electron-builder -mwl",
 60 |     "test": "cross-env NODE_ENV=test jest",
 61 |     "linter": "eslint src"
 62 |   },
 63 |   "bin": {
 64 |     "react-proto": "./index.js"
 65 |   },
 66 |   "preferGlobal": true,
 67 |   "contributors": [
 68 |     "Blessing Ebowe <blessingebowe.repo@gmail.com> (https://www.linkedin.com/in/blessingebowe/)",
 69 |     "Brian Taylor <brianwtaylor@protonmail.com> (https://www.linkedin.com/in/brianwtaylor/)",
 70 |     "Erik Guntner <erikguntner@gmail.com> (https://www.linkedin.com/in/erik-guntner-9aa324b9/)"
 71 |   ],
 72 |   "license": "MIT",
 73 |   "jest": {
 74 |     "moduleNameMapper": {
 75 |       "^.+\\.(css|scss|styl|less|sass|scss|png|jpg|ttf|woff|woff2)
quot;: "identity-obj-proxy",
 76 |       "electron": "<rootDir>/__mocks__/electron.js"
 77 |     },
 78 |     "setupFiles": [
 79 |       "./src/setupTests.js"
 80 |     ]
 81 |   },
 82 |   "dependencies": {
 83 |     "@material-ui/core": "^1.4.1",
 84 |     "@material-ui/icons": "^2.0.0",
 85 |     "autoprefixer": "^9.0.1",
 86 |     "babel-polyfill": "^6.26.0",
 87 |     "classnames": "^2.2.6",
 88 |     "cli-spinner": "^0.2.8",
 89 |     "commander": "^2.17.1",
 90 |     "enzyme": "^3.4.1",
 91 |     "konva": "^2.1.7",
 92 |     "localforage": "^1.7.2",
 93 |     "lodash.throttle": "^4.1.1",
 94 |     "prettier": "^1.14.2",
 95 |     "prop-types": "^15.6.2",
 96 |     "react": "^16.4.1",
 97 |     "react-dom": "^16.4.1",
 98 |     "react-draggable": "^3.0.5",
 99 |     "react-konva": "^1.7.12",
100 |     "react-redux": "^5.0.7",
101 |     "react-sortable-tree": "^2.2.0",
102 |     "redux": "^4.0.0",
103 |     "redux-devtools-extension": "^2.13.5",
104 |     "redux-logger": "^3.0.6",
105 |     "redux-thunk": "^2.3.0"
106 |   },
107 |   "devDependencies": {
108 |     "babel-core": "^6.26.0",
109 |     "babel-eslint": "^8.2.6",
110 |     "babel-loader": "^7.1.4",
111 |     "babel-preset-env": "^1.6.1",
112 |     "babel-preset-react": "^6.24.1",
113 |     "babel-preset-stage-0": "^6.24.1",
114 |     "clean-webpack-plugin": "^0.1.19",
115 |     "copy-webpack-plugin": "^4.5.2",
116 |     "cross-env": "^5.2.0",
117 |     "css-loader": "^0.28.11",
118 |     "electron": "^2.0.7",
119 |     "electron-builder": "^20.28.1",
120 |     "electron-devtools-installer": "^2.2.4",
121 |     "electron-installer-dmg": "^2.0.0",
122 |     "enzyme-adapter-react-16": "^1.2.0",
123 |     "eslint": "^4.19.1",
124 |     "eslint-config-airbnb-base": "^13.0.0",
125 |     "eslint-plugin-babel": "^5.1.0",
126 |     "eslint-plugin-import": "^2.13.0",
127 |     "eslint-plugin-jest": "^21.21.0",
128 |     "eslint-plugin-jsx-a11y": "^6.1.1",
129 |     "eslint-plugin-react": "^7.10.0",
130 |     "extract-text-webpack-plugin": "^4.0.0-beta.0",
131 |     "html-webpack-plugin": "^3.1.0",
132 |     "identity-obj-proxy": "^3.0.0",
133 |     "jest": "^23.5.0",
134 |     "node-sass": "^4.9.2",
135 |     "postcss-loader": "^2.1.6",
136 |     "sass-loader": "^7.0.3",
137 |     "style-loader": "^0.20.3",
138 |     "webpack": "^4.4.0",
139 |     "webpack-cli": "^2.0.13"
140 |   }
141 | }
142 | 


--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | const prefixer = require('autoprefixer');
2 | 
3 | module.exports = {
4 |   plugins: [prefixer],
5 | };
6 | 


--------------------------------------------------------------------------------
/src/actionTypes/index.js:
--------------------------------------------------------------------------------
 1 | export const LOAD_INIT_DATA = 'LOAD_INIT_DATA';
 2 | export const ADD_COMPONENT = 'ADD_COMPONENT';
 3 | export const UPDATE_COMPONENT = 'UPDATE_COMPONENT';
 4 | export const DELETE_COMPONENT = 'DELETE_COMPONENT';
 5 | export const UPDATE_CHILDREN = 'UPDATE_CHILDREN';
 6 | export const REASSIGN_PARENT = 'REASSIGN_PARENT';
 7 | export const SET_SELECTABLE_PARENTS = 'SET_SELECTABLE_PARENTS';
 8 | export const EXPORT_FILES = 'EXPORT_FILES';
 9 | export const EXPORT_FILES_SUCCESS = 'EXPORT_FILES_SUCCESS';
10 | export const EXPORT_FILES_ERROR = 'EXPORT_FILES_ERROR';
11 | export const HANDLE_CLOSE = 'HANDLE_CLOSE';
12 | export const HANDLE_TRANSFORM = 'HANDLE_TRANSFORM';
13 | export const CREATE_APPLICATION = 'CREATE_APPLICATION';
14 | export const CREATE_APPLICATION_SUCCESS = 'CREATE_APPLICATION_SUCCESS';
15 | export const CREATE_APPLICATION_ERROR = 'CREATE_APPLICATION_ERROR';
16 | export const TOGGLE_DRAGGING = 'TOGGLE_DRAGGING';
17 | export const MOVE_TO_BOTTOM = 'MOVE_TO_BOTTOM';
18 | export const MOVE_TO_TOP = 'MOVE_TO_TOP';
19 | export const OPEN_EXPANSION_PANEL = 'OPEN_EXPANSION_PANEL';
20 | export const DELETE_PROP = 'DELETE_PROP';
21 | export const ADD_PROP = 'ADD_PROP';
22 | export const DELETE_ALL_DATA = 'DELETE_ALL_DATA';
23 | export const CHANGE_IMAGE_PATH = 'CHANGE_IMAGE_PATH';
24 | 


--------------------------------------------------------------------------------
/src/actions/components.js:
--------------------------------------------------------------------------------
  1 | import {
  2 |   LOAD_INIT_DATA,
  3 |   ADD_COMPONENT,
  4 |   UPDATE_COMPONENT,
  5 |   DELETE_COMPONENT,
  6 |   UPDATE_CHILDREN,
  7 |   REASSIGN_PARENT,
  8 |   SET_SELECTABLE_PARENTS,
  9 |   EXPORT_FILES,
 10 |   EXPORT_FILES_SUCCESS,
 11 |   EXPORT_FILES_ERROR,
 12 |   HANDLE_CLOSE,
 13 |   HANDLE_TRANSFORM,
 14 |   CREATE_APPLICATION,
 15 |   CREATE_APPLICATION_SUCCESS,
 16 |   CREATE_APPLICATION_ERROR,
 17 |   TOGGLE_DRAGGING,
 18 |   MOVE_TO_BOTTOM,
 19 |   MOVE_TO_TOP,
 20 |   OPEN_EXPANSION_PANEL,
 21 |   DELETE_PROP,
 22 |   ADD_PROP,
 23 |   DELETE_ALL_DATA,
 24 |   CHANGE_IMAGE_PATH,
 25 | } from '../actionTypes/index';
 26 | 
 27 | import { loadState } from '../localStorage';
 28 | 
 29 | import createFiles from '../utils/createFiles.util';
 30 | import createApplicationUtil from '../utils/createApplication.util';
 31 | 
 32 | export const loadInitData = () => (dispatch) => {
 33 |   loadState()
 34 |     .then(data => dispatch({
 35 |       type: LOAD_INIT_DATA,
 36 |       payload: {
 37 |         data: data ? data.workspace : {},
 38 |       },
 39 |     }));
 40 | };
 41 | 
 42 | export const updateChildren = (({
 43 |   parentIds, childIndex, childId,
 44 | }) => ({
 45 |   type: UPDATE_CHILDREN,
 46 |   payload: {
 47 |     parentIds, childIndex, childId,
 48 |   },
 49 | }));
 50 | 
 51 | export const parentReassignment = (({ index, id, parentIds }) => ({
 52 |   type: REASSIGN_PARENT,
 53 |   payload: {
 54 |     index,
 55 |     id,
 56 |     parentIds,
 57 |   },
 58 | }));
 59 | 
 60 | export const addComponent = ({ title }) => (dispatch) => {
 61 |   dispatch({ type: ADD_COMPONENT, payload: { title } });
 62 |   dispatch({ type: SET_SELECTABLE_PARENTS });
 63 | };
 64 | 
 65 | export const deleteComponent = ({ index, id, parentIds = [] }) => (dispatch) => {
 66 |   if (parentIds.length) {
 67 |     // Delete Component  from its parent if it has a parent.
 68 |     dispatch(updateChildren({ parentIds, childId: id, childIndex: index }));
 69 |   }
 70 |   // Reassign Component's children to its parent if it has one or make them orphans
 71 |   dispatch(parentReassignment({ index, id, parentIds }));
 72 | 
 73 |   dispatch({ type: DELETE_COMPONENT, payload: { index, id } });
 74 |   dispatch({ type: SET_SELECTABLE_PARENTS });
 75 | };
 76 | 
 77 | export const updateComponent = ({
 78 |   id, index, newParentId = null, color = null, stateful = null,
 79 | }) => (dispatch) => {
 80 |   dispatch({
 81 |     type: UPDATE_COMPONENT,
 82 |     payload: {
 83 |       id, index, newParentId, color, stateful,
 84 |     },
 85 |   });
 86 | 
 87 |   if (newParentId) {
 88 |     dispatch(updateChildren({ parentIds: [newParentId], childId: id, childIndex: index }));
 89 |   }
 90 | 
 91 |   dispatch({ type: SET_SELECTABLE_PARENTS });
 92 | };
 93 | 
 94 | export const exportFiles = ({ components, path }) => (dispatch) => {
 95 |   dispatch({
 96 |     type: EXPORT_FILES,
 97 |   });
 98 | 
 99 |   createFiles(components, path)
100 |     .then(dir => dispatch({
101 |       type: EXPORT_FILES_SUCCESS,
102 |       payload: { status: true, dir: dir[0] },
103 |     }))
104 |     .catch(err => dispatch({
105 |       type: EXPORT_FILES_ERROR,
106 |       payload: { status: true, err },
107 |     }));
108 | };
109 | 
110 | export const handleClose = () => ({
111 |   type: HANDLE_CLOSE,
112 |   payload: false,
113 | });
114 | 
115 | export const handleTransform = (id, {
116 |   x, y, width, height,
117 | }) => ({
118 |   type: HANDLE_TRANSFORM,
119 |   payload: {
120 |     id, x, y, width, height,
121 |   },
122 | });
123 | 
124 | // Application generation options
125 | // cosnt genOptions = [
126 | //   'Export into existing project.', 'Export with starter repo.', 'Export with create-react-app.'
127 | // ];
128 | 
129 | export const createApplication = ({
130 |   path, components = [], genOption, appName = 'proto_app', repoUrl,
131 | }) => (dispatch) => {
132 |   if (genOption === 0) {
133 |     dispatch(exportFiles({ path, components }));
134 |   } else if (genOption) {
135 |     dispatch({
136 |       type: CREATE_APPLICATION,
137 |     });
138 |     createApplicationUtil({
139 |       path, appName, genOption, repoUrl,
140 |     })
141 |       .then(() => {
142 |         dispatch({
143 |           type: CREATE_APPLICATION_SUCCESS,
144 |         });
145 |         dispatch(exportFiles({ path: `${path}/${appName}`, components }));
146 |       })
147 |       .catch(err => dispatch({
148 |         type: CREATE_APPLICATION_ERROR,
149 |         payload: { status: true, err },
150 |       }));
151 |   }
152 | };
153 | 
154 | export const toggleDragging = status => ({
155 |   type: TOGGLE_DRAGGING,
156 |   payload: status,
157 | });
158 | 
159 | export const moveToBottom = componentId => ({
160 |   type: MOVE_TO_BOTTOM,
161 |   payload: componentId,
162 | });
163 | 
164 | export const moveToTop = componentId => ({
165 |   type: MOVE_TO_TOP,
166 |   payload: componentId,
167 | });
168 | 
169 | export const openExpansionPanel = component => ({
170 |   type: OPEN_EXPANSION_PANEL,
171 |   payload: { component },
172 | });
173 | 
174 | export const deleteAllData = () => ({
175 |   type: DELETE_ALL_DATA,
176 | });
177 | 
178 | export const changeImagePath = path => ({
179 |   type: CHANGE_IMAGE_PATH,
180 |   payload: path,
181 | });
182 | 
183 | export const deleteCompProp = ({ id, index }) => ({
184 |   type: DELETE_PROP,
185 |   payload: { id, index },
186 | });
187 | 
188 | export const addCompProp = prop => ({
189 |   type: ADD_PROP,
190 |   payload: { ...prop },
191 | });
192 | 


--------------------------------------------------------------------------------
/src/components/App.jsx:
--------------------------------------------------------------------------------
 1 | import React, { Component } from 'react';
 2 | import '../public/styles/style.css';
 3 | import { MuiThemeProvider } from '@material-ui/core/styles';
 4 | import theme from './theme';
 5 | import AppContainer from '../containers/AppContainer.jsx';
 6 | 
 7 | class App extends Component {
 8 |   render() {
 9 |     return (
10 |       <MuiThemeProvider theme={theme}>
11 |         <div className="app">
12 |           <div>
13 |             <header style={{ height: '40px', width: '100%' }}>React Proto</header>
14 |             <AppContainer />
15 |           </div>
16 |         </div>
17 |       </MuiThemeProvider>
18 |     );
19 |   }
20 | }
21 | 
22 | export default App;
23 | 


--------------------------------------------------------------------------------
/src/components/Info.jsx:
--------------------------------------------------------------------------------
 1 | import React from "react";
 2 | 
 3 | const command = window.navigator.platform.match(/^mac/) ? 'Cmd+O' : 'Ctrl+Shift+O';
 4 | 
 5 | const Info = () => (
 6 |   <div className="info">
 7 |     <h1>Press {command} to upload an image</h1>
 8 |   </div>
 9 | );
10 | 
11 | export default Info;
12 | 


--------------------------------------------------------------------------------
/src/components/KonvaStage.jsx:
--------------------------------------------------------------------------------
  1 | import React, { Component, createRef } from 'react';
  2 | import PropTypes from 'prop-types';
  3 | import {
  4 |   Stage, Layer, Image, Group,
  5 | } from 'react-konva';
  6 | import TransformerComponent from './TransformerComponent.jsx';
  7 | import Rectangle from './Rectangle.jsx';
  8 | 
  9 | 
 10 | class KonvaStage extends Component {
 11 |   state = {
 12 |     x: undefined,
 13 |     y: undefined,
 14 |   };
 15 | 
 16 |   constructor(props) {
 17 |     super(props);
 18 |     this.main = createRef();
 19 |     this.group = createRef();
 20 |   }
 21 | 
 22 |   handleStageMouseDown = (e) => {
 23 |     // clicked on stage - cler selection
 24 |     if (e.target === e.target.getStage()) {
 25 |       this.props.openExpansionPanel({});
 26 |       return;
 27 |     }
 28 |     // clicked on transformer - do nothing
 29 |     const clickedOnTransformer = e.target.getParent().className === 'Transformer';
 30 |     if (clickedOnTransformer) {
 31 |       return;
 32 |     }
 33 | 
 34 |     // find clicked rect by its name
 35 |     const id = e.target.name();
 36 |     const rect = this.props.components.find(r => r.id === id);
 37 | 
 38 |     if (rect) {
 39 |       this.props.openExpansionPanel(rect);
 40 |     } else {
 41 |       this.props.openExpansionPanel({});
 42 |     }
 43 |   };
 44 | 
 45 |   // handleStageDrag = () => {
 46 |   //   // const mainWindowHeight = this.main.current.clientHeight;
 47 |   //   // const mainWindowWidth = this.main.current.clientWidth;
 48 |   //   // const groupX = this.refs.group.attrs.x;
 49 |   //   // const groupY = this.refs.group.attrs.y;
 50 | 
 51 |   //   // const componentX = (mainWindowWidth / 2) - groupX;
 52 |   //   // const componentY = (mainWindowHeight / 2) - groupY;
 53 |   //   // console.log(componentX, componentY);
 54 |   // }
 55 | 
 56 |   componentDidMount() {
 57 |     this.props.setImage();
 58 |   }
 59 | 
 60 |   render() {
 61 |     const {
 62 |       components, handleTransform, image, draggable, scaleX, scaleY, focusComponent,
 63 |     } = this.props;
 64 |     const { selectedShapeName } = this.state;
 65 | 
 66 |     return (
 67 |       <Stage
 68 |         ref={(node) => {
 69 |           this.stage = node;
 70 |         }}
 71 |         onMouseDown={this.handleStageMouseDown}
 72 |         width={window.innerWidth}
 73 |         height={window.innerHeight}
 74 |       >
 75 |         <Layer>
 76 |           <Group
 77 |             scaleX={scaleX}
 78 |             scaleY={scaleY}
 79 |             ref={(node) => {
 80 |               this.group = node;
 81 |             }}
 82 |             draggable={draggable}>
 83 |             <Image image={image} />
 84 |             {components.map((comp, i) => <Rectangle
 85 |               draggable={comp.draggable}
 86 |               selectedShapeName={selectedShapeName}
 87 |               key={i}
 88 |               componentId={comp.id}
 89 |               x={comp.position.x}
 90 |               y={comp.position.y}
 91 |               width={comp.position.width}
 92 |               height={comp.position.height}
 93 |               title={comp.title}
 94 |               color={comp.color}
 95 |               handleTransform={handleTransform}
 96 |             />)}
 97 |             <TransformerComponent
 98 |               focusComponent={focusComponent}
 99 |               selectedShapeName={selectedShapeName}
100 |             />
101 |           </Group>
102 |         </Layer>
103 |       </Stage>
104 |     );
105 |   }
106 | }
107 | 
108 | KonvaStage.propTypes = {
109 |   draggable: PropTypes.bool.isRequired,
110 |   components: PropTypes.array.isRequired,
111 |   handleTransform: PropTypes.func.isRequired,
112 |   image: PropTypes.oneOfType([
113 |     PropTypes.string,
114 |     PropTypes.object,
115 |   ]),
116 |   scaleX: PropTypes.number.isRequired,
117 |   scaleY: PropTypes.number.isRequired,
118 |   openExpansionPanel: PropTypes.func.isRequired,
119 |   setImage: PropTypes.func.isRequired,
120 |   focusComponent: PropTypes.object.isRequired,
121 | };
122 | 
123 | export default KonvaStage;
124 | 


--------------------------------------------------------------------------------
/src/components/LeftColExpansionPanel.jsx:
--------------------------------------------------------------------------------
  1 | import React from 'react';
  2 | import PropTypes from 'prop-types';
  3 | import { withStyles } from '@material-ui/core/styles';
  4 | import ExpansionPanel from '@material-ui/core/ExpansionPanel';
  5 | import ExpansionPanelSummary from '@material-ui/core/ExpansionPanelSummary';
  6 | import ExpansionPanelDetails from '@material-ui/core/ExpansionPanelDetails';
  7 | import ExpansionPanelActions from '@material-ui/core/ExpansionPanelActions';
  8 | import Typography from '@material-ui/core/Typography';
  9 | import Input from '@material-ui/core/Input';
 10 | import MenuItem from '@material-ui/core/MenuItem';
 11 | import RemoveCircleOutlineIcon from '@material-ui/icons/RemoveCircleOutline';
 12 | import ListItemText from '@material-ui/core/ListItemText';
 13 | import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
 14 | import Switch from '@material-ui/core/Switch';
 15 | import Chip from '@material-ui/core/Chip';
 16 | import IconButton from '@material-ui/core/IconButton';
 17 | import DeleteIcon from '@material-ui/icons/Delete';
 18 | import FlipToBackIcon from '@material-ui/icons/FlipToBack';
 19 | import FlipToFrontIcon from '@material-ui/icons/FlipToFront';
 20 | import Select from '@material-ui/core/Select';
 21 | import Tooltip from '@material-ui/core/Tooltip';
 22 | import InputLabel from '@material-ui/core/InputLabel';
 23 | import Divider from '@material-ui/core/Divider';
 24 | 
 25 | const styles = theme => ({
 26 |   root: {
 27 |     width: '100%',
 28 |     marginTop: 10,
 29 |     // backgroundColor: '#333333',
 30 |   },
 31 |   heading: {
 32 |     fontSize: theme.typography.pxToRem(15),
 33 |     fontWeight: theme.typography.fontWeightRegular,
 34 |   },
 35 |   chips: {
 36 |     display: 'flex',
 37 |     flexWrap: 'wrap',
 38 |   },
 39 |   chip: {
 40 |     margin: theme.spacing.unit / 4,
 41 |   },
 42 |   panel: {
 43 |     backgroundColor: '#333333',
 44 |   },
 45 |   details: {
 46 |     display: 'flex',
 47 |     flexDirection: 'column',
 48 |   },
 49 |   actions: {
 50 |     padding: 0,
 51 |   },
 52 |   column: {
 53 |     display: 'flex',
 54 |     alignItems: 'center',
 55 |   },
 56 |   light: {
 57 |     color: '#eee',
 58 |     // opacity: '0.8',
 59 | 
 60 |     '&:hover': {
 61 |       color: '#1de9b6',
 62 |     },
 63 |   },
 64 |   label: {
 65 |     color: '#eee',
 66 |     marginRight: '10px',
 67 |   },
 68 |   formControl: {
 69 |     margin: theme.spacing.unit * 3,
 70 |   },
 71 |   group: {
 72 |     margin: `${theme.spacing.unit}px 0`,
 73 |   },
 74 |   icon: {
 75 |     fontSize: '20px',
 76 |     color: '#000',
 77 |     transition: 'all .2s ease',
 78 | 
 79 |     '&:hover': {
 80 |       color: 'red',
 81 |     },
 82 |   },
 83 | });
 84 | 
 85 | const LeftColExpansionPanel = (props) => {
 86 |   const {
 87 |     index,
 88 |     classes,
 89 |     focusComponent,
 90 |     component,
 91 |     updateComponent,
 92 |     deleteComponent,
 93 |     onExpansionPanelChange,
 94 |     moveToBottom,
 95 |     moveToTop,
 96 |   } = props;
 97 |   const {
 98 |     title,
 99 |     id,
100 |     stateful,
101 |     color,
102 |     parents,
103 |     parentIds,
104 |     selectableParents,
105 |   } = component;
106 | 
107 |   const handleParentChange = (event, parentId = null) => {
108 |     let newParentId = parentId;
109 |     if (event) {
110 |       const selectedParents = event.target.value;
111 |       newParentId = selectedParents[selectedParents.length - 1].id;
112 |     }
113 | 
114 |     return updateComponent({
115 |       index,
116 |       id,
117 |       newParentId,
118 |     });
119 |   };
120 | 
121 |   return (
122 |     <div className={classes.root}>
123 |       <ExpansionPanel
124 |         className={classes.panel}
125 |         expanded={focusComponent.id === id}
126 |         onChange={() => onExpansionPanelChange(component)}
127 |         elevation={4}
128 |       >
129 |         <ExpansionPanelSummary expandIcon={<ExpandMoreIcon style={{ color }} />}>
130 |           <Typography className={classes.light}>{title}</Typography>
131 |         </ExpansionPanelSummary>
132 |         <ExpansionPanelDetails className={classes.details}>
133 |           <div className={classes.column}>
134 |             <InputLabel className={classes.label} htmlFor='stateful'>Stateful?</InputLabel>
135 |             <Switch
136 |               checked={stateful}
137 |               onChange={event => updateComponent({ stateful: event.target.checked, index, id })}
138 |               value='stateful'
139 |               color='primary'
140 |               id='stateful'
141 |             />
142 |           </div>
143 |           <div className={classes.column}>
144 |             <InputLabel className={classes.label} htmlFor='boxColor'>Box Color</InputLabel>
145 |             <Input
146 |               type='color'
147 |               id='boxColor'
148 |               disableUnderline={true}
149 |               value={color}
150 |               onChange={event => updateComponent({ color: event.target.value, index, id })}
151 |             />
152 |           </div>
153 |           <div className={classes.column}>
154 |             <InputLabel className={classes.label} htmlFor='parentSelect'>selectedParents</InputLabel>
155 |             <Select
156 |               className={classes.light}
157 |               multiple
158 |               value={parents}
159 |               id='parentSelect'
160 |               name='parentName'
161 |               disabled={selectableParents.length < 1}
162 |               onChange={handleParentChange}
163 |               input={<Input id='parentSelect' />}
164 |               renderValue={selectedP => (
165 |                 <div className={classes.chips}>
166 |                   {selectedP.map(parent => (
167 |                     <Chip
168 |                       key={parent.id}
169 |                       label={parent.title}
170 |                       className={classes.chip}
171 |                       onDelete={() => handleParentChange(null, parent.id)}
172 |                       deleteIcon={<RemoveCircleOutlineIcon className={classes.icon} />}
173 |                       />
174 |                   ))}
175 |                 </div>
176 |               )}
177 |             >
178 |             {selectableParents.map(parentObj => (
179 |               <MenuItem key={parentObj.id} value={parentObj}>
180 |                 <ListItemText primary={parentObj.title} />
181 |               </MenuItem>
182 |             ))}
183 |             </Select>
184 |           </div>
185 |         </ExpansionPanelDetails>
186 |         <Divider />
187 |         <ExpansionPanelActions className={classes.actions}>
188 |           <Tooltip title="move layer up">
189 |             <IconButton
190 |               className={classes.button}
191 |               onClick={() => moveToTop(id)}
192 |               aria-label='Flip to back'>
193 |               <FlipToFrontIcon className={classes.light} />
194 |             </IconButton>
195 |           </Tooltip>
196 |           <Tooltip title="move layer down">
197 |             <IconButton
198 |               className={classes.button}
199 |               onClick={() => moveToBottom(id)}
200 |               aria-label='Flip to back'>
201 |               <FlipToBackIcon className={classes.light} />
202 |             </IconButton>
203 |           </Tooltip>
204 |           <IconButton
205 |             className={classes.button}
206 |             onClick={() => {
207 |               deleteComponent({
208 |                 index, id, parentIds,
209 |               });
210 |             }}
211 |             aria-label='Delete'>
212 |             <DeleteIcon className={classes.light} />
213 |           </IconButton>
214 |         </ExpansionPanelActions>
215 |       </ExpansionPanel>
216 |     </div >
217 |   );
218 | };
219 | 
220 | export default withStyles(styles)(LeftColExpansionPanel);
221 | 
222 | LeftColExpansionPanel.propTypes = {
223 |   classes: PropTypes.object.isRequired,
224 |   component: PropTypes.object,
225 |   index: PropTypes.number,
226 |   focusComponent: PropTypes.object.isRequired,
227 |   onExpansionPanelChange: PropTypes.func,
228 |   updateComponent: PropTypes.func,
229 |   deleteComponent: PropTypes.func,
230 |   moveToBottom: PropTypes.func,
231 |   moveToTop: PropTypes.func,
232 | };
233 | 


--------------------------------------------------------------------------------
/src/components/MainContainerHeader.jsx:
--------------------------------------------------------------------------------
  1 | import React from 'react';
  2 | import PropTypes from 'prop-types';
  3 | import Button from '@material-ui/core/Button';
  4 | import ZoomInIcon from '@material-ui/icons/ZoomIn';
  5 | import ZoomOutIcon from '@material-ui/icons/ZoomOut';
  6 | import ImageSearchIcon from '@material-ui/icons/ImageSearch';
  7 | import OpenWithIcon from '@material-ui/icons/OpenWith';
  8 | import KeyboardArrowLeftIcon from '@material-ui/icons/KeyboardArrowLeft';
  9 | import KeyboardArrowRightIcon from '@material-ui/icons/KeyboardArrowRight';
 10 | import DeleteOutlineIcon from '@material-ui/icons/DeleteOutline';
 11 | import GetAppIcon from '@material-ui/icons/GetApp';
 12 | import { withStyles } from '@material-ui/core/styles';
 13 | import Tooltip from '@material-ui/core/Tooltip';
 14 | 
 15 | 
 16 | const styles = () => ({
 17 |   iconSmall: {
 18 |     fontSize: 10,
 19 |   },
 20 |   button: {
 21 |     // borderRight: '1px solid grey',
 22 |     borderRadius: '0px',
 23 |     backgroundColor: '#212121',
 24 | 
 25 |     '&:hover > span > svg': {
 26 |       color: '#1de9b6',
 27 |       transition: 'all .2s ease',
 28 |     },
 29 |     '&:hover': {
 30 |       backgroundColor: '#212121',
 31 |     },
 32 |     '&:disabled': {
 33 |       backgroundColor: '#424242',
 34 |     },
 35 | 
 36 |     '&:disabled > span > svg': {
 37 |       color: '#eee',
 38 |       opacity: '0.3',
 39 |     },
 40 |   },
 41 |   buttonDrag: {
 42 |     // borderRight: '1px solid grey',
 43 |     borderRadius: '0px',
 44 |     backgroundColor: '#212121',
 45 |     '&:hover > span > svg': {
 46 |       color: '#1de9b6',
 47 |     },
 48 | 
 49 |     '&:hover': {
 50 |       backgroundColor: '#212121',
 51 |     },
 52 | 
 53 |     '&:disabled': {
 54 |       backgroundColor: '#424242',
 55 |     },
 56 | 
 57 |     '&:disabled > span > svg': {
 58 |       color: '#eee',
 59 |       opacity: '0.3',
 60 |     },
 61 |   },
 62 |   buttonDragDark: {
 63 |     backgroundColor: '#1de9b6',
 64 |   },
 65 |   light: {
 66 |     color: '#eee',
 67 |     // opacity: '0.7',
 68 |   },
 69 |   dark: {
 70 |     color: '#1de9b6',
 71 |   },
 72 | });
 73 | 
 74 | const MainContainerHeader = (props) => {
 75 |   const {
 76 |     increaseHeight,
 77 |     decreaseHeight,
 78 |     classes,
 79 |     image,
 80 |     showImageDeleteModal,
 81 |     updateImage,
 82 |     toggleDrag,
 83 |     totalComponents,
 84 |     showGenerateAppModal,
 85 |     collapseColumn,
 86 |     rightColumnOpen,
 87 |     toggleClass,
 88 |   } = props;
 89 | 
 90 |   return (
 91 |     <div className="main-header">
 92 |       <div className="main-header-buttons" style={{ marginRight: 'auto' }}>
 93 |         <Tooltip title="zoom in">
 94 |           <div>
 95 |             <Button disabled={!image} color="default" className={classes.button} onClick={increaseHeight}>
 96 |               <ZoomInIcon className={classes.light} />
 97 |             </Button>
 98 |           </div>
 99 |         </Tooltip>
100 |         <Tooltip title="zoom out">
101 |           <div>
102 |             <Button disabled={!image} color="default" className={classes.button} onClick={decreaseHeight}>
103 |               <ZoomOutIcon className={classes.light} />
104 |             </Button>
105 |           </div>
106 |         </Tooltip>
107 |         <Tooltip title="toggle drag">
108 |           <div>
109 |             <Button disabled={!image} color="default" className={toggleDrag ? classes.buttonDrag : classes.buttonDragDark} onClick={toggleDrag}>
110 |               <OpenWithIcon className={toggleClass ? classes.light : classes.dark} />
111 |             </Button>
112 |           </div>
113 |         </Tooltip>
114 |       </div>
115 |       <div className="main-header-buttons" >
116 |         <Tooltip title="remove image">
117 |           <div>
118 |             <Button disabled={!image} color="default" className={classes.button} onClick={showImageDeleteModal}>
119 |               <DeleteOutlineIcon className={classes.light} />
120 |             </Button>
121 |           </div>
122 |         </Tooltip>
123 |         <Tooltip title={image ? 'update image' : 'upload image'}>
124 |           <div>
125 |             <Button color="default" className={classes.button} onClick={updateImage}>
126 |               <ImageSearchIcon className={classes.light} />
127 |             </Button>
128 |           </div>
129 |         </Tooltip>
130 |         <Tooltip title={'export'}>
131 |           <div>
132 |             <Button color='default' className={classes.button} disabled={totalComponents < 1} onClick={showGenerateAppModal}>
133 |               <GetAppIcon className={classes.light} />
134 |             </Button>
135 |           </div>
136 |         </Tooltip>
137 |         <div>
138 |           <Button color="default" className={classes.button} onClick={() => collapseColumn()}>
139 |             {rightColumnOpen ? <KeyboardArrowRightIcon
140 |               className={classes.light} /> : <KeyboardArrowLeftIcon className={classes.light}
141 |               />}
142 |           </Button>
143 |         </div>
144 |       </div>
145 |     </div>
146 |   );
147 | };
148 | 
149 | // style={{ borderLeft: '1px solid grey' }}
150 | 
151 | MainContainerHeader.propTypes = {
152 |   image: PropTypes.oneOfType([
153 |     PropTypes.string,
154 |     PropTypes.object,
155 |   ]),
156 |   classes: PropTypes.object.isRequired,
157 |   increaseHeight: PropTypes.func.isRequired,
158 |   decreaseHeight: PropTypes.func.isRequired,
159 |   showImageDeleteModal: PropTypes.func.isRequired,
160 |   updateImage: PropTypes.func.isRequired,
161 |   toggleDrag: PropTypes.func.isRequired,
162 |   showGenerateAppModal: PropTypes.func.isRequired,
163 |   totalComponents: PropTypes.number.isRequired,
164 |   collapseColumn: PropTypes.func.isRequired,
165 |   rightColumnOpen: PropTypes.bool.isRequired,
166 |   toggleClass: PropTypes.bool.isRequired,
167 | };
168 | 
169 | export default withStyles(styles)(MainContainerHeader);
170 | 


--------------------------------------------------------------------------------
/src/components/Props.jsx:
--------------------------------------------------------------------------------
  1 | import React, { Component } from 'react';
  2 | import PropTypes from 'prop-types';
  3 | import { withStyles } from '@material-ui/core/styles';
  4 | import Chip from '@material-ui/core/Chip';
  5 | import Avatar from '@material-ui/core/Avatar';
  6 | import FormControl from '@material-ui/core/FormControl';
  7 | import Grid from '@material-ui/core/Grid';
  8 | import TextField from '@material-ui/core/TextField';
  9 | import Button from '@material-ui/core/Button';
 10 | import Select from '@material-ui/core/Select';
 11 | import Switch from '@material-ui/core/Switch';
 12 | import InputLabel from '@material-ui/core/InputLabel';
 13 | import RemoveCircleOutlineIcon from '@material-ui/icons/RemoveCircleOutline';
 14 | 
 15 | const styles = theme => ({
 16 |   root: {
 17 |     display: 'flex',
 18 |     justifyContent: 'center',
 19 |     flexWrap: 'wrap',
 20 |   },
 21 |   chip: {
 22 |     margin: theme.spacing.unit,
 23 |     color: '#eee',
 24 |     backgroundColor: '#333333',
 25 |   },
 26 |   column: {
 27 |     display: 'inline-flex',
 28 |     alignItems: 'baseline',
 29 |   },
 30 |   icon: {
 31 |     fontSize: '20px',
 32 |     color: '#eee',
 33 |     opacity: '0.7',
 34 |     transition: 'all .2s ease',
 35 | 
 36 |     '&:hover': {
 37 |       color: 'red',
 38 |     },
 39 |   },
 40 |   cssLabel: {
 41 |     color: 'white',
 42 | 
 43 |     '&$cssFocused': {
 44 |       color: 'green',
 45 |     },
 46 |   },
 47 |   cssFocused: {},
 48 |   input: {
 49 |     color: '#eee',
 50 |     marginBottom: '10px',
 51 |     width: '60%',
 52 |   },
 53 |   light: {
 54 |     color: '#eee',
 55 |   },
 56 |   avatar: {
 57 |     color: '#eee',
 58 |     fontSize: '10px',
 59 |   },
 60 | });
 61 | 
 62 | const availablePropTypes = {
 63 |   string: 'STR',
 64 |   object: 'OBJ',
 65 |   array: 'ARR',
 66 |   number: 'NUM',
 67 |   bool: 'BOOL',
 68 |   func: 'FUNC',
 69 |   symbol: 'SYM',
 70 |   node: 'NODE',
 71 |   element: 'ELEM',
 72 | };
 73 | 
 74 | const typeOptions = [
 75 |   <option
 76 |     value=''
 77 |     key=''
 78 |   >
 79 |   </option>,
 80 |   ...Object.keys(availablePropTypes).map(type => (
 81 |     <option
 82 |       value={type}
 83 |       key={type}
 84 |       style={{ color: '#000'}}
 85 |     >
 86 |       {type}
 87 |     </option>
 88 |     ),
 89 |   ),
 90 | ];
 91 | 
 92 | class Props extends Component {
 93 |   state = {
 94 |     propKey: '',
 95 |     propValue: '',
 96 |     propRequired: false,
 97 |     propType: '',
 98 |   }
 99 | 
100 |   handleChange = (event) => {
101 |     this.setState({
102 |       [event.target.id]: event.target.value.trim(),
103 |     });
104 |   }
105 | 
106 |   togglePropRequired = () => {
107 |     this.setState({
108 |       propRequired: !this.state.propRequired,
109 |     });
110 |   }
111 | 
112 |   handleAddProp = (event) => {
113 |     event.preventDefault();
114 |     const {
115 |       propKey,
116 |       propValue,
117 |       propRequired,
118 |       propType,
119 |     } = this.state;
120 |     this.props.addProp({
121 |       key: propKey,
122 |       value: propValue,
123 |       required: propRequired,
124 |       type: propType,
125 |     });
126 |     this.setState({
127 |       propKey: '',
128 |       propValue: '',
129 |       propRequired: false,
130 |       propType: '',
131 |     });
132 |   }
133 | 
134 |   render() {
135 |     const {
136 |       focusComponent,
137 |       classes,
138 |       deleteProp,
139 |       rightColumnOpen,
140 |     } = this.props;
141 | 
142 |     return <div style={{ display: rightColumnOpen ? 'inline' : 'none' }}> {
143 |       Object.keys(focusComponent).length < 1
144 |         ? <div style={{ marginTop: '20px', marginLeft: '20px' }}>Click a component to view its props.</div>
145 |         : <div className='props-container'>
146 |           <form className='props-input' onSubmit={this.handleAddProp}>
147 |             <Grid container spacing={24}>
148 |               <Grid item xs={6}>
149 |                 <TextField
150 |                   id='propKey'
151 |                   label='Key'
152 |                   margin='normal'
153 |                   autoFocus
154 |                   onChange={this.handleChange}
155 |                   value={this.state.propKey}
156 |                   required
157 |                   InputProps={{
158 |                     className: classes.input,
159 |                   }}
160 |                   InputLabelProps={{
161 |                     className: classes.input,
162 |                   }}
163 |                 />
164 |               </Grid>
165 |               <Grid item xs={6}>
166 |                 <TextField
167 |                   id='propValue'
168 |                   label='Value'
169 |                   margin='normal'
170 |                   onChange={this.handleChange}
171 |                   InputProps={{
172 |                     className: classes.input,
173 |                   }}
174 |                   InputLabelProps={{
175 |                     className: classes.input,
176 |                   }}
177 |                   value={this.state.propValue}
178 |                 />
179 |               </Grid>
180 |               <Grid item xs={6}>
181 |                 <FormControl required>
182 |                   <InputLabel className={classes.light} htmlFor='propType'>Type</InputLabel>
183 |                   <Select
184 |                     native
185 |                     className={classes.light}
186 |                     id='propType'
187 |                     placeholder='title'
188 |                     onChange={this.handleChange}
189 |                     value={this.state.propType}
190 |                     required
191 |                   >
192 |                     {typeOptions}
193 |                   </Select>
194 |                 </FormControl>
195 |               </Grid>
196 |               <Grid item xs={6}>
197 |                 <div className={classes.column}>
198 |                   <InputLabel className={classes.light} htmlFor='propRequired'>Required?</InputLabel>
199 |                   <Switch
200 |                     checked={this.state.propRequired}
201 |                     onChange={this.togglePropRequired}
202 |                     value='propRequired'
203 |                     color='secondary'
204 |                     id='propRequired'
205 |                   />
206 |                 </div>
207 |               </Grid>
208 |               <Grid item>
209 |                 <Button
210 |                   color='primary'
211 |                   aria-label='Add'
212 |                   type='submit'
213 |                   disabled={!this.state.propKey || !this.state.propType}
214 |                   variant='contained'
215 |                   size="large"
216 |                 >
217 |                   ADD PROP
218 |                 </Button>
219 |               </Grid>
220 |             </Grid>
221 |           </form>
222 |           <div className='chips'>
223 |             {
224 |               focusComponent.props.map(({
225 |                 id, type, key, value, required,
226 |               }, index) => (
227 |                   <Chip
228 |                     key={id}
229 |                     avatar={<Avatar className={classes.avatar} >{availablePropTypes[type]}</Avatar>}
230 |                     label={`${key}: ${value}`}
231 |                     onDelete={() => deleteProp({ id, index })}
232 |                     className={classes.chip}
233 |                     elevation={6}
234 |                     color={required ? 'secondary' : 'primary'}
235 |                     deleteIcon={<RemoveCircleOutlineIcon className={classes.icon} />}
236 |                   />
237 |               ))
238 |             }
239 |           </div>
240 |         </div>
241 |     }
242 |     </div>;
243 |   }
244 | }
245 | 
246 | Props.propTypes = {
247 |   classes: PropTypes.object.isRequired,
248 |   focusComponent: PropTypes.object.isRequired,
249 |   deleteProp: PropTypes.func.isRequired,
250 |   addProp: PropTypes.func.isRequired,
251 |   rightColumnOpen: PropTypes.bool.isRequired,
252 | };
253 | 
254 | export default withStyles(styles)(Props);
255 | 


--------------------------------------------------------------------------------
/src/components/Rectangle.jsx:
--------------------------------------------------------------------------------
 1 | import React, { Component } from 'react';
 2 | import { Rect } from 'react-konva';
 3 | import PropTypes from 'prop-types';
 4 | 
 5 | class Rectangle extends Component {
 6 |   extractPositionInfo(componentId, target) {
 7 |     const transformation = {
 8 |       x: target.x(),
 9 |       y: target.y(),
10 |       width: target.width() * target.scaleX(),
11 |       height: target.height() * target.scaleY(),
12 |     };
13 | 
14 |     this.props.handleTransform(componentId, transformation);
15 |   }
16 | 
17 |   render() {
18 |     const {
19 |       color, x, y, componentId, draggable, width, height,
20 |     } = this.props;
21 | 
22 |     return (
23 |       <Rect
24 |         name={componentId}
25 |         x={x}
26 |         y={y}
27 |         componentid={componentId}
28 |         scaleX={1}
29 |         scaleY={1}
30 |         width={width}
31 |         height={height}
32 |         stroke={color}
33 |         strokeWidth={6}
34 |         strokeScaleEnabled={false}
35 |         onTransformEnd={event => this.extractPositionInfo(componentId, event.target)}
36 |         onDragEnd={event => this.extractPositionInfo(componentId, event.target)}
37 |         draggable={draggable}
38 |       />
39 |     );
40 |   }
41 | }
42 | 
43 | Rectangle.propTypes = {
44 |   // title: PropTypes.string.isRequired,
45 |   color: PropTypes.string.isRequired,
46 |   handleTransform: PropTypes.func.isRequired,
47 |   x: PropTypes.number,
48 |   y: PropTypes.number,
49 |   height: PropTypes.number,
50 |   width: PropTypes.number,
51 |   componentId: PropTypes.string.isRequired,
52 |   draggable: PropTypes.bool.isRequired,
53 | };
54 | 
55 | export default Rectangle;
56 | 


--------------------------------------------------------------------------------
/src/components/RightTabs.jsx:
--------------------------------------------------------------------------------
  1 | import React, { Component } from 'react';
  2 | import PropTypes from 'prop-types';
  3 | import { withStyles } from '@material-ui/core/styles';
  4 | import Tabs from '@material-ui/core/Tabs';
  5 | import Tab from '@material-ui/core/Tab';
  6 | import Badge from '@material-ui/core/Badge';
  7 | import Props from './Props.jsx';
  8 | import SortableComponent from './SortableComponent.jsx';
  9 | 
 10 | const styles = theme => ({
 11 |   root: {
 12 |     flexGrow: 1,
 13 |     backgroundColor: '#212121',
 14 |     height: '100%',
 15 |     color: '#fff',
 16 |     boxShadow: '0 3px 6px rgba(0,0,0,0.16), 0 3px 6px rgba(0,0,0,0.23)',
 17 |   },
 18 |   tabsRoot: {
 19 |     // borderBottom: '1px solid #e8e8e8',
 20 |   },
 21 |   tabsIndicator: {
 22 |     backgroundColor: '#1de9b6',
 23 |   },
 24 |   tabRoot: {
 25 |     textTransform: 'initial',
 26 |     minWidth: 72,
 27 |     fontWeight: theme.typography.fontWeightRegular,
 28 |     marginRight: theme.spacing.unit * 4,
 29 | 
 30 |     fontFamily: [
 31 |       '-apple-system',
 32 |       'BlinkMacSystemFont',
 33 |       '"Segoe UI"',
 34 |       'Roboto',
 35 |       '"Helvetica Neue"',
 36 |       'Arial',
 37 |       'sans-serif',
 38 |       '"Apple Color Emoji"',
 39 |       '"Segoe UI Emoji"',
 40 |       '"Segoe UI Symbol"',
 41 |     ].join(','),
 42 |     '&:hover': {
 43 |       color: '#1de9b6',
 44 |       opacity: 1,
 45 |     },
 46 |     '&$tabSelected': {
 47 |       color: '#33eb91',
 48 |       fontWeight: theme.typography.fontWeightMedium,
 49 |     },
 50 |     '&:focus': {
 51 |       color: '#4aedc4',
 52 |     },
 53 |   },
 54 |   tabSelected: {},
 55 |   typography: {
 56 |     padding: theme.spacing.unit * 3,
 57 |   },
 58 |   padding: {
 59 |     padding: `0 ${theme.spacing.unit * 2}px`,
 60 |   },
 61 | });
 62 | 
 63 | class RightTabs extends Component {
 64 |   state = {
 65 |     value: 0,
 66 |   };
 67 | 
 68 |   handleChange = (event, value) => {
 69 |     this.setState({ value });
 70 |   };
 71 | 
 72 |   render() {
 73 |     const {
 74 |       classes,
 75 |       components,
 76 |       focusComponent,
 77 |       deleteProp,
 78 |       addProp,
 79 |       rightColumnOpen,
 80 |     } = this.props;
 81 |     const { value } = this.state;
 82 | 
 83 |     return (
 84 |       <div className={classes.root}>
 85 |         <Tabs
 86 |           value={value}
 87 |           onChange={this.handleChange}
 88 |           classes={{ root: classes.tabsRoot, indicator: classes.tabsIndicator }}
 89 |         >
 90 |           <Tab
 91 |             disableRipple
 92 |             classes={{ root: classes.tabRoot, selected: classes.tabSelected }}
 93 |             label="Hierarchy"
 94 |           />
 95 |           <Tab
 96 |             disableRipple
 97 |             classes={{ root: classes.tabRoot, selected: classes.tabSelected }}
 98 |             label={
 99 |               focusComponent.props
100 |                 ? <Badge
101 |                   className={classes.padding}
102 |                   color='primary'
103 |                   badgeContent={focusComponent.props.length}
104 |                 >
105 |                   Props
106 |                 </Badge> : 'Props'
107 |             }
108 |           />
109 |         </Tabs>
110 |         {value === 0 && <SortableComponent components={components} />}
111 |         {value === 1 && <Props
112 |           rightColumnOpen={rightColumnOpen}
113 |           focusComponent={focusComponent}
114 |           deleteProp={deleteProp}
115 |           addProp={addProp}
116 |         />
117 |         }
118 |       </div>
119 |     );
120 |   }
121 | }
122 | 
123 | RightTabs.propTypes = {
124 |   classes: PropTypes.object.isRequired,
125 |   components: PropTypes.array.isRequired,
126 |   focusComponent: PropTypes.object.isRequired,
127 |   deleteProp: PropTypes.func.isRequired,
128 |   addProp: PropTypes.func.isRequired,
129 |   rightColumnOpen: PropTypes.bool.isRequired,
130 | };
131 | 
132 | export default withStyles(styles)(RightTabs);
133 | 


--------------------------------------------------------------------------------
/src/components/SimpleModal.jsx:
--------------------------------------------------------------------------------
  1 | import React, { Fragment } from 'react';
  2 | import PropTypes from 'prop-types';
  3 | import Modal from '@material-ui/core/Modal';
  4 | import { withStyles } from '@material-ui/core/styles';
  5 | import Typography from '@material-ui/core/Typography';
  6 | import Button from '@material-ui/core/Button';
  7 | import Icon from '@material-ui/core/Icon';
  8 | 
  9 | const styles = theme => ({
 10 |   paper: {
 11 |     position: 'absolute',
 12 |     width: 'auto',
 13 |     maxWidth: '500px',
 14 |     backgroundColor: theme.palette.background.paper,
 15 |     boxShadow: theme.shadows[5],
 16 |     padding: '4%',
 17 |     minWidth: '500px',
 18 |   },
 19 |   button: {
 20 |     marginTop: '8%',
 21 |     height: 'auto',
 22 |     marginLeft: '3%',
 23 |     borderRadius: '4px',
 24 |     float: 'right',
 25 |   },
 26 | });
 27 | 
 28 | const SimpleModal = (props) => {
 29 |   const {
 30 |     classes,
 31 |     open,
 32 |     message,
 33 |     primBtnLabel,
 34 |     secBtnLabel,
 35 |     primBtnAction,
 36 |     secBtnAction,
 37 |     closeModal,
 38 |     children = null,
 39 |   } = props;
 40 | 
 41 |   return (
 42 |     <Fragment>
 43 |       <Modal
 44 |         aria-labelledby="simple-modal-title"
 45 |         aria-describedby="simple-modal-description"
 46 |         onClose={closeModal}
 47 |         open={open}
 48 |       >
 49 |         <div style={{
 50 |           top: '50%',
 51 |           left: '50%',
 52 |           transform: 'translate(-50%, -50%)',
 53 |         }} className={classes.paper}>
 54 |           <Icon
 55 |             onClick={closeModal}
 56 |             className='closeIcon'
 57 |             style={{
 58 |               position: 'absolute',
 59 |               top: '2%',
 60 |               right: '1%',
 61 |               fontSize: '17px',
 62 |               fontWeight: 'bold',
 63 |             }}
 64 |           >close</Icon>
 65 |           <Typography variant="title" id="modal-title">
 66 |             {message}
 67 |           </Typography>
 68 |           <div>
 69 |             {children}
 70 |           </div>
 71 |           <div>
 72 |             {
 73 |               secBtnLabel ? <Button variant='extendedFab' color="secondary" className={classes.button} onClick={secBtnAction}>
 74 |                 {secBtnLabel}
 75 |               </Button> : null
 76 |             }
 77 |             {
 78 |               primBtnLabel ? <Button variant='extendedFab' color="primary" className={classes.button} onClick={primBtnAction}>
 79 |                 {primBtnLabel}
 80 |               </Button> : null
 81 |             }
 82 |           </div>
 83 |         </div>
 84 |       </Modal>
 85 |     </Fragment>
 86 |   );
 87 | };
 88 | 
 89 | SimpleModal.propTypes = {
 90 |   open: PropTypes.bool.isRequired,
 91 |   classes: PropTypes.object.isRequired,
 92 |   secBtnAction: PropTypes.func,
 93 |   closeModal: PropTypes.func.isRequired,
 94 |   primBtnAction: PropTypes.func,
 95 |   children: PropTypes.object,
 96 |   message: PropTypes.string,
 97 |   primBtnLabel: PropTypes.string,
 98 |   secBtnLabel: PropTypes.string,
 99 | };
100 | 
101 | export default withStyles(styles)(SimpleModal);
102 | 


--------------------------------------------------------------------------------
/src/components/SnackbarContentWrapper.jsx:
--------------------------------------------------------------------------------
 1 | import React from 'react';
 2 | import PropTypes from 'prop-types';
 3 | import CheckCircleIcon from '@material-ui/icons/CheckCircle';
 4 | import ErrorIcon from '@material-ui/icons/Error';
 5 | import InfoIcon from '@material-ui/icons/Info';
 6 | import CloseIcon from '@material-ui/icons/Close';
 7 | import green from '@material-ui/core/colors/green';
 8 | import amber from '@material-ui/core/colors/amber';
 9 | import IconButton from '@material-ui/core/IconButton';
10 | import SnackbarContent from '@material-ui/core/SnackbarContent';
11 | import WarningIcon from '@material-ui/icons/Warning';
12 | import classNames from 'classnames';
13 | import { withStyles } from '@material-ui/core/styles';
14 | 
15 | const variantIcon = {
16 |   success: CheckCircleIcon,
17 |   warning: WarningIcon,
18 |   error: ErrorIcon,
19 |   info: InfoIcon,
20 | };
21 | 
22 | const styles1 = theme => ({
23 |   success: {
24 |     backgroundColor: green[600],
25 |   },
26 |   error: {
27 |     backgroundColor: theme.palette.error.dark,
28 |   },
29 |   info: {
30 |     backgroundColor: theme.palette.primary.dark,
31 |   },
32 |   warning: {
33 |     backgroundColor: amber[700],
34 |   },
35 |   icon: {
36 |     fontSize: 20,
37 |   },
38 |   iconVariant: {
39 |     opacity: 0.9,
40 |     marginRight: theme.spacing.unit,
41 |   },
42 |   message: {
43 |     display: 'flex',
44 |     alignItems: 'center',
45 |   },
46 | });
47 | 
48 | const SnackbarContentWrapper = (props) => {
49 |   const {
50 |     classes, className, message, onClose, variant, ...other
51 |   } = props;
52 |   const Icon = variantIcon[variant];
53 | 
54 |   return (
55 |     <SnackbarContent
56 |       className={classNames(classes[variant], className)}
57 |       aria-describedby="client-snackbar"
58 |       message={
59 |         <span id="client-snackbar" className={classes.message}>
60 |           <Icon className={classNames(classes.icon, classes.iconVariant)} />
61 |           {message}
62 |         </span>
63 |       }
64 |       action={[
65 |         <IconButton
66 |           key="close"
67 |           aria-label="Close"
68 |           color="inherit"
69 |           className={classes.close}
70 |           onClick={onClose}
71 |         >
72 |           <CloseIcon className={classes.icon} />
73 |         </IconButton>,
74 |       ]}
75 |       {...other}
76 |     />
77 |   );
78 | };
79 | 
80 | SnackbarContentWrapper.propTypes = {
81 |   classes: PropTypes.object.isRequired,
82 |   className: PropTypes.string,
83 |   message: PropTypes.node,
84 |   onClose: PropTypes.func,
85 |   variant: PropTypes.oneOf(['success', 'warning', 'error', 'info']).isRequired,
86 | };
87 | 
88 | export default withStyles(styles1)(SnackbarContentWrapper);
89 | 


--------------------------------------------------------------------------------
/src/components/Snackbars.jsx:
--------------------------------------------------------------------------------
 1 | import React from 'react';
 2 | import PropTypes from 'prop-types';
 3 | import Button from '@material-ui/core/Button';
 4 | import Snackbar from '@material-ui/core/Snackbar';
 5 | import SnackbarContentWrapper from './SnackbarContentWrapper.jsx';
 6 | 
 7 | const Snackbars = (props) => {
 8 |   const {
 9 |     successOpen, errorOpen, handleNotificationClose, msg, viewAppDir,
10 |   } = props;
11 |   const successMsg = <div>Your files were successfully created. <Button
12 |       variant='fab'
13 |       mini
14 |       aria-label='View app directory'
15 |       onClick={viewAppDir}
16 |     >View</Button></div>;
17 |   const errMsg = `There was an error while creating your files. ${msg}`;
18 |   return (
19 |     <div>
20 |       <Snackbar
21 |         anchorOrigin={{
22 |           vertical: 'bottom',
23 |           horizontal: 'left',
24 |         }}
25 |         open={successOpen}
26 |         autoHideDuration={5000}
27 |       >
28 |         <SnackbarContentWrapper
29 |           onClose={handleNotificationClose}
30 |           variant="success"
31 |           message={successMsg}
32 |         />
33 |       </Snackbar>
34 |       <Snackbar
35 |         anchorOrigin={{
36 |           vertical: 'bottom',
37 |           horizontal: 'left',
38 |         }}
39 |         open={errorOpen}
40 |         autoHideDuration={5000}
41 |       >
42 |         <SnackbarContentWrapper
43 |           onClose={handleNotificationClose}
44 |           variant="error"
45 |           message={errMsg}
46 |         />
47 |       </Snackbar>
48 |     </div>
49 |   );
50 | };
51 | 
52 | Snackbars.propTypes = {
53 |   successOpen: PropTypes.bool.isRequired,
54 |   errorOpen: PropTypes.bool.isRequired,
55 |   msg: PropTypes.string.isRequired,
56 |   viewAppDir: PropTypes.func.isRequired,
57 |   handleNotificationClose: PropTypes.func.isRequired,
58 | };
59 | 
60 | export default Snackbars;
61 | 


--------------------------------------------------------------------------------
/src/components/SortableComponent.jsx:
--------------------------------------------------------------------------------
 1 | import React from 'react';
 2 | import SortableTree from 'react-sortable-tree';
 3 | import PropTypes from 'prop-types';
 4 | import 'react-sortable-tree/style.css';
 5 | 
 6 | const SortableComponent = (props) => {
 7 |   const rootComponents = props.components.filter(
 8 |     comp => comp.parentIds.length === 0,
 9 |   ).reverse();
10 | 
11 |   return (
12 |     <div className="sortable-tree">
13 |       <SortableTree
14 |         style={{ backgroundColor: 'rgb(37, 37, 38)' }}
15 |         treeData={rootComponents}
16 |         canDrag={false}
17 |         onChange={() => {}}
18 |       />
19 |     </div>
20 |   );
21 | };
22 | 
23 | export default SortableComponent;
24 | 
25 | SortableComponent.propTypes = {
26 |   components: PropTypes.array.isRequired,
27 | };
28 | 


--------------------------------------------------------------------------------
/src/components/TransformerComponent.jsx:
--------------------------------------------------------------------------------
 1 | import React, { Component } from 'react';
 2 | import { Transformer } from 'react-konva';
 3 | import PropTypes from 'prop-types';
 4 | 
 5 | export default class TransformerComponent extends Component {
 6 |   componentDidMount() {
 7 |     this.checkNode();
 8 |   }
 9 | 
10 |   componentDidUpdate() {
11 |     this.checkNode();
12 |   }
13 | 
14 |   checkNode() {
15 |     const stage = this.transformer.getStage();
16 |     const { focusComponent } = this.props;
17 |     const selectedNode = stage.findOne(`.${focusComponent.id}`);
18 | 
19 |     if (selectedNode === this.transformer.node()) {
20 |       return;
21 |     }
22 |     if (selectedNode) {
23 |       this.transformer.attachTo(selectedNode);
24 |     } else {
25 |       this.transformer.detach();
26 |     }
27 |     this.transformer.getLayer().batchDraw();
28 |   }
29 | 
30 |   render() {
31 |     return (
32 |       <Transformer
33 |         rotateEnabled={false}
34 |         onMouseUp={this.handleMouseUp}
35 |         ref={(node) => {
36 |           this.transformer = node;
37 |         }}
38 |       />
39 |     );
40 |   }
41 | }
42 | 
43 | TransformerComponent.propTypes = {
44 |   focusComponent: PropTypes.object,
45 | };
46 | 


--------------------------------------------------------------------------------
/src/components/__tests__/App.test.js:
--------------------------------------------------------------------------------
 1 | import React from 'react';
 2 | // import '../../setupTests';
 3 | import { shallow } from 'enzyme';
 4 | import App from '../App.jsx';
 5 | import AppContainer from '../../containers/AppContainer.jsx';
 6 | 
 7 | it('contains a AppContainer', () => {
 8 |   // wrapped version of react component
 9 |   // component comes with additional functionality
10 |   const wrapped = shallow(<App />);
11 |   // look inside wrapped component and find every instance of commentBox inside of it
12 |   expect(wrapped.find(AppContainer).length).toEqual(1);
13 | });
14 | 


--------------------------------------------------------------------------------
/src/components/theme.js:
--------------------------------------------------------------------------------
 1 | import { createMuiTheme } from '@material-ui/core/styles';
 2 | // import teal from '@material-ui/core/colors/teal';
 3 | import indigo from '@material-ui/core/colors/indigo';
 4 | 
 5 | const theme = createMuiTheme({
 6 |   palette: {
 7 |     primary: {
 8 |       light: '#00e676',
 9 |       main: '#33eb91',
10 |       dark: '#14a37f',
11 |       contrastText: '#fff',
12 |     },
13 |     secondary: indigo,
14 |   },
15 | });
16 | 
17 | export default theme;
18 | 


--------------------------------------------------------------------------------
/src/config/index.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/React-Proto/react-proto/e5e988c7afd961891e3dd8afc2c5c7bf813ae534/src/config/index.js


--------------------------------------------------------------------------------
/src/containers/AppContainer.jsx:
--------------------------------------------------------------------------------
 1 | import React, { Component } from 'react';
 2 | import { connect } from 'react-redux';
 3 | import PropTypes from 'prop-types';
 4 | import { MuiThemeProvider } from '@material-ui/core/styles';
 5 | import LinearProgress from '@material-ui/core/LinearProgress';
 6 | import LeftContainer from './LeftContainer.jsx';
 7 | import MainContainer from './MainContainer.jsx';
 8 | import RightContainer from './RightContainer.jsx';
 9 | import convertIdsToObjs from '../utils/convertIdsToObjs.util';
10 | import theme from '../components/theme';
11 | import { loadInitData } from '../actions/components';
12 | 
13 | const mapStateToProps = store => ({
14 |   components: store.workspace.components,
15 |   totalComponents: store.workspace.totalComponents,
16 |   focusComponent: store.workspace.focusComponent,
17 |   loading: store.workspace.loading,
18 | });
19 | 
20 | const mapDispatchToProps = { loadInitData };
21 | 
22 | class AppContainer extends Component {
23 |   state = {
24 |     width: 25,
25 |     rightColumnOpen: true,
26 |   }
27 | 
28 |   collapseColumn = () => {
29 |     if (this.state.width === 25) {
30 |       this.setState({
31 |         width: 0,
32 |         rightColumnOpen: false,
33 |       });
34 |     } else {
35 |       this.setState({
36 |         width: 25,
37 |         rightColumnOpen: true,
38 |       });
39 |     }
40 |   }
41 | 
42 |   componentDidMount() {
43 |     this.props.loadInitData();
44 |   }
45 | 
46 |   render() {
47 |     const {
48 |       components,
49 |       totalComponents,
50 |       focusComponent,
51 |       loading,
52 |     } = this.props;
53 |     const { width, rightColumnOpen } = this.state;
54 |     const updatedComponents = convertIdsToObjs(components);
55 | 
56 |     return (
57 |       <MuiThemeProvider theme={theme}>
58 |         <div className='app-container'>
59 |           <LeftContainer
60 |             components={updatedComponents}
61 |             totalComponents={totalComponents}
62 |             focusComponent={focusComponent}
63 |           />
64 |           <MainContainer
65 |             components={updatedComponents}
66 |             collapseColumn={this.collapseColumn}
67 |             width={width}
68 |             rightColumnOpen={rightColumnOpen}
69 |             totalComponents={totalComponents}
70 |           />
71 |           <RightContainer
72 |             width={width}
73 |             components={updatedComponents}
74 |             rightColumnOpen={rightColumnOpen}
75 |             focusComponent={focusComponent}
76 |           />
77 |           {
78 |             loading ? <div style={{ alignSelf: 'flex-end', position: 'fixed', width: '100%' }}>
79 |             <LinearProgress color="secondary" /></div> : null
80 |           }
81 |         </div>
82 |       </MuiThemeProvider>
83 |     );
84 |   }
85 | }
86 | 
87 | export default connect(mapStateToProps, mapDispatchToProps)(AppContainer);
88 | 
89 | AppContainer.propTypes = {
90 |   components: PropTypes.array.isRequired,
91 |   totalComponents: PropTypes.number.isRequired,
92 |   focusComponent: PropTypes.object.isRequired,
93 |   loadInitData: PropTypes.func.isRequired,
94 |   loading: PropTypes.bool,
95 | };
96 | 


--------------------------------------------------------------------------------
/src/containers/LeftContainer.jsx:
--------------------------------------------------------------------------------
  1 | import React, { Component } from 'react';
  2 | import { connect } from 'react-redux';
  3 | import { compose } from 'redux';
  4 | import PropTypes from 'prop-types';
  5 | import FormControl from '@material-ui/core/FormControl';
  6 | import TextField from '@material-ui/core/TextField';
  7 | import Button from '@material-ui/core/Button';
  8 | import AddIcon from '@material-ui/icons/Add';
  9 | import Grid from '@material-ui/core/Grid';
 10 | import { withStyles } from '@material-ui/core/styles';
 11 | import LeftColExpansionPanel from '../components/LeftColExpansionPanel.jsx';
 12 | import createModal from '../utils/createModal.util';
 13 | import * as actions from '../actions/components';
 14 | 
 15 | const mapDispatchToProps = dispatch => ({
 16 |   addComponent: ({ title }) => dispatch(actions.addComponent({ title })),
 17 |   updateComponent:
 18 |     ({
 19 |       id, index, newParentId = null, color = null, stateful = null,
 20 |     }) => dispatch(actions.updateComponent({
 21 |       id, index, newParentId, color, stateful,
 22 |     })),
 23 |   deleteComponent: ({
 24 |     index, id, parentIds,
 25 |   }) => dispatch(actions.deleteComponent({ index, id, parentIds })),
 26 |   moveToBottom: componentId => dispatch(actions.moveToBottom(componentId)),
 27 |   moveToTop: componentId => dispatch(actions.moveToTop(componentId)),
 28 |   openExpansionPanel: component => dispatch(actions.openExpansionPanel(component)),
 29 |   deleteAllData: () => dispatch(actions.deleteAllData()),
 30 | });
 31 | 
 32 | const styles = () => ({
 33 |   cssLabel: {
 34 |     color: 'white',
 35 | 
 36 |     '&$cssFocused': {
 37 |       color: 'green',
 38 |     },
 39 |   },
 40 |   cssFocused: {},
 41 |   input: {
 42 |     color: '#fff',
 43 |     opacity: '0.7',
 44 |     marginBottom: '10px',
 45 |   },
 46 |   underline: {
 47 |     color: 'white',
 48 |     '&::before': {
 49 |       color: 'white',
 50 |     },
 51 |   },
 52 |   button: {
 53 |     color: '#fff',
 54 | 
 55 |     '&:disabled': {
 56 |       color: 'grey',
 57 |     },
 58 |   },
 59 |   clearButton: {
 60 |     top: '96%',
 61 |     position: 'sticky!important',
 62 |     zIndex: '1',
 63 | 
 64 |     '&:disabled': {
 65 |       color: 'grey',
 66 |       backgroundColor: '#424242',
 67 |     },
 68 |   },
 69 | });
 70 | 
 71 | 
 72 | class LeftContainer extends Component {
 73 |   state = {
 74 |     componentName: '',
 75 |     modal: null,
 76 |   }
 77 | 
 78 |   handleChange = (event) => {
 79 |     this.setState({
 80 |       [event.target.name]: event.target.value,
 81 |     });
 82 |   }
 83 | 
 84 |   handleExpansionPanelChange = (component) => {
 85 |     const { focusComponent } = this.props;
 86 |     this.props.openExpansionPanel(focusComponent.id === component.id ? {} : component);
 87 |   }
 88 | 
 89 |   handleAddComponent = () => {
 90 |     this.props.addComponent({ title: this.state.componentName });
 91 |     this.setState({
 92 |       componentName: '',
 93 |     });
 94 |   }
 95 | 
 96 |   closeModal = () => this.setState({ modal: null });
 97 | 
 98 |   clearWorkspace = () => {
 99 |     this.setState({
100 |       modal: createModal({
101 |         message: 'Are you sure want to delete all data?',
102 |         closeModal: this.closeModal,
103 |         secBtnLabel: 'Clear Workspace',
104 |         secBtnAction: () => { this.props.deleteAllData(); this.closeModal(); },
105 |       }),
106 |     });
107 |   }
108 | 
109 |   render() {
110 |     const {
111 |       components,
112 |       updateComponent,
113 |       deleteComponent,
114 |       moveToBottom,
115 |       moveToTop,
116 |       focusComponent,
117 |       totalComponents,
118 |       classes,
119 |     } = this.props;
120 |     const { componentName, modal } = this.state;
121 | 
122 |     const componentsExpansionPanel = components.map(
123 |       (component, i) => <LeftColExpansionPanel
124 |         key={component.id}
125 |         index={i}
126 |         id={component.id}
127 |         updateComponent={updateComponent}
128 |         deleteComponent={deleteComponent}
129 |         component={component}
130 |         focusComponent={focusComponent}
131 |         onExpansionPanelChange={this.handleExpansionPanelChange}
132 |         moveToBottom={moveToBottom}
133 |         moveToTop={moveToTop}
134 |       />,
135 |     );
136 |     // className={classes.root}
137 | 
138 |     return (
139 |       <div className='column left'>
140 |         <FormControl
141 |           fullWidth={true}
142 |           formlabellasses={{
143 |             root: classes.cssLabel,
144 |           }}
145 |         >
146 |           <Grid container alignItems='baseline' align='stretch'>
147 |             <Grid item xs={10}>
148 |               <TextField
149 |                 id='title-input'
150 |                 label='Add a new component'
151 |                 placeholder='AppComponent'
152 |                 margin='normal'
153 |                 autoFocus
154 |                 onChange={this.handleChange}
155 |                 onKeyPress={(ev) => {
156 |                   if (ev.key === 'Enter') {
157 |                     // Do code here
158 |                     this.handleAddComponent();
159 |                     ev.preventDefault();
160 |                   }
161 |                 }}
162 |                 value={componentName}
163 |                 name='componentName'
164 |                 className={classes.light}
165 |                 InputProps={{
166 |                   className: classes.input,
167 |                 }}
168 |                 InputLabelProps={{
169 |                   className: classes.input,
170 |                 }}
171 |               />
172 |             </Grid>
173 |             <Grid item xs={2}>
174 |               <Button
175 |                 variant='fab'
176 |                 mini
177 |                 color='primary'
178 |                 className={classes.button}
179 |                 aria-label='Add'
180 |                 onClick={this.handleAddComponent}
181 |                 disabled={!this.state.componentName}
182 |               >
183 |                 <AddIcon />
184 |               </Button>
185 |             </Grid>
186 |           </Grid>
187 |         </FormControl>
188 |         <div className='expansionPanel'>
189 |           {componentsExpansionPanel}
190 |         </div>
191 |         <Button
192 |           color='secondary'
193 |           aria-label='Delete All'
194 |           variant='contained'
195 |           onClick={this.clearWorkspace}
196 |           disabled={totalComponents < 1}
197 |           className={classes.clearButton}
198 |         >
199 |           Clear workspace
200 |         </Button>
201 |         {modal}
202 |       </div >
203 |     );
204 |   }
205 | }
206 | 
207 | export default compose(withStyles(styles), connect(null, mapDispatchToProps))(LeftContainer);
208 | 
209 | LeftContainer.propTypes = {
210 |   components: PropTypes.array.isRequired,
211 |   addComponent: PropTypes.func.isRequired,
212 |   deleteComponent: PropTypes.func.isRequired,
213 |   updateComponent: PropTypes.func.isRequired,
214 |   deleteAllData: PropTypes.func.isRequired,
215 |   moveToBottom: PropTypes.func.isRequired,
216 |   moveToTop: PropTypes.func.isRequired,
217 |   focusComponent: PropTypes.object.isRequired,
218 |   openExpansionPanel: PropTypes.func.isRequired,
219 |   totalComponents: PropTypes.number.isRequired,
220 |   classes: PropTypes.object,
221 | };
222 | 


--------------------------------------------------------------------------------
/src/containers/MainContainer.jsx:
--------------------------------------------------------------------------------
  1 | import React, { Component } from 'react';
  2 | import PropTypes from 'prop-types';
  3 | import { connect } from 'react-redux';
  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 TextField from '@material-ui/core/TextField';
  8 | import { MuiThemeProvider } from '@material-ui/core/styles';
  9 | import theme from '../components/theme';
 10 | import {
 11 |   toggleDragging, openExpansionPanel, handleTransform, createApplication, changeImagePath,
 12 | } from '../actions/components';
 13 | import KonvaStage from '../components/KonvaStage.jsx';
 14 | import MainContainerHeader from '../components/MainContainerHeader.jsx';
 15 | import createModal from '../utils/createModal.util';
 16 | import Info from '../components/Info.jsx';
 17 | 
 18 | const IPC = require('electron').ipcRenderer;
 19 | 
 20 | const mapDispatchToProps = dispatch => ({
 21 |   handleTransformation: (id, {
 22 |     x, y, width, height,
 23 |   }) => dispatch(handleTransform(id, {
 24 |     x, y, width, height,
 25 |   })),
 26 |   toggleComponetDragging: status => dispatch(toggleDragging(status)),
 27 |   openPanel: component => dispatch(openExpansionPanel(component)),
 28 |   createApp: ({
 29 |     path, components, genOption, repoUrl,
 30 |   }) => dispatch(createApplication({
 31 |     path, components, genOption, repoUrl,
 32 |   })),
 33 |   changeImagePath: path => dispatch(changeImagePath(path)),
 34 | });
 35 | 
 36 | const mapStateToProps = store => ({
 37 |   totalComponents: store.workspace.totalComponents,
 38 |   imagePath: store.workspace.imagePath,
 39 |   focusComponent: store.workspace.focusComponent,
 40 | });
 41 | 
 42 | class MainContainer extends Component {
 43 |   state = {
 44 |     repoUrl: '',
 45 |     image: '',
 46 |     modal: null,
 47 |     genOptions: ['Export into existing project.', 'Export with starter repo.', 'Export with create-react-app.'],
 48 |     genOption: 0,
 49 |     draggable: false,
 50 |     toggleClass: true,
 51 |     scaleX: 1,
 52 |     scaleY: 1,
 53 |     x: undefined,
 54 |     y: undefined,
 55 |   };
 56 | 
 57 |   constructor(props) {
 58 |     super(props);
 59 | 
 60 |     IPC.on('new-file', (event, file) => {
 61 |       const image = new window.Image();
 62 |       image.src = file;
 63 |       this.props.changeImagePath(file);
 64 |       image.onload = () => {
 65 |         this.setState({ image });
 66 |       };
 67 |       this.draggableItems = [];
 68 |     });
 69 | 
 70 |     IPC.on('app_dir_selected', (event, path) => {
 71 |       const { components } = this.props;
 72 |       const { genOption, repoUrl } = this.state;
 73 |       this.props.createApp({
 74 |         path, components, genOption, repoUrl,
 75 |       });
 76 |     });
 77 |   }
 78 | 
 79 |   setImage = () => {
 80 |     const image = new window.Image();
 81 |     image.src = this.props.imagePath;
 82 |     image.onload = () => {
 83 |       // setState will redraw layer
 84 |       // because "image" property is changed
 85 |       this.setState({
 86 |         image,
 87 |       });
 88 |     };
 89 |   }
 90 | 
 91 |   componentDidMount() {
 92 |     this.setImage();
 93 |   }
 94 | 
 95 |   handleChange = (event) => {
 96 |     this.setState({ repoUrl: event.target.value.trim() });
 97 |   }
 98 | 
 99 |   updateImage = () => {
100 |     IPC.send('update-file');
101 |   }
102 | 
103 |   increaseHeight = () => {
104 |     this.setState({
105 |       scaleX: this.state.scaleX * 1.5,
106 |       scaleY: this.state.scaleY * 1.5,
107 |     });
108 |   }
109 | 
110 |   decreaseHeight = () => {
111 |     this.setState({
112 |       scaleX: this.state.scaleX * 0.75,
113 |       scaleY: this.state.scaleY * 0.75,
114 |     });
115 |   }
116 | 
117 |   deleteImage = () => {
118 |     this.props.changeImagePath('');
119 |     this.setState({ image: '' });
120 |   };
121 | 
122 |   closeModal = () => this.setState({ modal: null });
123 | 
124 |   chooseAppDir = () => IPC.send('choose_app_dir');
125 | 
126 |   toggleDrag = () => {
127 |     this.props.toggleComponetDragging(this.state.draggable);
128 |     this.setState({
129 |       toggleClass: !this.state.toggleClass,
130 |       draggable: !this.state.draggable,
131 |     });
132 |   }
133 | 
134 |   showImageDeleteModal = () => {
135 |     const { closeModal, deleteImage } = this;
136 |     this.setState({
137 |       modal: createModal({
138 |         closeModal,
139 |         message: 'Are you sure you want to delete image?',
140 |         secBtnLabel: 'Delete',
141 |         secBtnAction: () => { deleteImage(); closeModal(); },
142 |       }),
143 |     });
144 |   }
145 | 
146 |   displayUrlModal = () => {
147 |     const { closeModal, chooseAppDir } = this;
148 |     const children = <TextField
149 |       id='url'
150 |       label='Repository URL'
151 |       placeholder='https://github.com/kriasoft/react-starter-kit.git'
152 |       margin='normal'
153 |       onChange={this.handleChange}
154 |       name='repoUrl'
155 |       style={{ width: '95%' }}
156 |     />;
157 |     this.setState({
158 |       modal: createModal({
159 |         closeModal,
160 |         children,
161 |         message: 'Enter repository URL:',
162 |         primBtnLabel: 'Accept',
163 |         primBtnAction: () => { chooseAppDir(); closeModal(); },
164 |         secBtnLabel: 'Cancel',
165 |         secBtnAction: () => { this.setState({ repoUrl: '' }); closeModal(); },
166 |       }),
167 |     });
168 |   }
169 | 
170 |   chooseGenOptions = (genOption) => {
171 |     // set option
172 |     this.setState({ genOption });
173 |     // closeModal
174 |     this.closeModal();
175 |     if (genOption === 1) {
176 |       this.displayUrlModal();
177 |     } else {
178 |       // Choose app dir
179 |       this.chooseAppDir();
180 |     }
181 |   }
182 | 
183 |   showGenerateAppModal = () => {
184 |     const { closeModal, chooseGenOptions } = this;
185 |     const { genOptions } = this.state;
186 |     const children = <List className='export-preference'>{genOptions.map(
187 |       (option, i) => <ListItem key={i} button onClick={() => chooseGenOptions(i)} style={{ border: '1px solid #3f51b5', marginBottom: '2%', marginTop: '5%' }}>
188 |         <ListItemText primary={option} style={{ textAlign: 'center' }} />
189 |       </ListItem>,
190 |     )}
191 |     </List>;
192 |     this.setState({
193 |       modal: createModal({
194 |         closeModal,
195 |         children,
196 |         message: 'Choose export preference:',
197 |       }),
198 |     });
199 |   }
200 | 
201 |   render() {
202 |     const {
203 |       image, draggable, scaleX, scaleY, modal, toggleClass,
204 |     } = this.state;
205 |     const {
206 |       components,
207 |       handleTransformation,
208 |       openPanel,
209 |       totalComponents,
210 |       collapseColumn,
211 |       rightColumnOpen,
212 |       focusComponent,
213 |     } = this.props;
214 |     const {
215 |       increaseHeight,
216 |       decreaseHeight,
217 |       updateImage,
218 |       toggleDrag,
219 |       main,
220 |       showImageDeleteModal,
221 |       showGenerateAppModal,
222 |       setImage,
223 |     } = this;
224 |     const cursor = this.state.draggable ? 'move' : 'default';
225 | 
226 |     return (
227 |       <MuiThemeProvider theme={theme}>
228 |         <div
229 |           className="main-container"
230 |           style={{ cursor }}>
231 |           <MainContainerHeader
232 |             image={image}
233 |             increaseHeight={increaseHeight}
234 |             decreaseHeight={decreaseHeight}
235 |             showImageDeleteModal={showImageDeleteModal}
236 |             showGenerateAppModal={showGenerateAppModal}
237 |             updateImage={updateImage}
238 |             toggleDrag={toggleDrag}
239 |             totalComponents={totalComponents}
240 |             collapseColumn={collapseColumn}
241 |             rightColumnOpen={rightColumnOpen}
242 |             components={components}
243 |             toggleClass={toggleClass}
244 |           />
245 |           <div className="main" ref={main}>
246 |             {
247 |               components.length > 0 || image ? (
248 |                 <KonvaStage
249 |                   scaleX={scaleX}
250 |                   scaleY={scaleY}
251 |                   image={image}
252 |                   draggable={draggable}
253 |                   components={components}
254 |                   handleTransform={handleTransformation}
255 |                   openExpansionPanel={openPanel}
256 |                   focusComponent={focusComponent}
257 |                   setImage={setImage}
258 |                 />
259 |               ) : <Info />
260 |             }
261 |           </div>
262 |           {modal}
263 |         </div>
264 |       </MuiThemeProvider>
265 |     );
266 |   }
267 | }
268 | 
269 | MainContainer.propTypes = {
270 |   components: PropTypes.array.isRequired,
271 |   handleTransformation: PropTypes.func.isRequired,
272 |   toggleComponetDragging: PropTypes.func.isRequired,
273 |   totalComponents: PropTypes.number.isRequired,
274 |   openPanel: PropTypes.func.isRequired,
275 |   collapseColumn: PropTypes.func.isRequired,
276 |   createApp: PropTypes.func.isRequired,
277 |   changeImagePath: PropTypes.func.isRequired,
278 |   imagePath: PropTypes.string.isRequired,
279 |   rightColumnOpen: PropTypes.bool.isRequired,
280 |   focusComponent: PropTypes.object.isRequired,
281 | };
282 | 
283 | export default connect(mapStateToProps, mapDispatchToProps)(MainContainer);
284 | 


--------------------------------------------------------------------------------
/src/containers/RightContainer.jsx:
--------------------------------------------------------------------------------
 1 | import React, { Component } from 'react';
 2 | import { connect } from 'react-redux';
 3 | import PropTypes from 'prop-types';
 4 | import {
 5 |   handleClose,
 6 |   deleteCompProp,
 7 |   addCompProp,
 8 | } from '../actions/components';
 9 | import Snackbars from '../components/Snackbars.jsx';
10 | import RightTabs from '../components/RightTabs.jsx';
11 | 
12 | const IPC = require('electron').ipcRenderer;
13 | 
14 | const mapDispatchToProps = dispatch => ({
15 |   handleNotificationClose: () => dispatch(handleClose()),
16 |   deleteProp: ({ id, index }) => dispatch(deleteCompProp({ id, index })),
17 |   addProp: prop => dispatch(addCompProp(prop)),
18 | });
19 | 
20 | const mapStateToProps = store => ({
21 |   successOpen: store.workspace.successOpen,
22 |   errorOpen: store.workspace.errorOpen,
23 |   appDir: store.workspace.appDir,
24 | });
25 | 
26 | class RightContainer extends Component {
27 |   state = {
28 |     successOpen: false,
29 |     errorOpen: false,
30 |   }
31 | 
32 |   viewAppDir = () => {
33 |     IPC.send('view_app_dir', this.props.appDir);
34 |   }
35 | 
36 |   render() {
37 |     const {
38 |       components,
39 |       successOpen,
40 |       errorOpen,
41 |       handleNotificationClose,
42 |       appDir,
43 |       focusComponent,
44 |       deleteProp,
45 |       addProp,
46 |       rightColumnOpen,
47 |     } = this.props;
48 | 
49 |     return (
50 |       <div className='column-right' style={{ width: `${this.props.width}%` }} >
51 |         <RightTabs
52 |           components={components}
53 |           focusComponent={focusComponent}
54 |           deleteProp={deleteProp}
55 |           addProp={addProp}
56 |           rightColumnOpen={rightColumnOpen}
57 |         />
58 |         <Snackbars
59 |           successOpen={successOpen}
60 |           errorOpen={errorOpen}
61 |           handleNotificationClose={handleNotificationClose}
62 |           msg={appDir}
63 |           viewAppDir={this.viewAppDir}
64 |         />
65 |       </div>
66 |     );
67 |   }
68 | }
69 | 
70 | RightContainer.propTypes = {
71 |   components: PropTypes.array.isRequired,
72 |   successOpen: PropTypes.bool.isRequired,
73 |   appDir: PropTypes.string,
74 |   errorOpen: PropTypes.bool.isRequired,
75 |   focusComponent: PropTypes.object.isRequired,
76 |   handleNotificationClose: PropTypes.func.isRequired,
77 |   deleteProp: PropTypes.func.isRequired,
78 |   addProp: PropTypes.func.isRequired,
79 |   width: PropTypes.number.isRequired,
80 |   rightColumnOpen: PropTypes.bool.isRequired,
81 | };
82 | 
83 | 
84 | export default connect(mapStateToProps, mapDispatchToProps)(RightContainer);
85 | 


--------------------------------------------------------------------------------
/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.jsx';
 6 | import store from './store';
 7 | 
 8 | ReactDOM.render(
 9 |   <Provider store={store}>
10 |     <App />
11 |   </Provider>,
12 |   document.getElementById('app'),
13 | );
14 | 


--------------------------------------------------------------------------------
/src/localStorage.js:
--------------------------------------------------------------------------------
1 | import localforage from 'localforage';
2 | 
3 | export const saveState = state => localforage.setItem('state-v1.0.1', state);
4 | export const loadState = () => localforage.getItem('state-v1.0.1');
5 | 


--------------------------------------------------------------------------------
/src/public/icons/mac/icon.icns:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/React-Proto/react-proto/e5e988c7afd961891e3dd8afc2c5c7bf813ae534/src/public/icons/mac/icon.icns


--------------------------------------------------------------------------------
/src/public/icons/png/1024x1024.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/React-Proto/react-proto/e5e988c7afd961891e3dd8afc2c5c7bf813ae534/src/public/icons/png/1024x1024.png


--------------------------------------------------------------------------------
/src/public/icons/png/128x128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/React-Proto/react-proto/e5e988c7afd961891e3dd8afc2c5c7bf813ae534/src/public/icons/png/128x128.png


--------------------------------------------------------------------------------
/src/public/icons/png/16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/React-Proto/react-proto/e5e988c7afd961891e3dd8afc2c5c7bf813ae534/src/public/icons/png/16x16.png


--------------------------------------------------------------------------------
/src/public/icons/png/24x24.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/React-Proto/react-proto/e5e988c7afd961891e3dd8afc2c5c7bf813ae534/src/public/icons/png/24x24.png


--------------------------------------------------------------------------------
/src/public/icons/png/256x256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/React-Proto/react-proto/e5e988c7afd961891e3dd8afc2c5c7bf813ae534/src/public/icons/png/256x256.png


--------------------------------------------------------------------------------
/src/public/icons/png/32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/React-Proto/react-proto/e5e988c7afd961891e3dd8afc2c5c7bf813ae534/src/public/icons/png/32x32.png


--------------------------------------------------------------------------------
/src/public/icons/png/48x48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/React-Proto/react-proto/e5e988c7afd961891e3dd8afc2c5c7bf813ae534/src/public/icons/png/48x48.png


--------------------------------------------------------------------------------
/src/public/icons/png/512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/React-Proto/react-proto/e5e988c7afd961891e3dd8afc2c5c7bf813ae534/src/public/icons/png/512x512.png


--------------------------------------------------------------------------------
/src/public/icons/png/64x64.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/React-Proto/react-proto/e5e988c7afd961891e3dd8afc2c5c7bf813ae534/src/public/icons/png/64x64.png


--------------------------------------------------------------------------------
/src/public/icons/win/icon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/React-Proto/react-proto/e5e988c7afd961891e3dd8afc2c5c7bf813ae534/src/public/icons/win/icon.ico


--------------------------------------------------------------------------------
/src/public/images/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/React-Proto/react-proto/e5e988c7afd961891e3dd8afc2c5c7bf813ae534/src/public/images/.gitkeep


--------------------------------------------------------------------------------
/src/public/index.html:
--------------------------------------------------------------------------------
 1 | <!DOCTYPE html>
 2 | <html lang="en">
 3 | 
 4 | <head>
 5 |   <meta charset="UTF-8">
 6 |   <meta name="viewport" content="width=device-width, initial-scale=1.0">
 7 |   <meta http-equiv="X-UA-Compatible" content="ie=edge">
 8 |   <meta http-equiv="Content-Security-Policy" content="default-src 'self' https: filesystem: data: ws: 'unsafe-eval' 'unsafe-inline'">
 9 |   <title>React Proto</title>
10 |   <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500">
11 |   <link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
12 | </head>
13 | 
14 | <body>
15 |   <div id="app"></div>
16 | </body>
17 | 
18 | </html>


--------------------------------------------------------------------------------
/src/public/styles/style.css:
--------------------------------------------------------------------------------
  1 | *,
  2 | *::before,
  3 | *::after {
  4 |   box-sizing: inherit;
  5 | }
  6 | 
  7 | 
  8 | html {
  9 |   box-sizing: border-box;
 10 |   overflow: hidden;
 11 | }
 12 | 
 13 | body {
 14 |   margin: 0;
 15 |   padding: 0;
 16 |   font-family: "Open sans", sans-serif;
 17 |   font-weight: 400;
 18 | }
 19 | 
 20 | /*
 21 | /////////////////////////////////////////// 
 22 | Remove focus styling
 23 | /////////////////////////////////////////////
 24 | */
 25 | 
 26 | :focus {
 27 |   outline: 0;
 28 | }
 29 | 
 30 | :disabled {
 31 |   cursor: not-allowed;
 32 | }
 33 | 
 34 | header {
 35 |   display: none;
 36 | }
 37 | 
 38 | .app-container {
 39 |   display: flex;
 40 |   width: 100%;
 41 |   height: 100vh;
 42 | }
 43 | 
 44 | .progress-bar {
 45 |   flex-grow: 1;
 46 | }
 47 | 
 48 | .column {
 49 |   width: 25%;
 50 |   background-color: #252526;
 51 | }
 52 | 
 53 | 
 54 | /* .column-right-export-btn {
 55 |   transition: display 400ms ease-in-out 300ms;
 56 | } */
 57 | 
 58 | 
 59 | /*
 60 | /////////////////////////////////////////// 
 61 | BUTTONS
 62 | /////////////////////////////////////////////
 63 | */
 64 | 
 65 | .btn {
 66 |   font-size: 16px;
 67 | }
 68 | 
 69 | /*
 70 | /////////////////////////////////////////// 
 71 | LEFT COLUMN
 72 | /////////////////////////////////////////////
 73 | */
 74 | 
 75 | .left {
 76 |   padding: 20px;
 77 |   width: 22%;
 78 |   display: flex;
 79 |   flex-direction: column;
 80 | }
 81 | 
 82 | .component-input {
 83 |   bottom: 2.8%;
 84 | }
 85 | 
 86 | .expansionPanel {
 87 |   flex-direction: column-reverse;
 88 |   display: flex;
 89 |   max-height: 80%;
 90 |   overflow-y: auto;
 91 |   padding: 1%;
 92 | }
 93 | 
 94 | .clear-workspace {
 95 |   top: 96%;
 96 |   position: sticky !important;
 97 |   z-index: 1;
 98 | }
 99 | 
100 | /*
101 | /////////////////////////////////////////// 
102 | MAIN COLUMN
103 | /////////////////////////////////////////////
104 | */
105 | 
106 | h1 {
107 |   color: #ccc;
108 | }
109 | 
110 | .main-container {
111 |   display: flex;
112 |   flex-direction: column;
113 |   flex: 1;
114 |   overflow-x: auto;
115 |   overflow-y: auto;
116 | }
117 | 
118 | .main-header {
119 |   display: flex;
120 |   /* border-left: 1px solid grey; */
121 |   /* border-right: 1px solid grey; */
122 |   /* border-bottom: 1px solid grey; */
123 |   background-color: #212121;
124 |   box-shadow: 0 5px 7px -2px rgba(0,0,0, 0.4);
125 |   z-index: 10;
126 | }
127 | 
128 | 
129 | .main-header-buttons {
130 |   display: flex;
131 |   font-size: 12px;
132 | }
133 | 
134 | .main {
135 |   /* border-left: 1px solid grey; */
136 |   /* border-right: 1px solid grey; */
137 |   background: #fff;
138 |   flex: 1;
139 |   width: 100%;
140 |   overflow: auto;
141 |   display: flex;
142 |   background-color: #1e1e1e;
143 |   /* justify-content: center;
144 |   align-items: center; */
145 | }
146 | 
147 | 
148 | .draggable {
149 |   position: relative;
150 | }
151 | 
152 | .image {
153 |   top: 0;
154 |   left: 0;
155 |   width: 100%;
156 |   position: relative;
157 | }
158 | 
159 | .image-container {
160 |   position: relative;
161 |   display: flex;
162 |   top: 0;
163 |   left: 0;
164 | }
165 | 
166 | .export-preference div span {
167 |   transition: color 1s, background-color 1.5s, transform 2s;
168 | }
169 | 
170 | .export-preference div:hover {
171 |   background-color: #303f9f;
172 | }
173 | 
174 | .export-preference div:hover span {
175 |   color: white;
176 | }
177 | 
178 | .info {
179 |   width: 100%;
180 |   height: 100%;
181 |   display: flex;
182 |   justify-content: center;
183 |   align-items: center;
184 |   text-align: center;
185 | }
186 | 
187 | /*
188 | /////////////////////////////////////////// 
189 | RIGHT COLUMN
190 | /////////////////////////////////////////////
191 | */
192 | 
193 | .export {
194 |   border-top: 1px solid #ccc;
195 |   display: flex;
196 |   justify-content: center;
197 |   align-items: center;
198 |   flex-direction: row;
199 | }
200 | 
201 | .column-right {
202 |   transition: width 250ms ease-in-out;
203 |   height: 100%;
204 |   display: flex; 
205 |   flex-direction: column;
206 |   background-color: #303030;
207 | }
208 | 
209 | .props-container {
210 |   margin-left: 10px;
211 |   height: 100%;
212 | }
213 | 
214 | .props-input {
215 |   margin-bottom: 15px;
216 | }
217 | 
218 | .chips {
219 |   height: 55%;
220 |   overflow: auto;
221 | }
222 | 
223 | /* Sortable tree sorting */
224 | .sortable-tree {
225 |   height: 100%;
226 |   background-color: rgb(37, 37, 38);
227 | }
228 | 
229 | div.rst__rowContents {
230 |   width: 45px;
231 |   border: none;
232 |   color: #eee;
233 |   background-color: #333333;
234 | }
235 | 
236 | .rst__rowContentsDragDisabled {
237 |   background-color: #333333;
238 |   background-color: rgba(37, 37, 38, .4);
239 | }
240 | 
241 | 
242 | .rst__moveHandle, .rst__loadingHandle {
243 |   width: 40px;
244 | }
245 | 
246 | .rst_tree {
247 |   overflow: auto;
248 | }
249 | 
250 | .rst__rowLabel {
251 |   padding-right: 5px;
252 | }
253 | 


--------------------------------------------------------------------------------
/src/reducers/componentReducer.js:
--------------------------------------------------------------------------------
  1 | import {
  2 |   LOAD_INIT_DATA,
  3 |   ADD_COMPONENT,
  4 |   UPDATE_COMPONENT,
  5 |   DELETE_COMPONENT,
  6 |   UPDATE_CHILDREN,
  7 |   REASSIGN_PARENT,
  8 |   SET_SELECTABLE_PARENTS,
  9 |   EXPORT_FILES,
 10 |   CREATE_APPLICATION,
 11 |   EXPORT_FILES_SUCCESS,
 12 |   EXPORT_FILES_ERROR,
 13 |   CREATE_APPLICATION_ERROR,
 14 |   HANDLE_CLOSE,
 15 |   HANDLE_TRANSFORM,
 16 |   TOGGLE_DRAGGING,
 17 |   MOVE_TO_BOTTOM,
 18 |   MOVE_TO_TOP,
 19 |   OPEN_EXPANSION_PANEL,
 20 |   DELETE_ALL_DATA,
 21 |   CHANGE_IMAGE_PATH,
 22 |   ADD_PROP,
 23 |   DELETE_PROP,
 24 | } from '../actionTypes';
 25 | 
 26 | import {
 27 |   addComponent,
 28 |   updateComponent,
 29 |   deleteComponent,
 30 |   updateChildren,
 31 |   reassignParent,
 32 |   setSelectableP,
 33 |   exportFilesSuccess,
 34 |   exportFilesError,
 35 |   handleClose,
 36 |   handleTransform,
 37 |   toggleDragging,
 38 |   moveToBottom,
 39 |   moveToTop,
 40 |   openExpansionPanel,
 41 |   changeImagePath,
 42 |   addProp,
 43 |   deleteProp,
 44 | } from '../utils/componentReducer.util';
 45 | 
 46 | const initialApplicationState = {
 47 |   totalComponents: 0,
 48 |   nextId: 1,
 49 |   imagePath: '',
 50 |   successOpen: false,
 51 |   errorOpen: false,
 52 |   focusComponent: {},
 53 |   components: [],
 54 |   appDir: '',
 55 |   loading: false,
 56 | };
 57 | 
 58 | const componentReducer = (state = initialApplicationState, action) => {
 59 |   switch (action.type) {
 60 |     case LOAD_INIT_DATA:
 61 |       return {
 62 |         ...state,
 63 |         ...action.payload.data,
 64 |         loading: false,
 65 |         appDir: '',
 66 |         successOpen: false,
 67 |         errorOpen: false,
 68 |       };
 69 |     case ADD_COMPONENT:
 70 |       return addComponent(state, action.payload);
 71 |     case UPDATE_COMPONENT:
 72 |       return updateComponent(state, action.payload);
 73 |     case DELETE_COMPONENT:
 74 |       return deleteComponent(state, action.payload);
 75 |     case UPDATE_CHILDREN:
 76 |       return updateChildren(state, action.payload);
 77 |     case REASSIGN_PARENT:
 78 |       return reassignParent(state, action.payload);
 79 |     case SET_SELECTABLE_PARENTS:
 80 |       return setSelectableP(state);
 81 |     case CREATE_APPLICATION:
 82 |     case EXPORT_FILES:
 83 |       return { ...state, loading: true };
 84 |     case EXPORT_FILES_SUCCESS:
 85 |       return exportFilesSuccess(state, action.payload);
 86 |     case CREATE_APPLICATION_ERROR:
 87 |     case EXPORT_FILES_ERROR:
 88 |       return exportFilesError(state, action.payload);
 89 |     case HANDLE_CLOSE:
 90 |       return handleClose(state, action.payload);
 91 |     case HANDLE_TRANSFORM:
 92 |       return handleTransform(state, action.payload);
 93 |     case TOGGLE_DRAGGING:
 94 |       return toggleDragging(state, action.payload);
 95 |     case MOVE_TO_BOTTOM:
 96 |       return moveToBottom(state, action.payload);
 97 |     case MOVE_TO_TOP:
 98 |       return moveToTop(state, action.payload);
 99 |     case OPEN_EXPANSION_PANEL:
100 |       return openExpansionPanel(state, action.payload);
101 |     case DELETE_ALL_DATA:
102 |       return initialApplicationState;
103 |     case CHANGE_IMAGE_PATH:
104 |       return changeImagePath(state, action.payload);
105 |     case ADD_PROP:
106 |       return addProp(state, action.payload);
107 |     case DELETE_PROP:
108 |       return deleteProp(state, action.payload);
109 |     default:
110 |       return state;
111 |   }
112 | };
113 | 
114 | export default componentReducer;
115 | 


--------------------------------------------------------------------------------
/src/reducers/index.js:
--------------------------------------------------------------------------------
 1 | import { combineReducers } from 'redux';
 2 | 
 3 | import componentReducer from './componentReducer';
 4 | 
 5 | const reducers = combineReducers({
 6 |   workspace: componentReducer,
 7 | });
 8 | 
 9 | export default reducers;
10 | 


--------------------------------------------------------------------------------
/src/setupTests.js:
--------------------------------------------------------------------------------
1 | import Enzyme, { shallow, render, mount } from 'enzyme';
2 | import Adapter from 'enzyme-adapter-react-16';
3 | 
4 | Enzyme.configure({ adapter: new Adapter() });
5 | 
6 | global.shallow = shallow;
7 | global.render = render;
8 | global.mount = mount;
9 | 


--------------------------------------------------------------------------------
/src/store.js:
--------------------------------------------------------------------------------
 1 | import logger from 'redux-logger';
 2 | import throttle from 'lodash.throttle';
 3 | import { composeWithDevTools } from 'redux-devtools-extension/developmentOnly';
 4 | import { createStore, applyMiddleware, compose } from 'redux';
 5 | import thunk from 'redux-thunk';
 6 | import reducers from './reducers';
 7 | import { saveState } from './localStorage';
 8 | 
 9 | let composer;
10 | 
11 | if (process.env.NODE_ENV === 'development') {
12 |   composer = compose(
13 |     applyMiddleware(logger, thunk),
14 |     composeWithDevTools(),
15 |   );
16 | } else {
17 |   composer = compose(applyMiddleware(thunk));
18 | }
19 | 
20 | const store = createStore(
21 |   reducers,
22 |   composer,
23 | );
24 | 
25 | store.subscribe(throttle(() => saveState(store.getState()), 1000));
26 | 
27 | export default store;
28 | 


--------------------------------------------------------------------------------
/src/utils/colors.util.js:
--------------------------------------------------------------------------------
 1 | const colors = [
 2 |   '#F44336',
 3 |   '#E91E63',
 4 |   '#9C27B0',
 5 |   '#2196F3',
 6 |   '#009688',
 7 |   '#00BCD4',
 8 |   '#18FFFF',
 9 |   '#64FFDA',
10 |   '#CDDC39',
11 |   '#4CAF50',
12 |   '#76FF03',
13 |   '#C6FF00',
14 |   '#FF9800',
15 |   '#FF6D00',
16 |   '#F50057',
17 |   '#D500F9',
18 | ];
19 | 
20 | const getColor = () => colors[Math.floor(Math.random() * colors.length)];
21 | 
22 | export default getColor;
23 | 


--------------------------------------------------------------------------------
/src/utils/componentReducer.util.js:
--------------------------------------------------------------------------------
  1 | import setSelectableParents from './setSelectableParents.util';
  2 | import getColor from './colors.util';
  3 | 
  4 | const initialComponentState = {
  5 |   id: null,
  6 |   stateful: false,
  7 |   title: '',
  8 |   parentIds: [],
  9 |   color: getColor(),
 10 |   draggable: true,
 11 |   childrenIds: [],
 12 |   selectableParents: [],
 13 |   expanded: true,
 14 |   props: [],
 15 |   nextPropId: 0,
 16 |   position: {
 17 |     x: 110,
 18 |     y: 120,
 19 |     width: 50,
 20 |     height: 50,
 21 |   },
 22 | };
 23 | 
 24 | export const addComponent = (state, { title }) => {
 25 |   const strippedTitle = title
 26 |     .replace(/[a-z]+/gi,
 27 |       word => word[0].toUpperCase() + word.slice(1))
 28 |     .replace(/[-_\s0-9\W]+/gi, '');
 29 |   const newComponent = {
 30 |     ...initialComponentState,
 31 |     title: strippedTitle,
 32 |     id: state.nextId.toString(),
 33 |     color: getColor(),
 34 |   };
 35 | 
 36 |   const components = [
 37 |     ...state.components,
 38 |     newComponent,
 39 |   ];
 40 | 
 41 |   const totalComponents = state.totalComponents + 1;
 42 |   const nextId = state.nextId + 1;
 43 | 
 44 |   return {
 45 |     ...state,
 46 |     totalComponents,
 47 |     nextId,
 48 |     components,
 49 |     focusComponent: newComponent,
 50 |   };
 51 | };
 52 | 
 53 | export const updateComponent = ((state, {
 54 |   id, newParentId = null, color = null, stateful = null, props = null,
 55 | }) => {
 56 |   let component;
 57 |   const components = state.components.map((comp) => {
 58 |     if (comp.id === id) {
 59 |       component = { ...comp };
 60 |       if (newParentId) {
 61 |         const parentIdsSet = new Set(component.parentIds);
 62 |         if (parentIdsSet.has(newParentId)) {
 63 |           parentIdsSet.delete(newParentId);
 64 |         } else {
 65 |           parentIdsSet.add(newParentId);
 66 |         }
 67 |         component.parentIds = [...parentIdsSet];
 68 |       }
 69 |       if (props) {
 70 |         component.props = props;
 71 |         component.nextPropId += 1;
 72 |       }
 73 |       component.color = color || component.color;
 74 |       component.stateful = stateful === null ? component.stateful : stateful;
 75 |       return component;
 76 |     }
 77 |     return comp;
 78 |   });
 79 | 
 80 |   return {
 81 |     ...state,
 82 |     components,
 83 |     focusComponent: component,
 84 |   };
 85 | });
 86 | 
 87 | // Delete component with the index for now, but will be adjusted to use id
 88 | export const deleteComponent = (state, { index, id }) => {
 89 |   const { focusComponent } = state;
 90 |   const components = [
 91 |     ...state.components.slice(0, index),
 92 |     ...state.components.slice(index + 1),
 93 |   ];
 94 | 
 95 |   const totalComponents = state.totalComponents - 1;
 96 | 
 97 |   return {
 98 |     ...state,
 99 |     totalComponents,
100 |     components,
101 |     focusComponent: focusComponent.id === id ? {} : focusComponent,
102 |   };
103 | };
104 | 
105 | // Add or remove children
106 | export const updateChildren = ((state, { parentIds, childId }) => {
107 |   const components = state.components.map((component) => {
108 |     if (parentIds.includes(component.id)) {
109 |       const parentComp = { ...component };
110 |       const childrenIdsSet = new Set(parentComp.childrenIds);
111 |       if (childrenIdsSet.has(childId)) {
112 |         childrenIdsSet.delete(childId);
113 |       } else {
114 |         childrenIdsSet.add(childId);
115 |       }
116 | 
117 |       parentComp.childrenIds = [...childrenIdsSet];
118 |       return parentComp;
119 |     }
120 |     return component;
121 |   });
122 | 
123 |   return {
124 |     ...state,
125 |     components,
126 |   };
127 | });
128 | 
129 | /**
130 |  * Moves component to the end of the components effectively giving it the highest z-index
131 |  * @param {object} state - The current state of the application
132 |  * @param {string} componentId - The id of the component that is to be moved
133 |  */
134 | 
135 | export const moveToTop = (state, componentId) => {
136 |   const components = state.components.concat();
137 |   const index = components.findIndex(component => component.id === componentId);
138 |   const removedComponent = components.splice(index, 1);
139 |   components.push(removedComponent[0]);
140 | 
141 |   return {
142 |     ...state,
143 |     components,
144 |   };
145 | };
146 | 
147 | /**
148 |  * Updates the current image path with the newly provided path
149 |  * @param {object} state - The current state of the application
150 |  * @param {string} imagePath - The new path for the updated image
151 |  */
152 | 
153 | export const changeImagePath = (state, imagePath) => ({
154 |   ...state,
155 |   imagePath,
156 | });
157 | 
158 | 
159 | // Assign comp's children to comp's parent
160 | export const reassignParent = ((state, { index, id, parentIds = [] }) => {
161 |   // Get all childrenIds of the component to be deleted
162 |   const { childrenIds } = state.components[index];
163 |   const components = state.components.map((comp) => {
164 |     // Give each child their previous parent's parent
165 |     if (childrenIds.includes(comp.id)) {
166 |       const prevParentIds = comp.parentIds.filter(parentId => parentId !== id);
167 |       return {
168 |         ...comp,
169 |         parentIds: [...new Set(prevParentIds.concat(parentIds))],
170 |       };
171 |     }
172 |     // Give the parent all children of it's to be deleted child
173 |     if (parentIds.includes(comp.id)) {
174 |       const prevChildrenIds = comp.childrenIds;
175 |       return { ...comp, childrenIds: [...new Set(prevChildrenIds.concat(childrenIds))] };
176 |     }
177 |     return comp;
178 |   });
179 | 
180 |   return {
181 |     ...state,
182 |     components,
183 |   };
184 | });
185 | 
186 | export const setSelectableP = (state => ({
187 |   ...state,
188 |   components: setSelectableParents(state.components),
189 | }));
190 | 
191 | export const exportFilesSuccess = ((state, { status, dir }) => ({
192 |   ...state,
193 |   successOpen: status,
194 |   appDir: dir,
195 |   loading: false,
196 | }));
197 | 
198 | export const exportFilesError = ((state, { status, err }) => ({
199 |   ...state,
200 |   errorOpen: status,
201 |   appDir: err,
202 |   loading: false,
203 | }));
204 | 
205 | export const handleClose = ((state, status) => ({
206 |   ...state,
207 |   errorOpen: status,
208 |   successOpen: status,
209 | }));
210 | 
211 | export const updatePosition = (state, { id, x, y }) => {
212 |   const components = state.components.map((component) => {
213 |     if (component.id === id) {
214 |       return {
215 |         ...component,
216 |         position: {
217 |           x,
218 |           y,
219 |           width: component.position.width,
220 |           height: component.position.height,
221 |         },
222 |       };
223 |     }
224 |     return component;
225 |   });
226 |   return {
227 |     ...state,
228 |     components,
229 |   };
230 | };
231 | 
232 | /**
233 |  * Applies the new x and y coordinates, as well as, the new width
234 |  * and height the of components to the component with the provided id.
235 |  * The transformation is calculated on component drags, as well as, whe the
236 |  * component is resized
237 |  * @param {object} state - The current state of the application
238 |  * @param {object} transform - Object containing new transformation
239 |  * @param {string} id - id of the component we want to apply the transformation to
240 |  * @param {number} x - updated x coordinate
241 |  * @param {number} y - updated y coordinate
242 |  * @param {number} width - updated width
243 |  * @param {number} height - updated height
244 |  */
245 | 
246 | export const handleTransform = (state, {
247 |   id, x, y, width, height,
248 | }) => {
249 |   const components = state.components.map((component) => {
250 |     if (component.id === id) {
251 |       return {
252 |         ...component,
253 |         position: {
254 |           x,
255 |           y,
256 |           width,
257 |           height,
258 |         },
259 |       };
260 |     }
261 |     return component;
262 |   });
263 |   return {
264 |     ...state,
265 |     components,
266 |   };
267 | };
268 | 
269 | /**
270 |  * Toggles the drag of the group, as well as all components. If the group is draggable the
271 |  * rectangles need to be undraggable so the user can drag the group from anywhere
272 |  * @param {object} state - The current state of the application
273 |  * @param {boolean} status - The boolean value to apply to all draggable components
274 |  */
275 | 
276 | export const toggleDragging = (state, status) => {
277 |   const components = state.components.map(component => ({
278 |     ...component,
279 |     draggable: status,
280 |   }));
281 |   return {
282 |     ...state,
283 |     components,
284 |   };
285 | };
286 | 
287 | /**
288 |  * Moves component to the front of the components effectively giving it the lowest z-index
289 |  * @param {object} state - The current state of the application
290 |  * @param {string} componentId - The id of the component that is to be moved
291 |  */
292 | 
293 | export const moveToBottom = (state, componentId) => {
294 |   const components = state.components.concat();
295 |   const index = components.findIndex(component => component.id === componentId);
296 |   const removedComponent = components.splice(index, 1);
297 |   components.unshift(removedComponent[0]);
298 | 
299 |   return {
300 |     ...state,
301 |     components,
302 |   };
303 | };
304 | 
305 | /**
306 |  * Selects a component and sets it as the focusComponent. The focus component is used to
307 |  * sync up expanding the panel, adding the transformer, and showing the components
308 |  * corresponding props.
309 |  * @param {object} state - The current state of the application
310 |  * @param {object} component - The component we want to assign as the currently focused component
311 |  */
312 | 
313 | export const openExpansionPanel = (state, { component }) => ({
314 |   ...state,
315 |   focusComponent: component,
316 | });
317 | 
318 | export const addProp = (state, {
319 |   key,
320 |   value = null,
321 |   required,
322 |   type,
323 | }) => {
324 |   const { props, nextPropId, id } = state.focusComponent;
325 |   const newProp = {
326 |     id: nextPropId.toString(),
327 |     key,
328 |     value: value || key,
329 |     required,
330 |     type,
331 |   };
332 |   const newProps = [...props, newProp];
333 |   return updateComponent(state, { id, props: newProps });
334 | };
335 | 
336 | export const deleteProp = (state, { index }) => {
337 |   const { props, id } = state.focusComponent;
338 |   const newProps = [...props.slice(0, index), ...props.slice(index + 1)];
339 |   return updateComponent(state, { id, props: newProps });
340 | };
341 | 


--------------------------------------------------------------------------------
/src/utils/componentRender.util.js:
--------------------------------------------------------------------------------
 1 | const componentRender = (component) => {
 2 |   const {
 3 |     stateful,
 4 |     children,
 5 |     title,
 6 |     props,
 7 |   } = component;
 8 | 
 9 |   if (stateful) {
10 |     return `
11 |       import React, { Component } from 'react';
12 |       import PropTypes from 'prop-types';
13 |       ${children.map(child => `import ${child.title} from './${child.title}.jsx'`).join('\n')}
14 | 
15 |       class ${title} extends Component {
16 |       constructor(props) {
17 |         super(props);
18 |         this.state = {};
19 |       }
20 |       render() {
21 |         const { ${props.map(p => `${p.key}`).join(', ')} } = this.props;
22 |         return (
23 |           <div>
24 |           ${children.map(child => `<${child.title} ${child.props.map(prop => `${prop.key}={${prop.value}}`).join(' ')}/>`).join('\n')}
25 |           </div>
26 |         )
27 |         }
28 |       }
29 | 
30 |       ${title}.propTypes = {
31 |         ${props.map(p => `${p.key}: PropTypes.${p.type}${p.required ? '.isRequired' : ''},`).join('\n')}
32 |       }
33 | 
34 |       export default ${title};
35 |     `;
36 |   }
37 | 
38 |   return `
39 |     import React from 'react';
40 |     import PropTypes from 'prop-types';
41 |     ${children.map(child => `import ${child.title} from './${child.title}.jsx'`).join('\n')}
42 |   
43 |     const ${title} = props => (
44 |       <div>
45 |         ${children.map(child => `<${child.title} ${child.props.map(prop => `${prop.key}={${prop.value}}`).join(' ')}/>`).join('\n')}
46 |       </div>
47 |     );
48 | 
49 |     ${title}.propTypes = {
50 |       ${props.map(p => `${p.key}: PropTypes.${p.type}${p.required ? '.isRequired' : ''},`).join('\n')}
51 |     }
52 | 
53 |     export default ${title};
54 |   `;
55 | };
56 | 
57 | export default componentRender;
58 | 


--------------------------------------------------------------------------------
/src/utils/convertIdsToObjs.util.js:
--------------------------------------------------------------------------------
 1 | let convertChildAndParentIds;
 2 | const convertComponentsArrToObj = components => components.reduce(
 3 |   (obj, comp) => {
 4 |     const compsObj = obj;
 5 |     compsObj[comp.id] = comp;
 6 |     return compsObj;
 7 |   }, {},
 8 | );
 9 | 
10 | const convertCompIdToObj = (id, compsObj) => {
11 |   const comp = compsObj[id];
12 |   if (!comp.children || (comp.childrenIds.length !== comp.children.length)) {
13 |     return convertChildAndParentIds(compsObj, comp);
14 |   }
15 |   if (!comp.parents || (comp.parentIds.length !== comp.parents.length)) {
16 |     return convertChildAndParentIds(compsObj, comp);
17 |   }
18 |   return comp;
19 | };
20 | 
21 | convertChildAndParentIds = (compsObj, component) => {
22 |   const childrenAsObj = component.childrenIds
23 |     .map(id => convertCompIdToObj(id, compsObj));
24 | 
25 |   const parentsAsObj = component.parentIds
26 |     .map(id => compsObj[id]);
27 | 
28 |   return { ...component, children: childrenAsObj, parents: parentsAsObj };
29 | };
30 | 
31 | const convertIdsToObjs = (components) => {
32 |   const compsObj = convertComponentsArrToObj(components);
33 |   return components.map(
34 |     component => convertChildAndParentIds(compsObj, component),
35 |   );
36 | };
37 | 
38 | export default convertIdsToObjs;
39 | 


--------------------------------------------------------------------------------
/src/utils/createApplication.util.js:
--------------------------------------------------------------------------------
 1 | import util from 'util';
 2 | 
 3 | const execFile = util.promisify(require('child_process').execFile);
 4 | 
 5 | // Application generation options
 6 | // cosnt genOptions = [
 7 | //   'Export into existing project.', 'Export with starter repo', 'Export with create-react-app.'
 8 | // ];
 9 | 
10 | async function createApplicationUtil({
11 |   path, appName, genOption, repoUrl,
12 | }) {
13 |   if (genOption === 2) {
14 |     return [
15 |       await execFile('npm', ['i', '-g', 'create-react-app'], { cwd: path }),
16 |       await execFile('create-react-app', [appName], { cwd: path }),
17 |     ];
18 |   }
19 |   return repoUrl ? execFile('git', ['clone', repoUrl, appName], { cwd: path }) : null;
20 | }
21 | 
22 | export default createApplicationUtil;
23 | 


--------------------------------------------------------------------------------
/src/utils/createFiles.util.js:
--------------------------------------------------------------------------------
 1 | import fs from 'fs';
 2 | import { format } from 'prettier';
 3 | import componentRender from './componentRender.util';
 4 | 
 5 | const createFiles = (data, path) => {
 6 |   let dir = path;
 7 |   if (!dir.match(/components|\*$/)) {
 8 |     if (fs.existsSync(`${dir}/src`)) {
 9 |       dir = `${dir}/src`;
10 |     }
11 |     dir = `${dir}/components`;
12 |     if (!fs.existsSync(dir)) {
13 |       fs.mkdirSync(dir);
14 |     }
15 |   }
16 |   const promises = [];
17 |   data.forEach((component) => {
18 |     const newPromise = new Promise((resolve, reject) => {
19 |       fs.writeFile(`${dir}/${component.title}.jsx`,
20 |         format(componentRender(component, data), {
21 |           singleQuote: true,
22 |           trailingComma: 'es5',
23 |           bracketSpacing: true,
24 |           jsxBracketSameLine: true,
25 |           parser: 'babylon',
26 |         }),
27 |         (err) => {
28 |           if (err) return reject(err.message);
29 |           return resolve(path);
30 |         });
31 |     });
32 | 
33 |     promises.push(newPromise);
34 |   });
35 | 
36 |   return Promise.all(promises);
37 | };
38 | 
39 | export default createFiles;
40 | 


--------------------------------------------------------------------------------
/src/utils/createModal.util.js:
--------------------------------------------------------------------------------
 1 | import React from 'react';
 2 | import PropTypes from 'prop-types';
 3 | import SimpleModal from '../components/SimpleModal.jsx';
 4 | 
 5 | const createModal = ({
 6 |   message,
 7 |   closeModal,
 8 |   primBtnLabel,
 9 |   primBtnAction,
10 |   secBtnAction = null,
11 |   secBtnLabel = null,
12 |   children = null,
13 |   open = true,
14 | }) => (
15 |   <SimpleModal
16 |     open={open}
17 |     message={message}
18 |     secBtnLabel={secBtnLabel}
19 |     primBtnLabel={primBtnLabel}
20 |     secBtnAction={secBtnAction}
21 |     primBtnAction={primBtnAction}
22 |     closeModal={closeModal}
23 |   >
24 |     {children}
25 |   </SimpleModal>
26 | );
27 | 
28 | createModal.propTypes = {
29 |   closeModal: PropTypes.func.isRequired,
30 |   primBtnAction: PropTypes.func.isRequired,
31 |   secBtnAction: PropTypes.func.isRequired,
32 |   open: PropTypes.bool,
33 |   children: PropTypes.object,
34 |   message: PropTypes.string.isRequired,
35 |   primBtnLabel: PropTypes.string.isRequired,
36 |   secBtnLabel: PropTypes.string.isRequired,
37 | };
38 | 
39 | export default createModal;
40 | 


--------------------------------------------------------------------------------
/src/utils/setSelectableParents.util.js:
--------------------------------------------------------------------------------
 1 | const getAllChildren = (components, childrenIds, unSelectable = []) => {
 2 |   if (!childrenIds || childrenIds.length < 1) return unSelectable;
 3 |   for (let i = 0; i < childrenIds.length; i += 1) {
 4 |     const component = components.find(({ id }) => id === childrenIds[i]);
 5 |     if (!unSelectable.includes(childrenIds[i])) {
 6 |       unSelectable.push(childrenIds[i]);
 7 |     }
 8 |     getAllChildren(components, component.childrenIds, unSelectable);
 9 |   }
10 |   return unSelectable;
11 | };
12 | 
13 | const getSelectableParents = ({
14 |   id, childrenIds,
15 |   parentIds, components,
16 | }) => {
17 |   const unSelectableParents = getAllChildren(components, childrenIds, [id, ...parentIds]);
18 |   return components
19 |     .filter(comp => !unSelectableParents.includes(comp.id));
20 | };
21 | 
22 | const setSelectableParents = components => components.map(
23 |   comp => (
24 |     {
25 |       ...comp,
26 |       selectableParents: getSelectableParents({
27 |         id: comp.id,
28 |         childrenIds: comp.childrenIds,
29 |         parentIds: comp.parentIds,
30 |         components,
31 |       }),
32 |     }
33 |   ),
34 | );
35 | 
36 | export default setSelectableParents;
37 | 


--------------------------------------------------------------------------------
/webpack.config.development.js:
--------------------------------------------------------------------------------
 1 | const path = require('path');
 2 | const webpack = require('webpack');
 3 | const HtmlWebpackPlugin = require('html-webpack-plugin');
 4 | const ExtractTextPlugin = require('extract-text-webpack-plugin');
 5 | const CopyWebpackPlugin = require('copy-webpack-plugin');
 6 | const CleanWebpackPlugin = require('clean-webpack-plugin');
 7 | 
 8 | const BUILD_DIR = path.join(__dirname, 'build');
 9 | const SRC_DIR = path.join(__dirname, 'src');
10 | 
11 | module.exports = {
12 |   mode: 'development',
13 |   target: 'electron-main',
14 |   context: SRC_DIR,
15 |   entry: ['babel-polyfill', './index.js'],
16 |   devtool: 'eval-source-map',
17 |   output: {
18 |     path: BUILD_DIR,
19 |     filename: 'js/bundle.js',
20 |   },
21 |   module: {
22 |     rules: [
23 |       {
24 |         test: /\.(js|jsx)$/,
25 |         exclude: /node_modules/,
26 |         use: ['babel-loader'],
27 |       },
28 |       {
29 |         test: /\.(s?css)$/,
30 |         use: ExtractTextPlugin.extract({
31 |           fallback: 'style-loader',
32 |           use: [
33 |             {
34 |               loader: 'css-loader',
35 |               options: {
36 |                 camelCase: true,
37 |                 sourceMap: true,
38 |               },
39 |             },
40 |             {
41 |               loader: 'postcss-loader',
42 |               options: {
43 |                 config: {
44 |                   ctx: {
45 |                     autoprefixer: {
46 |                       browsers: 'last 2 versions',
47 |                     },
48 |                   },
49 |                 },
50 |               },
51 |             },
52 |             {
53 |               loader: 'sass-loader',
54 |             },
55 |           ],
56 |         }),
57 |       },
58 |     ],
59 |   },
60 |   plugins: [
61 |     // new CleanWebpackPlugin([BUILD_DIR]),
62 |     new HtmlWebpackPlugin({
63 |       template: 'public/index.html',
64 |     }),
65 |     new ExtractTextPlugin({
66 |       filename: 'styles/style.css',
67 |       allChunks: true,
68 |     }),
69 |     new CopyWebpackPlugin([
70 |       {
71 |         from: 'public/images/**/*',
72 |         to: 'images/',
73 |         flatten: true,
74 |         force: true,
75 |       },
76 |     ]),
77 |   ],
78 |   devServer: {
79 |     contentBase: BUILD_DIR,
80 |     hot: true,
81 |   },
82 |   watch: true,
83 | };
84 | 


--------------------------------------------------------------------------------
/webpack.config.production.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 | const CleanWebpackPlugin = require('clean-webpack-plugin');
  6 | 
  7 | const BUILD_DIR = path.join(__dirname, 'build');
  8 | const SRC_DIR = path.join(__dirname, 'src');
  9 | 
 10 | module.exports = {
 11 |   mode: 'production',
 12 |   target: 'electron-renderer',
 13 |   context: SRC_DIR,
 14 |   entry: {
 15 |     app: ['babel-polyfill', './index.js'],
 16 |     vendor: [
 17 |       '@material-ui/core',
 18 |     ],
 19 |   },
 20 |   output: {
 21 |     filename: 'js/bundle.js',
 22 |     path: BUILD_DIR,
 23 |   },
 24 |   module: {
 25 |     rules: [
 26 |       {
 27 |         test: /\.(js|jsx)$/,
 28 |         exclude: /node_modules/,
 29 |         use: ['babel-loader?cacheDirectory'],
 30 |       },
 31 |       {
 32 |         test: /\.(s?css)$/,
 33 |         use: ExtractTextPlugin.extract({
 34 |           fallback: 'style-loader',
 35 |           use: [
 36 |             {
 37 |               loader: 'css-loader',
 38 |               options: {
 39 |                 camelCase: true,
 40 |                 sourceMap: true,
 41 |               },
 42 |             },
 43 |             {
 44 |               loader: 'postcss-loader',
 45 |               options: {
 46 |                 config: {
 47 |                   ctx: {
 48 |                     autoprefixer: {
 49 |                       browsers: 'last 2 versions',
 50 |                     },
 51 |                   },
 52 |                 },
 53 |               },
 54 |             },
 55 |             {
 56 |               loader: 'sass-loader',
 57 |             },
 58 |           ],
 59 |         }),
 60 |       },
 61 |     ],
 62 |   },
 63 |   optimization: {
 64 |     splitChunks: {
 65 |       cacheGroups: {
 66 |         vendor: {
 67 |           chunks: 'initial',
 68 |           test: 'vendor',
 69 |           name: 'vendor',
 70 |           enforce: true,
 71 |         },
 72 |       },
 73 |     },
 74 |   },
 75 |   plugins: [
 76 |     // new CleanWebpackPlugin([BUILD_DIR]),
 77 |     new HtmlWebpackPlugin({
 78 |       template: 'public/index.html',
 79 |     }),
 80 |     new ExtractTextPlugin({
 81 |       filename: 'styles/style.css',
 82 |       allChunks: true,
 83 |     }),
 84 |     new CopyWebpackPlugin([
 85 |       {
 86 |         from: 'public/images/**/*',
 87 |         to: 'images/',
 88 |         flatten: true,
 89 |         force: true,
 90 |       },
 91 |       {
 92 |         from: 'public/icons/**/*',
 93 |         to: 'icons/',
 94 |         flatten: true,
 95 |         force: true,
 96 |       },
 97 |     ]),
 98 |   ],
 99 | };
100 | 


--------------------------------------------------------------------------------