├── .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 [![Build Status](https://travis-ci.com/React-Proto/react-proto.svg?branch=master)](https://travis-ci.com/React-Proto/react-proto) [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT) 2 | 3 | 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 |
50 | 51 | Follow 52 | 53 | Watch 54 | 55 | Star 56 |
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 |
107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 |
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 |
12 |
13 |
React Proto
14 | 15 |
16 |
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 | 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 |
147 | 148 | 149 | 164 | 165 | 166 | 179 | 180 | 181 | 182 | Type 183 | 194 | 195 | 196 | 197 |
198 | Required? 199 | 206 |
207 |
208 | 209 | 219 | 220 |
221 |
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 | --------------------------------------------------------------------------------