├── .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 |
9 |
10 | ### Details
11 |
12 |
14 |
15 | ### Expected Behavior
16 |
17 |
18 |
19 | ### Actual Behavior
20 |
21 |
22 |
23 | ### Possible Fix
24 |
25 |
26 |
27 | Additional Info
28 |
29 | ### Your Environment
30 |
31 |
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 |
40 |
41 |
42 |
43 | 1. first...
44 | 2.
45 | 3.
46 | 4.
47 |
48 | ### Stack Trace
49 |
50 |
51 |
52 |
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 |
9 |
10 | ### Why
11 |
12 |
13 |
14 |
15 |
16 | ### Possible Implementation & Open Questions
17 |
18 |
19 |
20 |
21 |
22 | ### Is this something you're interested in working on?
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | ## Description
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | ## How Has This Been Tested?
12 |
13 |
14 |
15 |
16 |
17 | ## Types of changes
18 |
19 |
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 |
29 |
30 |
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 [](https://travis-ci.com/React-Proto/react-proto) [](https://opensource.org/licenses/MIT)
2 |
3 |
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 |
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 |
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 |
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 |
21 |
--------------------------------------------------------------------------------
/docs/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | React Proto
13 |
14 |
15 |
16 |
36 |
37 |
38 |
39 |
React Proto
40 |
React prototyping for designers and developers
41 |
49 |
57 |
58 |
59 |

60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
Quick Prototyping
68 |
69 | Quickly create, drag, and resize components to create a visual representaion of your application
70 |
71 |
72 |
73 |

74 |
75 |
76 |
77 |
78 |
79 |
Define component hierarchy
80 |
81 | Define parent and child components along with props and state
82 |
83 |
84 |
85 |
86 |

87 |
88 |
89 |
90 |
91 |
92 |
Export Files
93 |
94 | Inject files into an existing project, start a new project using create-react-app, or clone your favorite github boilerplate
95 |
96 |
97 |
98 |
99 |

100 |
101 |
102 |
103 |
104 |
105 | Download React Proto
106 |
117 |
118 |
119 |
120 | Meet Our Team
121 |
122 |
123 |
124 |
125 |
126 |
Blessing Ebowe
127 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
Erik Guntner
146 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
Brian Taylor
165 |
177 |
178 |
179 |
180 |
181 |
182 |
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 (https://www.linkedin.com/in/blessingebowe/)",
69 | "Brian Taylor (https://www.linkedin.com/in/brianwtaylor/)",
70 | "Erik Guntner (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)$": "identity-obj-proxy",
76 | "electron": "/__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 |
11 |
17 |
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 |
7 |
Press {command} to upload an image
8 |
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 | {
69 | this.stage = node;
70 | }}
71 | onMouseDown={this.handleStageMouseDown}
72 | width={window.innerWidth}
73 | height={window.innerHeight}
74 | >
75 |
76 | {
80 | this.group = node;
81 | }}
82 | draggable={draggable}>
83 |
84 | {components.map((comp, i) => )}
97 |
101 |
102 |
103 |
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 |
123 |
onExpansionPanelChange(component)}
127 | elevation={4}
128 | >
129 | }>
130 | {title}
131 |
132 |
133 |
134 | Stateful?
135 | updateComponent({ stateful: event.target.checked, index, id })}
138 | value='stateful'
139 | color='primary'
140 | id='stateful'
141 | />
142 |
143 |
144 | Box Color
145 | updateComponent({ color: event.target.value, index, id })}
151 | />
152 |
153 |
154 |
selectedParents
155 |
}
164 | renderValue={selectedP => (
165 |
166 | {selectedP.map(parent => (
167 | handleParentChange(null, parent.id)}
172 | deleteIcon={}
173 | />
174 | ))}
175 |
176 | )}
177 | >
178 | {selectableParents.map(parentObj => (
179 |
182 | ))}
183 |
184 |
185 |
186 |
187 |
188 |
189 | moveToTop(id)}
192 | aria-label='Flip to back'>
193 |
194 |
195 |
196 |
197 | moveToBottom(id)}
200 | aria-label='Flip to back'>
201 |
202 |
203 |
204 | {
207 | deleteComponent({
208 | index, id, parentIds,
209 | });
210 | }}
211 | aria-label='Delete'>
212 |
213 |
214 |
215 |
216 |
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 |
92 |
93 |
94 |
95 |
98 |
99 |
100 |
101 |
102 |
105 |
106 |
107 |
108 |
109 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
121 |
122 |
123 |
124 |
125 |
128 |
129 |
130 |
131 |
132 |
135 |
136 |
137 |
138 |
143 |
144 |
145 |
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 | ,
80 | ...Object.keys(availablePropTypes).map(type => (
81 |
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 {
143 | Object.keys(focusComponent).length < 1
144 | ?
Click a component to view its props.
145 | :
146 |
222 |
223 | {
224 | focusComponent.props.map(({
225 | id, type, key, value, required,
226 | }, index) => (
227 | {availablePropTypes[type]}}
230 | label={`${key}: ${value}`}
231 | onDelete={() => deleteProp({ id, index })}
232 | className={classes.chip}
233 | elevation={6}
234 | color={required ? 'secondary' : 'primary'}
235 | deleteIcon={}
236 | />
237 | ))
238 | }
239 |
240 |
241 | }
242 |
;
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 | 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 |
85 |
90 |
95 |
105 | Props
106 | : 'Props'
107 | }
108 | />
109 |
110 | {value === 0 &&
}
111 | {value === 1 &&
117 | }
118 |
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 |
43 |
49 |
54 |
close
65 |
66 | {message}
67 |
68 |
69 | {children}
70 |
71 |
72 | {
73 | secBtnLabel ? : null
76 | }
77 | {
78 | primBtnLabel ? : null
81 | }
82 |
83 |
84 |
85 |
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 |
60 |
61 | {message}
62 |
63 | }
64 | action={[
65 |
72 |
73 | ,
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 = Your files were successfully created.
;
17 | const errMsg = `There was an error while creating your files. ${msg}`;
18 | return (
19 |
20 |
28 |
33 |
34 |
42 |
47 |
48 |
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 |
13 | {}}
18 | />
19 |
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 | {
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();
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 |
58 |
59 |
64 |
71 |
77 | {
78 | loading ?
79 |
: null
80 | }
81 |
82 |
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) => ,
135 | );
136 | // className={classes.root}
137 |
138 | return (
139 |
140 |
146 |
147 |
148 | {
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 |
173 |
174 |
185 |
186 |
187 |
188 |
189 | {componentsExpansionPanel}
190 |
191 |
201 | {modal}
202 |
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 = ;
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 = {genOptions.map(
187 | (option, i) => chooseGenOptions(i)} style={{ border: '1px solid #3f51b5', marginBottom: '2%', marginTop: '5%' }}>
188 |
189 | ,
190 | )}
191 |
;
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 |
228 |
231 |
245 |
246 | {
247 | components.length > 0 || image ? (
248 |
259 | ) :
260 | }
261 |
262 | {modal}
263 |
264 |
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 |
51 |
58 |
65 |
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 |
10 |
11 | ,
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 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | React Proto
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/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 |
24 | ${children.map(child => `<${child.title} ${child.props.map(prop => `${prop.key}={${prop.value}}`).join(' ')}/>`).join('\n')}
25 |
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 |
45 | ${children.map(child => `<${child.title} ${child.props.map(prop => `${prop.key}={${prop.value}}`).join(' ')}/>`).join('\n')}
46 |
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 |
24 | {children}
25 |
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 |
--------------------------------------------------------------------------------