├── .editorconfig ├── .eslintrc ├── .gitattributes ├── .gitignore ├── .tags ├── .travis.yml ├── LICENSE.md ├── README.md ├── app ├── Atomic │ ├── Atoms │ │ ├── button │ │ │ ├── Component.js │ │ │ ├── Editor │ │ │ │ ├── index.js │ │ │ │ └── styles.css │ │ │ ├── default.png │ │ │ ├── index.js │ │ │ ├── preview.png │ │ │ └── styles.css │ │ ├── image │ │ │ ├── Component.js │ │ │ ├── Editor │ │ │ │ ├── index.js │ │ │ │ └── styles.css │ │ │ ├── default.png │ │ │ ├── index.js │ │ │ ├── preview.png │ │ │ └── styles.css │ │ ├── index.js │ │ ├── text │ │ │ ├── Component.js │ │ │ ├── Editor │ │ │ │ └── index.js │ │ │ ├── images.png │ │ │ ├── index.js │ │ │ ├── preview.png │ │ │ ├── styles.css │ │ │ └── typo.css │ │ └── video │ │ │ ├── Component.js │ │ │ ├── Editor │ │ │ ├── index.js │ │ │ └── styles.css │ │ │ ├── images.png │ │ │ ├── index.js │ │ │ ├── preview.png │ │ │ └── styles.css │ ├── Molecules │ │ ├── index.js │ │ └── static │ │ │ ├── Component.js │ │ │ ├── index.js │ │ │ └── styles.css │ ├── Organisms │ │ ├── index.js │ │ ├── type1 │ │ │ ├── Component.js │ │ │ ├── index.js │ │ │ ├── preview.png │ │ │ └── styles.css │ │ ├── type2 │ │ │ ├── Component.js │ │ │ ├── index.js │ │ │ ├── preview.png │ │ │ └── styles.css │ │ ├── type3 │ │ │ ├── Component.js │ │ │ ├── index.js │ │ │ ├── preview.png │ │ │ └── styles.css │ │ ├── type4 │ │ │ ├── Component.js │ │ │ ├── index.js │ │ │ ├── preview.png │ │ │ └── styles.css │ │ ├── type5 │ │ │ ├── Component.js │ │ │ ├── index.js │ │ │ ├── preview.png │ │ │ └── styles.css │ │ ├── type6 │ │ │ ├── Component.js │ │ │ ├── index.js │ │ │ ├── preview.png │ │ │ └── styles.css │ │ └── type7 │ │ │ ├── Component.js │ │ │ ├── index.js │ │ │ ├── preview.png │ │ │ └── styles.css │ └── Templates │ │ ├── index.js │ │ ├── type1 │ │ ├── index.js │ │ ├── preview.jpg │ │ └── state.json │ │ ├── type2 │ │ ├── index.js │ │ ├── preview.jpg │ │ └── state.json │ │ ├── type3 │ │ ├── index.js │ │ ├── preview.png │ │ └── state.json │ │ ├── type4 │ │ ├── index.js │ │ ├── preview.png │ │ └── state.json │ │ ├── type5 │ │ ├── index.js │ │ ├── preview.png │ │ └── state.json │ │ ├── type6 │ │ ├── index.js │ │ ├── preview.png │ │ └── state.json │ │ ├── type7 │ │ ├── index.js │ │ ├── preview.png │ │ └── state.json │ │ └── type8 │ │ ├── index.js │ │ ├── preview.png │ │ └── state.json ├── Editor │ ├── atom.js │ ├── components │ │ ├── Customizer │ │ │ ├── button.js │ │ │ ├── index.js │ │ │ └── styles.css │ │ ├── EditorPreview │ │ │ ├── index.js │ │ │ └── styles.css │ │ ├── EditorSidebar │ │ │ ├── components.js │ │ │ ├── index.js │ │ │ └── styles.css │ │ ├── Eraser │ │ │ ├── index.js │ │ │ └── styles.css │ │ ├── Icon.js │ │ ├── PagePreview │ │ │ ├── index.js │ │ │ └── styles.css │ │ ├── SettingsComponents │ │ │ ├── Align │ │ │ │ ├── index.js │ │ │ │ └── styles.css │ │ │ ├── Border │ │ │ │ ├── index.js │ │ │ │ └── styles.css │ │ │ ├── BoxSpacing │ │ │ │ ├── index.js │ │ │ │ └── styles.css │ │ │ ├── ColorPicker │ │ │ │ ├── index.js │ │ │ │ └── styles.css │ │ │ ├── FileUploader │ │ │ │ ├── index.js │ │ │ │ └── styles.css │ │ │ ├── Font │ │ │ │ ├── index.js │ │ │ │ ├── react-select.css │ │ │ │ └── styles.css │ │ │ ├── Link │ │ │ │ ├── index.js │ │ │ │ └── styles.css │ │ │ ├── SettingsTitle │ │ │ │ ├── index.js │ │ │ │ └── styles.css │ │ │ └── Shadow │ │ │ │ ├── index.js │ │ │ │ └── styles.css │ │ └── Wrappers │ │ │ ├── AtomWrap │ │ │ ├── index.js │ │ │ └── styles.css │ │ │ ├── EditorWrap │ │ │ ├── BlankState.js │ │ │ ├── index.js │ │ │ └── styles.css │ │ │ ├── MoleculeWrap │ │ │ ├── index.js │ │ │ └── styles.css │ │ │ └── OrganismWrap │ │ │ ├── index.js │ │ │ └── styles.css │ ├── creators │ │ ├── createAtom.js │ │ ├── createEditor.js │ │ ├── createMolecule.js │ │ ├── createOrganism.js │ │ ├── createPreview.js │ │ └── createShape.js │ ├── dnd │ │ ├── dragSource.js │ │ ├── dragTarget.js │ │ ├── handler.js │ │ ├── helpers.js │ │ ├── spec.js │ │ └── state.js │ ├── draft │ │ ├── Toolbar │ │ │ ├── Button.js │ │ │ ├── Buttons.js │ │ │ ├── ColorPicker.js │ │ │ ├── LinkInput.js │ │ │ ├── Overlay.js │ │ │ ├── Separator.js │ │ │ ├── actions.js │ │ │ ├── index.js │ │ │ └── styles.css │ │ ├── helpers │ │ │ ├── blockRenderer.js │ │ │ ├── blockStyleFn.js │ │ │ ├── decorator.js │ │ │ ├── findEntity.js │ │ │ ├── getSelectionCoords.js │ │ │ └── renderOptions.js │ │ ├── index.js │ │ └── styles.css │ ├── editorContext.js │ ├── helpers │ │ ├── disableUpdate.js │ │ ├── editorState.js │ │ ├── fileProcessing.js │ │ ├── getStyles.js │ │ ├── imagesConvert.js │ │ ├── onClickOutside.js │ │ └── previewWindow.js │ ├── icons │ │ ├── align_center.svg │ │ ├── align_left.svg │ │ ├── align_right.svg │ │ ├── bold.svg │ │ ├── chevron_left.svg │ │ ├── color.svg │ │ ├── cross.svg │ │ ├── font.svg │ │ ├── h1.svg │ │ ├── h2.svg │ │ ├── h3.svg │ │ ├── h4.svg │ │ ├── italic.svg │ │ ├── letterSpacing.svg │ │ ├── line-through.svg │ │ ├── lineHeight.svg │ │ ├── link.svg │ │ ├── lowercase.svg │ │ ├── ol.svg │ │ ├── quote.svg │ │ ├── size.svg │ │ ├── ul.svg │ │ ├── underline.svg │ │ └── uppercase.svg │ ├── immutable │ │ ├── animation.js │ │ ├── dndMonitor.js │ │ └── editingItem.js │ ├── molecule.js │ └── organism.js ├── app.js ├── components │ ├── Menu │ │ ├── Toggler │ │ │ ├── index.js │ │ │ └── styles.css │ │ ├── index.js │ │ └── styles.css │ └── Root │ │ ├── index.js │ │ └── styles.css ├── index.html ├── modules │ └── builder │ │ ├── actions.js │ │ ├── index.js │ │ └── selectors.js ├── reducers.js ├── store.js ├── utils │ ├── asyncInjectors.js │ ├── request.js │ └── tests │ │ ├── asyncInjectors.test.js │ │ └── request.test.js └── widgets.js ├── appveyor.yml ├── docs └── Atomic.png ├── internals ├── config.js ├── generators │ ├── component │ │ ├── es6.js.hbs │ │ ├── index.js │ │ ├── stateless.js.hbs │ │ ├── styles.css.hbs │ │ └── test.js.hbs │ ├── container │ │ ├── actions.js.hbs │ │ ├── actions.test.js.hbs │ │ ├── constants.js.hbs │ │ ├── index.js │ │ ├── index.js.hbs │ │ ├── reducer.js.hbs │ │ ├── reducer.test.js.hbs │ │ ├── sagas.js.hbs │ │ ├── sagas.test.js.hbs │ │ ├── selectors.js.hbs │ │ ├── selectors.test.js.hbs │ │ ├── styles.css.hbs │ │ └── test.js.hbs │ ├── index.js │ ├── route │ │ ├── index.js │ │ ├── route.hbs │ │ └── routeWithReducer.hbs │ └── utils │ │ └── componentExists.js ├── scripts │ ├── analyze.js │ ├── clean.js │ ├── dependencies.js │ ├── helpers │ │ ├── checkmark.js │ │ └── progress.js │ ├── npmcheckversion.js │ └── pagespeed.js ├── templates │ ├── app.js │ ├── appContainer.js │ ├── asyncInjectors.js │ ├── asyncInjectors.test.js │ ├── homePage.js │ ├── index.html │ ├── notFoundPage.js │ ├── reducers.js │ ├── routes.js │ ├── selectors.js │ ├── selectors.test.js │ ├── store.js │ ├── store.test.js │ └── styles.css ├── testing │ ├── karma.conf.js │ └── test-bundler.js └── webpack │ ├── webpack.base.babel.js │ ├── webpack.dev.babel.js │ ├── webpack.dll.babel.js │ ├── webpack.prod.babel.js │ └── webpack.test.babel.js ├── package.json └── server ├── index.js ├── logger.js └── middlewares └── frontendMiddleware.js /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = false 6 | indent_style = space 7 | indent_size = 2 8 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "airbnb", 3 | "parser": "babel-eslint", 4 | 5 | "env": { 6 | "browser": true, 7 | "node": true, 8 | "mocha": true 9 | }, 10 | 11 | "plugins": [ 12 | "prefer-object-spread" 13 | ], 14 | 15 | "rules": { 16 | "comma-dangle": [1, "never"], 17 | 18 | "space-before-function-paren": [2, { 19 | "anonymous": "always", 20 | "named": "never" 21 | }], 22 | 23 | "no-use-before-define": 0, 24 | "no-else-return": 0, 25 | "no-return-assign": 0, 26 | "react/jsx-key": 1, 27 | "react/jsx-no-literals": 0, 28 | "react/prop-types": 0, 29 | "react/no-direct-mutation-state": 2, 30 | "jsx-a11y/img-uses-alt": 0, 31 | "jsx-a11y/redundant-alt": 0, 32 | "jsx-a11y/valid-aria-role": 0, 33 | "jsx-quotes": [2, "prefer-single"], 34 | "react/jsx-filename-extension": [1, { "extensions": [".js", ".jsx"] }], 35 | "no-void": 0, 36 | "react/require-render-return": 0, 37 | "react/jsx-no-undef": 0, 38 | "react/react-in-jsx-scope": 0, 39 | "react/jsx-closing-bracket-location": 0, 40 | "react/jsx-pascal-case": 0, 41 | "import/no-extraneous-dependencies": 0, 42 | "no-mixed-operators": 0, 43 | "react/jsx-no-bind": [1, { 44 | "ignoreRefs": true 45 | }], 46 | "react/sort-comp": 0, 47 | 48 | "no-underscore-dangle": 0, 49 | 50 | "no-undef": 2, 51 | "import/no-unresolved": 0, 52 | "no-unused-vars": [2, { 53 | "args": "after-used", 54 | "argsIgnorePattern": "^_" 55 | }], 56 | 57 | "jsx-sort-props": 0, 58 | 59 | "id-length": 0, 60 | 61 | "prefer-object-spread/prefer-object-spread": 2 62 | }, 63 | 64 | "settings": { 65 | "import/parser": "babel-eslint", 66 | "import/resolve": { 67 | "moduleDirectory": [ 68 | "node_modules", 69 | "app" 70 | ] 71 | } 72 | }, 73 | } 74 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # From https://github.com/Danimoth/gitattributes/blob/master/Web.gitattributes 2 | 3 | # Handle line endings automatically for files detected as text 4 | # and leave all files detected as binary untouched. 5 | * text=auto 6 | 7 | # 8 | # The above will handle all files NOT found below 9 | # 10 | 11 | # 12 | ## These files are text and should be normalized (Convert crlf => lf) 13 | # 14 | 15 | # source code 16 | *.php text 17 | *.css text 18 | *.sass text 19 | *.scss text 20 | *.less text 21 | *.styl text 22 | *.js text eol=lf 23 | *.coffee text 24 | *.json text 25 | *.htm text 26 | *.html text 27 | *.xml text 28 | *.svg text 29 | *.txt text 30 | *.ini text 31 | *.inc text 32 | *.pl text 33 | *.rb text 34 | *.py text 35 | *.scm text 36 | *.sql text 37 | *.sh text 38 | *.bat text 39 | 40 | # templates 41 | *.ejs text 42 | *.hbt text 43 | *.jade text 44 | *.haml text 45 | *.hbs text 46 | *.dot text 47 | *.tmpl text 48 | *.phtml text 49 | 50 | # server config 51 | .htaccess text 52 | 53 | # git config 54 | .gitattributes text 55 | .gitignore text 56 | .gitconfig text 57 | 58 | # code analysis config 59 | .jshintrc text 60 | .jscsrc text 61 | .jshintignore text 62 | .csslintrc text 63 | 64 | # misc config 65 | *.yaml text 66 | *.yml text 67 | .editorconfig text 68 | 69 | # build config 70 | *.npmignore text 71 | *.bowerrc text 72 | 73 | # Heroku 74 | Procfile text 75 | .slugignore text 76 | 77 | # Documentation 78 | *.md text 79 | LICENSE text 80 | AUTHORS text 81 | 82 | 83 | # 84 | ## These files are binary and should be left untouched 85 | # 86 | 87 | # (binary is a macro for -text -diff) 88 | *.png binary 89 | *.jpg binary 90 | *.jpeg binary 91 | *.gif binary 92 | *.ico binary 93 | *.mov binary 94 | *.mp4 binary 95 | *.mp3 binary 96 | *.flv binary 97 | *.fla binary 98 | *.swf binary 99 | *.gz binary 100 | *.zip binary 101 | *.7z binary 102 | *.ttf binary 103 | *.eot binary 104 | *.woff binary 105 | *.pyc binary 106 | *.pdf binary 107 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Don't check auto-generated stuff into git 2 | coverage 3 | build 4 | node_modules 5 | stats.json 6 | 7 | # Cruft 8 | .DS_Store 9 | npm-debug.log 10 | .idea 11 | .targs* 12 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | sudo: true 3 | dist: trusty 4 | node_js: 5 | - "5.0" 6 | script: npm run build 7 | before_install: 8 | - export CHROME_BIN=/usr/bin/google-chrome 9 | - export DISPLAY=:99.0 10 | - sudo apt-get update 11 | - sudo apt-get install -y libappindicator1 fonts-liberation 12 | - wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb 13 | - sudo dpkg -i google-chrome*.deb 14 | - sh -e /etc/init.d/xvfb start 15 | notifications: 16 | email: 17 | on_failure: change 18 | after_success: 'npm run coveralls' 19 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Uscreen.tv 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | react atomic builder 2 | 3 | ## API 4 | > coming soon... 5 | 6 | ## License 7 | 8 | This project is licensed under the MIT license, Copyright (c) 2016 Uscreen.tv. For more information see `LICENSE.md`. 9 | -------------------------------------------------------------------------------- /app/Atomic/Atoms/button/Component.js: -------------------------------------------------------------------------------- 1 | import cx from 'classnames'; 2 | import Immutable from 'immutable'; 3 | import tinycolor from 'tinycolor2'; 4 | import styles from './styles.css'; 5 | import Editor from './Editor/'; 6 | import getStyles from 'Editor/helpers/getStyles'; 7 | 8 | const Component = ({ settings, content, style }) => { 9 | const { url, target } = settings.link; 10 | const { backgroundColor } = style; 11 | let convertedColor; 12 | 13 | if (backgroundColor && backgroundColor !== 'transparent') { 14 | convertedColor = tinycolor(backgroundColor).isDark() 15 | ? tinycolor(backgroundColor).brighten().toString() 16 | : tinycolor(backgroundColor).darken().toString(); 17 | } else { 18 | convertedColor = 'transparent'; 19 | } 20 | 21 | return ( 22 |
23 |
24 | 30 | { content } 31 | 34 | 35 |
36 |
37 | ); 38 | }; 39 | 40 | export default ({ 41 | content, 42 | active, 43 | deactivate, 44 | settings, 45 | onChange, 46 | updateSettings 47 | }) => { 48 | const style = getStyles(settings); 49 | return ( 50 |
51 | { 52 | !active 53 | ? 54 | : 55 | } 56 |
57 | ); 58 | }; 59 | -------------------------------------------------------------------------------- /app/Atomic/Atoms/button/Editor/index.js: -------------------------------------------------------------------------------- 1 | import onClickOutside from 'Editor/helpers/onClickOutside'; 2 | import cx from 'classnames'; 3 | import Input from 'react-input-autosize'; 4 | import { compose, withHandlers } from 'recompose'; 5 | 6 | import styles from '../styles.css'; 7 | 8 | const focus = (r) => r && r.refs.input.focus(); 9 | 10 | const Editor = compose( 11 | withHandlers({ 12 | onChange: props => e => props.onChange(e.target.value) 13 | }) 14 | )(({ 15 | content, 16 | settings, 17 | onChange, 18 | style 19 | }) => ( 20 |
21 | 25 | 32 | 33 |
34 | )); 35 | 36 | export default onClickOutside(props => props.deactivate())(Editor); 37 | -------------------------------------------------------------------------------- /app/Atomic/Atoms/button/Editor/styles.css: -------------------------------------------------------------------------------- 1 | .wrap{ 2 | position: relative; 3 | width: 100%; 4 | height: 100%; 5 | } 6 | 7 | .resizable{ 8 | display: inline-block; 9 | position: relative; 10 | &:before{ 11 | content: ' '; 12 | position: absolute; 13 | top: -1px; 14 | right: -1px; 15 | bottom: -1px; 16 | left: -1px; 17 | border: 2px dashed color(#333 a(60%)); 18 | border-radius: 3px; 19 | } 20 | } 21 | 22 | .handle{ 23 | background-color: #333; 24 | border-radius: 50%; 25 | border: 1px solid #333; 26 | } 27 | 28 | 29 | .align{ 30 | position: absolute; 31 | top: 7px; 32 | left: 0; 33 | right: 0; 34 | text-align: center; 35 | } 36 | 37 | .align_left{ 38 | text-align: left; 39 | } 40 | .align_center{ 41 | text-align: center; 42 | } 43 | .align_right{ 44 | text-align: right; 45 | } 46 | -------------------------------------------------------------------------------- /app/Atomic/Atoms/button/default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Uscreen-video/atomic-builder/ca83a73d7b496e29032d5549fd19e09ee2fdb3cc/app/Atomic/Atoms/button/default.png -------------------------------------------------------------------------------- /app/Atomic/Atoms/button/index.js: -------------------------------------------------------------------------------- 1 | import atom from 'Editor/atom'; 2 | import { shape as Shape } from 'Editor/creators/createShape'; 3 | 4 | export default atom({ 5 | preview: require('./preview.png'), 6 | component: require('./Component').default, 7 | props: { 8 | type: 'button', 9 | content: 'Your text', 10 | groups: { 11 | spacing: 'Box spacing and alignment', 12 | border: 'Borders and shadows', 13 | colors: 'Colors', 14 | link: 'Add a link:' 15 | }, 16 | settings: { 17 | align: Shape.align('left'), 18 | spacing: Shape.spacing({ 19 | margin: { top: 0, right: 0, bottom: 0, left: 0 }, 20 | padding: { top: 8, right: 16, bottom: 8, left: 16 } 21 | }), 22 | font: Shape.font({ 23 | weight: 400, 24 | size: 16, 25 | style: 'normal', 26 | family: 'Arial', 27 | transform: 'none', 28 | decoration: 'none', 29 | lineHeight: 150, 30 | letterSpacing: 0, 31 | color: '#333' 32 | }), 33 | backgroundImage: Shape.background({ 34 | url: '', 35 | x: 0, 36 | y: 0, 37 | repeat: 'no-repeat', 38 | size: 'auto', 39 | color: '#E6E6E6' 40 | }), 41 | border: Shape.border({ width: 0, style: 'solid', color: '#000', radius: 0 }), 42 | shadow: Shape.shadow({ x: 0, y: 0, blur: 0, spread: 0, color: '#333' }), 43 | link: Shape.link({ url: '', target: '_blank' }, { title: 'Add a link' }) 44 | } 45 | } 46 | }); 47 | -------------------------------------------------------------------------------- /app/Atomic/Atoms/button/preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Uscreen-video/atomic-builder/ca83a73d7b496e29032d5549fd19e09ee2fdb3cc/app/Atomic/Atoms/button/preview.png -------------------------------------------------------------------------------- /app/Atomic/Atoms/button/styles.css: -------------------------------------------------------------------------------- 1 | .button { 2 | position: relative; 3 | font-family: inherit; 4 | font-size: 100%; 5 | line-height: normal; 6 | display: inline-block; 7 | zoom: 1; 8 | box-sizing: border-box; 9 | padding: .5em 1em; 10 | cursor: pointer; 11 | text-align: center; 12 | vertical-align: middle; 13 | white-space: nowrap; 14 | text-decoration: none; 15 | color: #444; 16 | border-radius: 2px; 17 | &__container { 18 | display: inline-block; 19 | } 20 | &__title { 21 | position: relative; 22 | z-index: 1; 23 | } 24 | &:hover { 25 | background-color: transparent; 26 | } 27 | &:hover > &__hoverOverlay { 28 | display: inline-block; 29 | position: absolute; 30 | top: 0; 31 | left: 0; 32 | width: 100%; 33 | height: 100%; 34 | border-radius: inherit; 35 | } 36 | } 37 | 38 | .align_left { 39 | text-align: left; 40 | } 41 | .align_center { 42 | text-align: center; 43 | } 44 | .align_right { 45 | text-align: right; 46 | } 47 | 48 | .input > input { 49 | min-width: 20px; 50 | padding: 0; 51 | border: none; 52 | outline: none; 53 | background: transparent; 54 | } 55 | -------------------------------------------------------------------------------- /app/Atomic/Atoms/image/Component.js: -------------------------------------------------------------------------------- 1 | import cx from 'classnames'; 2 | import styles from './styles.css'; 3 | import Editor from './Editor/'; 4 | import getStyles from 'Editor/helpers/getStyles'; 5 | 6 | const Component = ({ settings, content, style }) => { 7 | const imagesStyles = { 8 | ...getStyles(settings), 9 | width: settings.get('width') || 200 10 | }; 11 | 12 | return ( 13 |
14 | 18 |
19 | ); 20 | }; 21 | 22 | export default ({ 23 | content, 24 | active, 25 | deactivate, 26 | settings, 27 | onChange, 28 | updateSettings 29 | }) => { 30 | const style = getStyles(settings); 31 | return ( 32 |
34 | { 35 | !active 36 | ? 37 | : 38 | } 39 |
40 | ); 41 | }; 42 | -------------------------------------------------------------------------------- /app/Atomic/Atoms/image/Editor/styles.css: -------------------------------------------------------------------------------- 1 | .wrap{ 2 | position: relative; 3 | width: 100%; 4 | height: 100%; 5 | } 6 | 7 | .resizable{ 8 | display: inline-block; 9 | position: relative; 10 | &:before{ 11 | content: ' '; 12 | position: absolute; 13 | top: -1px; 14 | right: -1px; 15 | bottom: -1px; 16 | left: -1px; 17 | border: 2px dashed color(#333 a(60%)); 18 | border-radius: 3px; 19 | } 20 | } 21 | 22 | .handle{ 23 | border-radius: 50%; 24 | border: 2px solid color(#333 a(60%)); 25 | background-color: white; 26 | } 27 | 28 | .dropzone{ 29 | width: 100%; 30 | position: relative; 31 | display: inline-block; 32 | line-height: 1; 33 | img{ 34 | filter: blur(5px); 35 | width: 100%; 36 | max-width: none; 37 | } 38 | } 39 | 40 | .dropzoneHint{ 41 | position: absolute; 42 | top: 0; 43 | bottom: 0; 44 | left: 0; 45 | right: 0; 46 | padding: 20px; 47 | display: flex; 48 | align-items: center; 49 | text-align: center; 50 | color: white; 51 | background-color: color(#5DB2DF a(50%)); 52 | text-shadow: 1px 1px 1px color(#333 a(40%)); 53 | cursor: pointer; 54 | line-height: 1.3; 55 | } 56 | 57 | .align_left{ 58 | text-align: left; 59 | } 60 | .align_center{ 61 | text-align: center; 62 | } 63 | .align_right{ 64 | text-align: right; 65 | } 66 | -------------------------------------------------------------------------------- /app/Atomic/Atoms/image/default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Uscreen-video/atomic-builder/ca83a73d7b496e29032d5549fd19e09ee2fdb3cc/app/Atomic/Atoms/image/default.png -------------------------------------------------------------------------------- /app/Atomic/Atoms/image/index.js: -------------------------------------------------------------------------------- 1 | import atom from 'Editor/atom'; 2 | import { shape as Shape } from 'Editor/creators/createShape'; 3 | 4 | export default atom({ 5 | preview: require('./preview.png'), 6 | component: require('./Component').default, 7 | props: { 8 | type: 'image', 9 | content: require('./default.png'), 10 | settings: { 11 | align: Shape.align(), 12 | spacing: Shape.spacing({ 13 | margin: { top: 0, right: 0, bottom: 0, left: 0 }, 14 | padding: { top: 0, right: 0, bottom: 0, left: 0 }, 15 | }), 16 | border: Shape.border({ width: 0, style: 'solid', color: '#000', radius: 0 }), 17 | shadow: Shape.shadow({ x: 0, y: 0, blur: 0, spread: 0, color: '#333' }) 18 | } 19 | } 20 | }); 21 | -------------------------------------------------------------------------------- /app/Atomic/Atoms/image/preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Uscreen-video/atomic-builder/ca83a73d7b496e29032d5549fd19e09ee2fdb3cc/app/Atomic/Atoms/image/preview.png -------------------------------------------------------------------------------- /app/Atomic/Atoms/image/styles.css: -------------------------------------------------------------------------------- 1 | .wrap{ 2 | img{ 3 | width: 100%; 4 | max-width: 100%; 5 | } 6 | } 7 | 8 | .align_left{ 9 | text-align: left; 10 | } 11 | .align_center{ 12 | text-align: center; 13 | } 14 | .align_right{ 15 | text-align: right; 16 | } 17 | -------------------------------------------------------------------------------- /app/Atomic/Atoms/index.js: -------------------------------------------------------------------------------- 1 | export text from './text/'; 2 | export image from './image/'; 3 | export video from './video/'; 4 | export button from './button/'; 5 | -------------------------------------------------------------------------------- /app/Atomic/Atoms/text/Component.js: -------------------------------------------------------------------------------- 1 | import cx from 'classnames'; 2 | import { convertToHTML } from 'draft-convert'; 3 | import { convertFromRaw } from 'draft-js'; 4 | import Editor from './Editor'; 5 | import renderOptions from 'Editor/draft/helpers/renderOptions'; 6 | import getStyles from 'Editor/helpers/getStyles'; 7 | import { isObject } from 'lodash'; 8 | import styles from './styles.css'; 9 | import typo from './typo.css'; 10 | 11 | const toHTML = convertToHTML(renderOptions); 12 | 13 | const Content = ({ content }) => { 14 | if (!content) return

Enter text here

; 15 | if (isObject(content)) { 16 | const __html = toHTML(convertFromRaw(content.toJS ? content.toJS() : content)); 17 | return
; 18 | } 19 | return
; 20 | }; 21 | 22 | export default ({ 23 | content, 24 | active, 25 | deactivate, 26 | settings, 27 | onChange 28 | }) => ( 29 |
30 | { 31 | !active 32 | ?
33 | : 34 | } 35 |
36 | ); 37 | -------------------------------------------------------------------------------- /app/Atomic/Atoms/text/Editor/index.js: -------------------------------------------------------------------------------- 1 | import onClickOutside from 'Editor/helpers/onClickOutside'; 2 | import Draft from 'Editor/draft'; 3 | 4 | export default onClickOutside(props => props.deactivate())(Draft); 5 | -------------------------------------------------------------------------------- /app/Atomic/Atoms/text/images.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Uscreen-video/atomic-builder/ca83a73d7b496e29032d5549fd19e09ee2fdb3cc/app/Atomic/Atoms/text/images.png -------------------------------------------------------------------------------- /app/Atomic/Atoms/text/index.js: -------------------------------------------------------------------------------- 1 | import atom from 'Editor/atom'; 2 | import { shape as Shape } from 'Editor/creators/createShape'; 3 | 4 | export default atom({ 5 | preview: require('./preview.png'), 6 | component: require('./Component').default, 7 | props: { 8 | type: 'text', 9 | content: '

We\'re here to put a dent in the universe. Otherwise why else even be here?

Each type of visual aid has pros and cons that must be evaluated to ensure it will be beneficial to the overall presentation. Before incorporating visual aids into speeches, the speaker should understand that if used incorrectly, the visual will not be an aid, but a distraction. Planning ahead is important when using visual aids.

', 10 | settings: { 11 | font: Shape.font({ 12 | weight: 400, 13 | size: 16, 14 | style: 'normal', 15 | family: 'Arial', 16 | transform: 'none', 17 | decoration: 'none', 18 | lineHeight: 150, 19 | letterSpacing: 0, 20 | color: '' 21 | }), 22 | backgroundImage: Shape.background({ 23 | url: '', 24 | x: 0, 25 | y: 0, 26 | repeat: 'no-repeat', 27 | size: 'auto', 28 | color: 'transparent' 29 | }), 30 | spacing: Shape.spacing({ 31 | margin: { top: 0, right: 0, bottom: 0, left: 0 }, 32 | padding: { top: 0, right: 0, bottom: 0, left: 0 } 33 | }), 34 | border: Shape.border({ width: 0, style: 'solid', color: '#000', radius: 0 }), 35 | shadow: Shape.shadow({ x: 0, y: 0, blur: 0, spread: 0, color: '#333' }) 36 | } 37 | } 38 | }); 39 | -------------------------------------------------------------------------------- /app/Atomic/Atoms/text/preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Uscreen-video/atomic-builder/ca83a73d7b496e29032d5549fd19e09ee2fdb3cc/app/Atomic/Atoms/text/preview.png -------------------------------------------------------------------------------- /app/Atomic/Atoms/text/styles.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Uscreen-video/atomic-builder/ca83a73d7b496e29032d5549fd19e09ee2fdb3cc/app/Atomic/Atoms/text/styles.css -------------------------------------------------------------------------------- /app/Atomic/Atoms/video/Component.js: -------------------------------------------------------------------------------- 1 | import Player from 'react-player'; 2 | import cx from 'classnames'; 3 | 4 | import Editor from './Editor/'; 5 | import styles from './styles.css'; 6 | 7 | 8 | const Content = ({ settings, content }) => ( 9 |
14 | 18 |
19 | ); 20 | 21 | export default ({ 22 | content, 23 | active, 24 | deactivate, 25 | updateSettings, 26 | settings, 27 | onChange 28 | }) => ( 29 |
30 | { 31 | !active 32 | ? 33 | : 34 | } 35 |
36 | ); 37 | -------------------------------------------------------------------------------- /app/Atomic/Atoms/video/Editor/styles.css: -------------------------------------------------------------------------------- 1 | .wrap{ 2 | position: relative; 3 | width: 100%; 4 | height: 100%; 5 | } 6 | 7 | .resizable{ 8 | display: inline-block; 9 | position: relative; 10 | &:before{ 11 | content: ' '; 12 | position: absolute; 13 | top: -1px; 14 | right: -1px; 15 | bottom: -1px; 16 | left: -1px; 17 | border: 2px dashed color(#333 a(60%)); 18 | border-radius: 3px; 19 | } 20 | } 21 | 22 | .handle{ 23 | background-color: #333; 24 | border-radius: 50%; 25 | border: 1px solid #333; 26 | } 27 | 28 | .align_left{ 29 | text-align: left; 30 | } 31 | .align_center{ 32 | text-align: center; 33 | } 34 | .align_right{ 35 | text-align: right; 36 | } 37 | 38 | .dropzone{ 39 | position: relative; 40 | display: inline-block; 41 | line-height: 1; 42 | img{ 43 | filter: blur(5px); 44 | width: 100%; 45 | max-height: 100%; 46 | } 47 | } 48 | 49 | .dropzoneHint{ 50 | position: absolute; 51 | top: 0; 52 | bottom: 0; 53 | left: 0; 54 | right: 0; 55 | padding: 20px; 56 | display: flex; 57 | align-items: center; 58 | text-align: center; 59 | color: white; 60 | background-color: color(#5DB2DF a(50%)); 61 | text-shadow: 1px 1px 1px color(#333 a(40%)); 62 | cursor: pointer; 63 | line-height: 1.3; 64 | } 65 | -------------------------------------------------------------------------------- /app/Atomic/Atoms/video/images.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Uscreen-video/atomic-builder/ca83a73d7b496e29032d5549fd19e09ee2fdb3cc/app/Atomic/Atoms/video/images.png -------------------------------------------------------------------------------- /app/Atomic/Atoms/video/index.js: -------------------------------------------------------------------------------- 1 | import atom from 'Editor/atom'; 2 | import { shape as Shape } from 'Editor/creators/createShape'; 3 | 4 | export default atom({ 5 | preview: require('./preview.png'), 6 | component: require('./Component').default, 7 | props: { 8 | type: 'video', 9 | content: 'http://www.w3schools.com/html/mov_bbb.mp4', 10 | settings: { 11 | align: Shape.align(), 12 | spacing: Shape.spacing({ 13 | margin: { top: 0, right: 0, bottom: 0, left: 0 }, 14 | padding: { top: 0, right: 0, bottom: 0, left: 0 } 15 | }) 16 | } 17 | } 18 | }); 19 | -------------------------------------------------------------------------------- /app/Atomic/Atoms/video/preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Uscreen-video/atomic-builder/ca83a73d7b496e29032d5549fd19e09ee2fdb3cc/app/Atomic/Atoms/video/preview.png -------------------------------------------------------------------------------- /app/Atomic/Atoms/video/styles.css: -------------------------------------------------------------------------------- 1 | .wrap{ 2 | video{ 3 | max-width: 100%; 4 | } 5 | } 6 | 7 | .videoWrap{ 8 | & > div { 9 | display: inline-block; 10 | } 11 | } 12 | 13 | .align_left{ 14 | text-align: left; 15 | } 16 | .align_center{ 17 | text-align: center; 18 | } 19 | .align_right{ 20 | text-align: right; 21 | } 22 | -------------------------------------------------------------------------------- /app/Atomic/Molecules/index.js: -------------------------------------------------------------------------------- 1 | export static from './static/'; 2 | -------------------------------------------------------------------------------- /app/Atomic/Molecules/static/Component.js: -------------------------------------------------------------------------------- 1 | import { defaultProps } from 'recompose'; 2 | import getStyles from 'Editor/helpers/getStyles'; 3 | import styles from './styles.css'; 4 | 5 | export default defaultProps({ 6 | theme: styles 7 | })(({ 8 | theme, 9 | children, 10 | settings 11 | }) => ( 12 |
15 |
16 | {children} 17 |
18 |
19 | )); 20 | -------------------------------------------------------------------------------- /app/Atomic/Molecules/static/index.js: -------------------------------------------------------------------------------- 1 | import molecule from 'Editor/molecule'; 2 | import styles from './styles.css'; 3 | import { shape as Shape } from 'Editor/creators/createShape'; 4 | 5 | export default molecule({ 6 | component: require('./Component').default, 7 | props: { 8 | type: 'static', 9 | settings: { 10 | backgroundImage: Shape.background({ 11 | url: '', 12 | x: 0, 13 | y: 0, 14 | repeat: 'no-repeat', 15 | size: 'auto', 16 | color: 'transparent' 17 | }), 18 | spacing: Shape.spacing({ 19 | margin: { top: 0, right: 0, bottom: 0, left: 0 }, 20 | padding: { top: 0, right: 0, bottom: 0, left: 0 } 21 | }) 22 | } 23 | } 24 | }); 25 | -------------------------------------------------------------------------------- /app/Atomic/Molecules/static/styles.css: -------------------------------------------------------------------------------- 1 | .wrap{ 2 | } 3 | -------------------------------------------------------------------------------- /app/Atomic/Organisms/index.js: -------------------------------------------------------------------------------- 1 | export type1 from './type1/'; 2 | export type2 from './type2/'; 3 | export type5 from './type5/'; 4 | export type3 from './type3/'; 5 | export type4 from './type4/'; 6 | export type6 from './type6/'; 7 | export type7 from './type7/'; 8 | -------------------------------------------------------------------------------- /app/Atomic/Organisms/type1/Component.js: -------------------------------------------------------------------------------- 1 | import getStyles from 'Editor/helpers/getStyles'; 2 | import styles from './styles.css'; 3 | 4 | export default ({ 5 | molecules, 6 | settings 7 | }) => ( 8 |
11 |
12 | {molecules.get('Main')} 13 |
14 |
15 | ); 16 | -------------------------------------------------------------------------------- /app/Atomic/Organisms/type1/index.js: -------------------------------------------------------------------------------- 1 | import organism from 'Editor/organism'; 2 | import { shape as Shape } from 'Editor/creators/createShape'; 3 | import styles from './styles.css'; 4 | 5 | export default organism({ 6 | preview: require('./preview.png'), 7 | component: require('./Component').default, 8 | props: { 9 | type: 'type1', 10 | molecules: { 11 | Main: { 12 | type: 'static', 13 | theme: { 14 | wrap: styles.moleculeWrap 15 | } 16 | } 17 | }, 18 | settings: { 19 | backgroundImage: Shape.background({ 20 | url: '', 21 | x: 0, 22 | y: 0, 23 | repeat: 'no-repeat', 24 | size: 'auto', 25 | color: '#fff' 26 | }), 27 | spacing: Shape.spacing({ 28 | margin: { top: 0, right: 0, bottom: 0, left: 0 }, 29 | padding: { top: 50, right: 10, bottom: 50, left: 10 } 30 | }) 31 | } 32 | } 33 | }); 34 | -------------------------------------------------------------------------------- /app/Atomic/Organisms/type1/preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Uscreen-video/atomic-builder/ca83a73d7b496e29032d5549fd19e09ee2fdb3cc/app/Atomic/Organisms/type1/preview.png -------------------------------------------------------------------------------- /app/Atomic/Organisms/type1/styles.css: -------------------------------------------------------------------------------- 1 | .wrap{ 2 | width: 100%; 3 | background-color: #EDEDED; 4 | } 5 | 6 | .container{ 7 | lost-center: 1000px; 8 | } 9 | 10 | .moleculeWrap{ 11 | } 12 | -------------------------------------------------------------------------------- /app/Atomic/Organisms/type2/Component.js: -------------------------------------------------------------------------------- 1 | import getStyles from 'Editor/helpers/getStyles'; 2 | import styles from './styles.css'; 3 | 4 | export default ({ 5 | settings, 6 | molecules 7 | }) => { 8 | const { leftImage, leftImageSize, ...style } = getStyles(settings); 9 | return ( 10 |
13 |
14 |
15 |
16 |
17 | {molecules.get('Main')} 18 |
19 |
20 |
21 |
22 | ); 23 | } 24 | -------------------------------------------------------------------------------- /app/Atomic/Organisms/type2/index.js: -------------------------------------------------------------------------------- 1 | import organism from 'Editor/organism'; 2 | import { shape as Shape } from 'Editor/creators/createShape'; 3 | 4 | export default organism({ 5 | preview: require('./preview.png'), 6 | component: require('./Component').default, 7 | props: { 8 | type: 'type2', 9 | molecules: { 10 | Main: { 11 | type: 'static' 12 | } 13 | }, 14 | settings: { 15 | leftImage:Shape.background({ 16 | url: '', 17 | x: 0, 18 | y: 0, 19 | repeat: 'no-repeat', 20 | size: 'auto', 21 | color: '#fff' 22 | }, { title: 'Main image:' }), 23 | backgroundImage: Shape.background({ 24 | url: '', 25 | x: 0, 26 | y: 0, 27 | repeat: 'no-repeat', 28 | size: 'auto', 29 | color: '#fff' 30 | }), 31 | spacing: Shape.spacing({ 32 | margin: { top: 0, right: 0, bottom: 0, left: 0 }, 33 | padding: { top: 0, right: 0, bottom: 0, left: 0 } 34 | }) 35 | } 36 | } 37 | }); 38 | -------------------------------------------------------------------------------- /app/Atomic/Organisms/type2/preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Uscreen-video/atomic-builder/ca83a73d7b496e29032d5549fd19e09ee2fdb3cc/app/Atomic/Organisms/type2/preview.png -------------------------------------------------------------------------------- /app/Atomic/Organisms/type2/styles.css: -------------------------------------------------------------------------------- 1 | .wrap{ 2 | width: 100%; 3 | background-color: #EDEDED; 4 | position: relative; 5 | } 6 | 7 | .container{ 8 | lost-center: 1000px; 9 | } 10 | 11 | .leftSide{ 12 | background-color: white; 13 | position: absolute; 14 | left: 0; 15 | top: 0; 16 | bottom: 0; 17 | width: 50%; 18 | } 19 | 20 | .rightSide{ 21 | lost-column: 1/2 2 30px flex; 22 | } 23 | 24 | .rightSideComtainer{ 25 | padding: 50px 0; 26 | } 27 | 28 | .moleculeWrap{ 29 | } 30 | -------------------------------------------------------------------------------- /app/Atomic/Organisms/type3/Component.js: -------------------------------------------------------------------------------- 1 | import getStyles from 'Editor/helpers/getStyles'; 2 | import styles from './styles.css'; 3 | 4 | export default ({ 5 | settings, 6 | molecules 7 | }) => ( 8 |
11 |
12 |
13 |
14 | {molecules.get('Main')} 15 |
16 |
17 |
18 |
19 | {molecules.get('Second')} 20 |
21 |
22 |
23 |
24 | {molecules.get('Third')} 25 |
26 |
27 |
28 |
29 | {molecules.get('Forth')} 30 |
31 |
32 |
33 |
34 | ); 35 | -------------------------------------------------------------------------------- /app/Atomic/Organisms/type3/index.js: -------------------------------------------------------------------------------- 1 | import organism from 'Editor/organism'; 2 | import { shape as Shape } from 'Editor/creators/createShape'; 3 | 4 | export default organism({ 5 | preview: require('./preview.png'), 6 | component: require('./Component').default, 7 | props: { 8 | type: 'type3', 9 | molecules: { 10 | Main: { 11 | type: 'static' 12 | }, 13 | Second: { 14 | type: 'static' 15 | }, 16 | Third: { 17 | type: 'static' 18 | }, 19 | Forth: { 20 | type: 'static' 21 | } 22 | }, 23 | settings: { 24 | backgroundImage: Shape.background({ 25 | url: '', 26 | x: 0, 27 | y: 0, 28 | repeat: 'no-repeat', 29 | size: 'auto', 30 | color: '#fff' 31 | }), 32 | spacing: Shape.spacing({ 33 | margin: { top: 0, right: 0, bottom: 0, left: 0 }, 34 | padding: { top: 0, right: 0, bottom: 0, left: 0 } 35 | }) 36 | } 37 | } 38 | }); 39 | -------------------------------------------------------------------------------- /app/Atomic/Organisms/type3/preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Uscreen-video/atomic-builder/ca83a73d7b496e29032d5549fd19e09ee2fdb3cc/app/Atomic/Organisms/type3/preview.png -------------------------------------------------------------------------------- /app/Atomic/Organisms/type3/styles.css: -------------------------------------------------------------------------------- 1 | .wrap{ 2 | width: 100%; 3 | background-color: #EDEDED; 4 | position: relative; 5 | } 6 | 7 | .container{ 8 | lost-center: 1000px 10px flex; 9 | } 10 | 11 | .leftSide{ 12 | background-color: white; 13 | position: absolute; 14 | left: 0; 15 | top: 0; 16 | bottom: 0; 17 | width: 50%; 18 | } 19 | 20 | .side{ 21 | lost-column: 1/4 4 20px flex; 22 | } 23 | 24 | .rightSide{ 25 | lost-column: 1/2 2 30px flex; 26 | } 27 | 28 | .rightSideContainer{ 29 | padding: 50px 0; 30 | } 31 | 32 | .moleculeWrap{ 33 | min-height: 300px; 34 | } 35 | -------------------------------------------------------------------------------- /app/Atomic/Organisms/type4/Component.js: -------------------------------------------------------------------------------- 1 | import getStyles from 'Editor/helpers/getStyles'; 2 | import styles from './styles.css'; 3 | 4 | export default ({ 5 | settings, 6 | molecules 7 | }) => ( 8 |
11 |
12 |
13 |
14 | {molecules.get('Main')} 15 |
16 |
17 |
18 |
19 | {molecules.get('Second')} 20 |
21 |
22 |
23 |
24 | {molecules.get('Third')} 25 |
26 |
27 |
28 |
29 | ); 30 | -------------------------------------------------------------------------------- /app/Atomic/Organisms/type4/index.js: -------------------------------------------------------------------------------- 1 | import organism from 'Editor/organism'; 2 | import { shape as Shape } from 'Editor/creators/createShape'; 3 | 4 | export default organism({ 5 | preview: require('./preview.png'), 6 | component: require('./Component').default, 7 | props: { 8 | type: 'type4', 9 | molecules: { 10 | Main: { 11 | type: 'static' 12 | }, 13 | Second: { 14 | type: 'static' 15 | }, 16 | Third: { 17 | type: 'static' 18 | } 19 | }, 20 | settings: { 21 | backgroundImage: Shape.background({ 22 | url: '', 23 | x: 0, 24 | y: 0, 25 | repeat: 'no-repeat', 26 | size: 'auto', 27 | color: '#fff' 28 | }), 29 | spacing: Shape.spacing({ 30 | margin: { top: 0, right: 0, bottom: 0, left: 0 }, 31 | padding: { top: 0, right: 0, bottom: 0, left: 0 } 32 | }) 33 | } 34 | } 35 | }); 36 | -------------------------------------------------------------------------------- /app/Atomic/Organisms/type4/preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Uscreen-video/atomic-builder/ca83a73d7b496e29032d5549fd19e09ee2fdb3cc/app/Atomic/Organisms/type4/preview.png -------------------------------------------------------------------------------- /app/Atomic/Organisms/type4/styles.css: -------------------------------------------------------------------------------- 1 | .wrap{ 2 | width: 100%; 3 | background-color: #EDEDED; 4 | position: relative; 5 | } 6 | 7 | .container{ 8 | lost-center: 1000px 10px flex; 9 | } 10 | 11 | .leftSide{ 12 | background-color: white; 13 | position: absolute; 14 | left: 0; 15 | top: 0; 16 | bottom: 0; 17 | width: 50%; 18 | } 19 | 20 | .side{ 21 | lost-column: 1/3 3 20px flex; 22 | } 23 | 24 | .rightSide{ 25 | lost-column: 1/3 3 30px flex; 26 | } 27 | 28 | .rightSideContainer{ 29 | padding: 50px 0; 30 | } 31 | 32 | .moleculeWrap{ 33 | } 34 | -------------------------------------------------------------------------------- /app/Atomic/Organisms/type5/Component.js: -------------------------------------------------------------------------------- 1 | import getStyles from 'Editor/helpers/getStyles'; 2 | import styles from './styles.css'; 3 | 4 | export default ({ 5 | settings, 6 | molecules 7 | }) => { 8 | const { leftImage, leftImageSize, ...style } = getStyles(settings); 9 | return ( 10 |
13 |
14 |
15 |
16 | {molecules.get('Main')} 17 |
18 |
19 |
20 |
21 |
22 | ); 23 | }; 24 | 25 | -------------------------------------------------------------------------------- /app/Atomic/Organisms/type5/index.js: -------------------------------------------------------------------------------- 1 | import organism from 'Editor/organism'; 2 | import { shape as Shape } from 'Editor/creators/createShape'; 3 | 4 | export default organism({ 5 | preview: require('./preview.png'), 6 | component: require('./Component').default, 7 | props: { 8 | type: 'type5', 9 | molecules: { 10 | Main: { 11 | type: 'static' 12 | } 13 | }, 14 | settings: { 15 | leftImage: Shape.background({ 16 | url: '', 17 | x: 0, 18 | y: 0, 19 | repeat: 'no-repeat', 20 | size: 'auto', 21 | color: '#fff' 22 | }, { title: 'Main image:' }), 23 | backgroundImage: Shape.background({ 24 | url: '', 25 | x: 0, 26 | y: 0, 27 | repeat: 'no-repeat', 28 | size: 'auto', 29 | color: '#fff' 30 | }), 31 | spacing: Shape.spacing({ 32 | margin: { top: 0, right: 0, bottom: 0, left: 0 }, 33 | padding: { top: 0, right: 0, bottom: 0, left: 0 } 34 | }) 35 | } 36 | } 37 | }); 38 | -------------------------------------------------------------------------------- /app/Atomic/Organisms/type5/preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Uscreen-video/atomic-builder/ca83a73d7b496e29032d5549fd19e09ee2fdb3cc/app/Atomic/Organisms/type5/preview.png -------------------------------------------------------------------------------- /app/Atomic/Organisms/type5/styles.css: -------------------------------------------------------------------------------- 1 | .wrap{ 2 | width: 100%; 3 | background-color: #EDEDED; 4 | position: relative; 5 | } 6 | 7 | .container{ 8 | lost-center: 1000px; 9 | } 10 | 11 | .leftSide{ 12 | background-color: white; 13 | position: absolute; 14 | right: 0; 15 | top: 0; 16 | bottom: 0; 17 | width: 50%; 18 | } 19 | 20 | .rightSide{ 21 | lost-column: 1/2 2 30px flex; 22 | } 23 | 24 | .rightSideComtainer{ 25 | padding: 50px 0; 26 | } 27 | 28 | .moleculeWrap{ 29 | } 30 | -------------------------------------------------------------------------------- /app/Atomic/Organisms/type6/Component.js: -------------------------------------------------------------------------------- 1 | import getStyles from 'Editor/helpers/getStyles'; 2 | import styles from './styles.css'; 3 | 4 | export default ({ 5 | settings, 6 | molecules 7 | }) => ( 8 |
11 |
12 |
13 |
14 | {molecules.get('Main')} 15 |
16 |
17 |
18 |
19 | {molecules.get('Second')} 20 |
21 |
22 |
23 |
24 | ); 25 | -------------------------------------------------------------------------------- /app/Atomic/Organisms/type6/index.js: -------------------------------------------------------------------------------- 1 | import organism from 'Editor/organism'; 2 | import { shape as Shape } from 'Editor/creators/createShape'; 3 | 4 | export default organism({ 5 | preview: require('./preview.png'), 6 | component: require('./Component').default, 7 | props: { 8 | type: 'type6', 9 | molecules: { 10 | Main: { 11 | type: 'static' 12 | }, 13 | Second: { 14 | type: 'static' 15 | }, 16 | }, 17 | settings: { 18 | backgroundImage: Shape.background({ 19 | url: '', 20 | x: 0, 21 | y: 0, 22 | repeat: 'no-repeat', 23 | size: 'auto', 24 | color: '#fff' 25 | }), 26 | spacing: Shape.spacing({ 27 | margin: { top: 0, right: 0, bottom: 0, left: 0 }, 28 | padding: { top: 0, right: 0, bottom: 0, left: 0 } 29 | }) 30 | } 31 | } 32 | }); 33 | -------------------------------------------------------------------------------- /app/Atomic/Organisms/type6/preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Uscreen-video/atomic-builder/ca83a73d7b496e29032d5549fd19e09ee2fdb3cc/app/Atomic/Organisms/type6/preview.png -------------------------------------------------------------------------------- /app/Atomic/Organisms/type6/styles.css: -------------------------------------------------------------------------------- 1 | .wrap{ 2 | width: 100%; 3 | background-color: #EDEDED; 4 | position: relative; 5 | } 6 | 7 | .container{ 8 | lost-center: 1000px 10px flex; 9 | } 10 | 11 | .sideLeft{ 12 | lost-column: 1/3 2 20px flex; 13 | } 14 | 15 | .sideRight{ 16 | lost-column: 2/3 2 30px flex; 17 | } 18 | 19 | .rightSideContainer{ 20 | padding: 50px 0; 21 | } 22 | 23 | .moleculeWrap{ 24 | } 25 | -------------------------------------------------------------------------------- /app/Atomic/Organisms/type7/Component.js: -------------------------------------------------------------------------------- 1 | import getStyles from 'Editor/helpers/getStyles'; 2 | import styles from './styles.css'; 3 | 4 | export default ({ 5 | settings, 6 | molecules 7 | }) => ( 8 |
11 |
12 |
13 |
14 | {molecules.get('Main')} 15 |
16 |
17 |
18 |
19 | {molecules.get('Second')} 20 |
21 |
22 |
23 |
24 | ); 25 | -------------------------------------------------------------------------------- /app/Atomic/Organisms/type7/index.js: -------------------------------------------------------------------------------- 1 | import organism from 'Editor/organism'; 2 | import { shape as Shape } from 'Editor/creators/createShape'; 3 | 4 | export default organism({ 5 | preview: require('./preview.png'), 6 | component: require('./Component').default, 7 | props: { 8 | type: 'type7', 9 | molecules: { 10 | Main: { 11 | type: 'static' 12 | }, 13 | Second: { 14 | type: 'static' 15 | } 16 | }, 17 | settings: { 18 | backgroundImage: Shape.background({ 19 | url: '', 20 | x: 0, 21 | y: 0, 22 | repeat: 'no-repeat', 23 | size: 'auto', 24 | color: '#fff' 25 | }), 26 | spacing: Shape.spacing({ 27 | margin: { top: 0, right: 0, bottom: 0, left: 0 }, 28 | padding: { top: 0, right: 0, bottom: 0, left: 0 } 29 | }) 30 | } 31 | } 32 | }); 33 | -------------------------------------------------------------------------------- /app/Atomic/Organisms/type7/preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Uscreen-video/atomic-builder/ca83a73d7b496e29032d5549fd19e09ee2fdb3cc/app/Atomic/Organisms/type7/preview.png -------------------------------------------------------------------------------- /app/Atomic/Organisms/type7/styles.css: -------------------------------------------------------------------------------- 1 | .wrap{ 2 | width: 100%; 3 | background-color: #EDEDED; 4 | position: relative; 5 | } 6 | 7 | .container{ 8 | lost-center: 1000px 10px flex; 9 | } 10 | 11 | .sideLeft{ 12 | lost-column: 2/3 2 20px flex; 13 | } 14 | 15 | .sideRight{ 16 | lost-column: 1/3 2 30px flex; 17 | } 18 | 19 | .rightSideContainer{ 20 | padding: 50px 0; 21 | } 22 | 23 | .moleculeWrap{ 24 | } 25 | -------------------------------------------------------------------------------- /app/Atomic/Templates/index.js: -------------------------------------------------------------------------------- 1 | export type1 from './type1'; 2 | export type2 from './type2'; 3 | export type3 from './type3'; 4 | export type4 from './type4'; 5 | export type5 from './type5'; 6 | export type6 from './type6'; 7 | export type7 from './type7'; 8 | export type8 from './type8'; 9 | -------------------------------------------------------------------------------- /app/Atomic/Templates/type1/index.js: -------------------------------------------------------------------------------- 1 | import createPreview from 'Editor/creators/createPreview'; 2 | import { fromJS } from 'immutable'; 3 | export default { 4 | Preview: createPreview('organism', { 5 | preview: require('./preview.jpg'), 6 | data: fromJS(require('./state.json')) 7 | }) 8 | }; 9 | -------------------------------------------------------------------------------- /app/Atomic/Templates/type1/preview.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Uscreen-video/atomic-builder/ca83a73d7b496e29032d5549fd19e09ee2fdb3cc/app/Atomic/Templates/type1/preview.jpg -------------------------------------------------------------------------------- /app/Atomic/Templates/type1/state.json: -------------------------------------------------------------------------------- 1 | {"type":"type1","molecules":{"Main":{"type":"static","theme":{"wrap":"moleculeWrap__app-Atomic-Organisms-type1-styles__xpKZq"},"settings":{},"atoms":[{"type":"text","content":{"entityMap":{"0":{"type":"COLOR","mutability":"MUTABLE","data":{"color":"#3F3C3C"}}},"blocks":[{"key":"b91r6","text":"Your dreams. Your Passion.","type":"header-two","depth":0,"inlineStyleRanges":[{"offset":0,"length":26,"style":"BOLD"},{"offset":0,"length":26,"style":"center"}],"entityRanges":[{"offset":0,"length":26,"key":0}],"data":{}}]},"settings":{"colors":{"background":"transparent","color":""},"font":{"weight":"400","size":16,"style":"normal","family":"\"Open Sans\", sans-serif","transform":"none","decoration":"none"},"spacing":{"margin":{"top":0,"right":0,"bottom":0,"left":0},"padding":{"top":50,"right":0,"bottom":0,"left":0}},"border":{"width":0,"style":"solid","color":"#000","radius":0},"shadow":{"x":0,"y":0,"blur":0,"spread":0,"color":"#333"}},"id":"ac4c5ac8-4e8e-45e5-901a-3ac515b1863e"},{"type":"image","content":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAAACCAYAAACuT3kTAAAAG0lEQVQoU2O0t7H5zzAKBk0IMI5GyKCJC7BDAAfBA29eBrRuAAAAAElFTkSuQmCC","settings":{"align":"center","spacing":{"margin":{"top":0,"right":0,"bottom":0,"left":0},"padding":{"top":0,"right":0,"bottom":0,"left":0}}},"id":"05dec26e-5e0f-4557-87b6-3b5ea2cb8ff9"},{"type":"text","content":{"entityMap":{},"blocks":[{"key":"5bev4","text":"It all starts today.","type":"unstyled","depth":0,"inlineStyleRanges":[{"offset":0,"length":20,"style":"center"}],"entityRanges":[],"data":{}}]},"settings":{"colors":{"background":"transparent","color":"#3f3c3c"},"font":{"weight":400,"size":"20","style":"normal","family":"\"Open Sans\", sans-serif","transform":"none","decoration":"none"},"spacing":{"margin":{"top":0,"right":0,"bottom":0,"left":0},"padding":{"top":11,"right":0,"bottom":0,"left":0}},"border":{"width":0,"style":"solid","color":"#000","radius":0},"shadow":{"x":0,"y":0,"blur":0,"spread":0,"color":"#333"}},"id":"48f07361-5a24-4d45-ab46-59497f80a9f6"}]}},"settings":{"colors":{"background":"#fff","color":""},"backgroundImage":{"url":"https://dl.dropboxusercontent.com/u/106914318/atomic/bg1.jpg","x":0,"y":0,"repeat":"no-repeat","size":"cover"},"spacing":{"margin":{"top":0,"right":0,"bottom":0,"left":0},"padding":{"top":50,"right":0,"bottom":0,"left":0}}},"id":"9e83c626-def8-4e8e-900c-d8d9e4d2631d"} -------------------------------------------------------------------------------- /app/Atomic/Templates/type2/index.js: -------------------------------------------------------------------------------- 1 | import createPreview from 'Editor/creators/createPreview'; 2 | import { fromJS } from 'immutable'; 3 | export default { 4 | Preview: createPreview('organism', { 5 | preview: require('./preview.jpg'), 6 | data: fromJS(require('./state.json')) 7 | }) 8 | }; 9 | -------------------------------------------------------------------------------- /app/Atomic/Templates/type2/preview.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Uscreen-video/atomic-builder/ca83a73d7b496e29032d5549fd19e09ee2fdb3cc/app/Atomic/Templates/type2/preview.jpg -------------------------------------------------------------------------------- /app/Atomic/Templates/type3/index.js: -------------------------------------------------------------------------------- 1 | import createPreview from 'Editor/creators/createPreview'; 2 | import { fromJS } from 'immutable'; 3 | export default { 4 | Preview: createPreview('organism', { 5 | preview: require('./preview.png'), 6 | data: fromJS(require('./state.json')) 7 | }) 8 | }; 9 | -------------------------------------------------------------------------------- /app/Atomic/Templates/type3/preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Uscreen-video/atomic-builder/ca83a73d7b496e29032d5549fd19e09ee2fdb3cc/app/Atomic/Templates/type3/preview.png -------------------------------------------------------------------------------- /app/Atomic/Templates/type3/state.json: -------------------------------------------------------------------------------- 1 | {"type":"type1","molecules":{"Main":{"type":"static","theme":{"wrap":"moleculeWrap__app-Atomic-Organisms-type1-styles__xpKZq"},"settings":{},"atoms":[{"type":"text","content":{"entityMap":{"0":{"type":"COLOR","mutability":"MUTABLE","data":{"color":"#39B6B3"}}},"blocks":[{"key":"9isqr","text":"","type":"header-four","depth":0,"inlineStyleRanges":[],"entityRanges":[],"data":{}},{"key":"1d945","text":"Do something to Win Free Gifts!","type":"unstyled","depth":0,"inlineStyleRanges":[{"offset":0,"length":31,"style":"center"}],"entityRanges":[{"offset":16,"length":3,"key":0}],"data":{}}]},"settings":{"colors":{"background":"transparent","color":""},"font":{"weight":400,"size":"60","style":"normal","family":"Tahoma, Geneva, sans-serif","transform":"none","decoration":"none","lineHeight":150,"letterSpacing":0},"spacing":{"margin":{"top":0,"right":0,"bottom":0,"left":0},"padding":{"top":0,"right":0,"bottom":0,"left":0}},"border":{"width":0,"style":"solid","color":"#000","radius":0},"shadow":{"x":0,"y":0,"blur":0,"spread":0,"color":"#333"}},"id":"74e98dc8-71aa-4972-889f-13202832602b"},{"type":"text","content":{"entityMap":{},"blocks":[{"key":"40mbl","text":"","type":"header-four","depth":0,"inlineStyleRanges":[],"entityRanges":[],"data":{}},{"key":"c54j0","text":"You don't have to do specifically","type":"unstyled","depth":0,"inlineStyleRanges":[{"offset":0,"length":33,"style":"center"}],"entityRanges":[],"data":{}}]},"settings":{"colors":{"background":"transparent","color":""},"font":{"weight":"700","size":"28","style":"normal","family":"Verdana, Geneva, sans-serif","transform":"uppercase","decoration":"none","lineHeight":150,"letterSpacing":"-1"},"spacing":{"margin":{"top":-10,"right":0,"bottom":0,"left":0},"padding":{"top":0,"right":0,"bottom":0,"left":0}},"border":{"width":0,"style":"solid","color":"#000","radius":0},"shadow":{"x":0,"y":0,"blur":0,"spread":0,"color":"#333"}},"id":"fccba870-07e8-4199-9a57-68cc879ee017"}]}},"settings":{"colors":{"background":"#fff","color":""},"backgroundImage":{"url":"","x":0,"y":0,"repeat":"no-repeat","size":"auto"},"spacing":{"margin":{"top":0,"right":0,"bottom":0,"left":0},"padding":{"top":0,"right":0,"bottom":0,"left":0}}},"id":"733272f6-a31f-446e-aadd-06ccbaf72edd"} -------------------------------------------------------------------------------- /app/Atomic/Templates/type4/index.js: -------------------------------------------------------------------------------- 1 | import createPreview from 'Editor/creators/createPreview'; 2 | import { fromJS } from 'immutable'; 3 | export default { 4 | Preview: createPreview('organism', { 5 | preview: require('./preview.png'), 6 | data: fromJS(require('./state.json')) 7 | }) 8 | }; 9 | -------------------------------------------------------------------------------- /app/Atomic/Templates/type4/preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Uscreen-video/atomic-builder/ca83a73d7b496e29032d5549fd19e09ee2fdb3cc/app/Atomic/Templates/type4/preview.png -------------------------------------------------------------------------------- /app/Atomic/Templates/type5/index.js: -------------------------------------------------------------------------------- 1 | import createPreview from 'Editor/creators/createPreview'; 2 | import { fromJS } from 'immutable'; 3 | export default { 4 | Preview: createPreview('organism', { 5 | preview: require('./preview.png'), 6 | data: fromJS(require('./state.json')) 7 | }) 8 | }; 9 | -------------------------------------------------------------------------------- /app/Atomic/Templates/type5/preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Uscreen-video/atomic-builder/ca83a73d7b496e29032d5549fd19e09ee2fdb3cc/app/Atomic/Templates/type5/preview.png -------------------------------------------------------------------------------- /app/Atomic/Templates/type6/index.js: -------------------------------------------------------------------------------- 1 | import createPreview from 'Editor/creators/createPreview'; 2 | import { fromJS } from 'immutable'; 3 | export default { 4 | Preview: createPreview('organism', { 5 | preview: require('./preview.png'), 6 | data: fromJS(require('./state.json')) 7 | }) 8 | }; 9 | -------------------------------------------------------------------------------- /app/Atomic/Templates/type6/preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Uscreen-video/atomic-builder/ca83a73d7b496e29032d5549fd19e09ee2fdb3cc/app/Atomic/Templates/type6/preview.png -------------------------------------------------------------------------------- /app/Atomic/Templates/type6/state.json: -------------------------------------------------------------------------------- 1 | {"type":"type1","molecules":{"Main":{"type":"static","theme":{"wrap":"moleculeWrap__app-Atomic-Organisms-type1-styles__xpKZq"},"settings":{"colors":{"color":""}},"atoms":[{"type":"text","content":{"entityMap":{},"blocks":[{"key":"deokp","text":"","type":"header-four","depth":0,"inlineStyleRanges":[],"entityRanges":[],"data":{}},{"key":"5oi6i","text":"Copyright © 2016 Your brand name. All rights Reserved.","type":"unstyled","depth":0,"inlineStyleRanges":[{"offset":0,"length":54,"style":"center"}],"entityRanges":[],"data":{}}]},"settings":{"colors":{"background":"transparent","color":"#ffffff"},"font":{"weight":400,"size":16,"style":"normal","family":"Arial","transform":"none","decoration":"none","lineHeight":150,"letterSpacing":0},"spacing":{"margin":{"top":0,"right":0,"bottom":0,"left":0},"padding":{"top":0,"right":0,"bottom":0,"left":0}},"border":{"width":0,"style":"solid","color":"#000","radius":0},"shadow":{"x":0,"y":0,"blur":0,"spread":0,"color":"#333"}},"id":"85788b73-d199-4394-9f2f-a6f5af31784c"}]}},"settings":{"colors":{"background":"#414d59","color":""},"backgroundImage":{"url":"","x":0,"y":0,"repeat":"no-repeat","size":"auto"},"spacing":{"margin":{"top":0,"right":0,"bottom":0,"left":0},"padding":{"top":20,"right":0,"bottom":20,"left":0}}},"id":"10a6b5a7-5eb0-4ffb-a0a9-76c7d4688243"} -------------------------------------------------------------------------------- /app/Atomic/Templates/type7/index.js: -------------------------------------------------------------------------------- 1 | import createPreview from 'Editor/creators/createPreview'; 2 | import { fromJS } from 'immutable'; 3 | export default { 4 | Preview: createPreview('organism', { 5 | preview: require('./preview.png'), 6 | data: fromJS(require('./state.json')) 7 | }) 8 | }; 9 | -------------------------------------------------------------------------------- /app/Atomic/Templates/type7/preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Uscreen-video/atomic-builder/ca83a73d7b496e29032d5549fd19e09ee2fdb3cc/app/Atomic/Templates/type7/preview.png -------------------------------------------------------------------------------- /app/Atomic/Templates/type8/index.js: -------------------------------------------------------------------------------- 1 | import createPreview from 'Editor/creators/createPreview'; 2 | import { fromJS } from 'immutable'; 3 | export default { 4 | Preview: createPreview('organism', { 5 | preview: require('./preview.png'), 6 | data: fromJS(require('./state.json')) 7 | }) 8 | }; 9 | -------------------------------------------------------------------------------- /app/Atomic/Templates/type8/preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Uscreen-video/atomic-builder/ca83a73d7b496e29032d5549fd19e09ee2fdb3cc/app/Atomic/Templates/type8/preview.png -------------------------------------------------------------------------------- /app/Editor/atom.js: -------------------------------------------------------------------------------- 1 | import createPreview from './creators/createPreview'; 2 | import createAtom from './creators/createAtom'; 3 | 4 | export default data => ({ 5 | Preview: createPreview('atom', data), 6 | Component: createAtom(data) 7 | }); 8 | -------------------------------------------------------------------------------- /app/Editor/components/Customizer/button.js: -------------------------------------------------------------------------------- 1 | import cx from 'classnames'; 2 | import UUID from 'uuid-js'; 3 | import { compose, withHandlers } from 'recompose'; 4 | import editorState from 'Editor/helpers/editorState'; 5 | 6 | import styles from './styles.css'; 7 | 8 | export default compose( 9 | editorState, 10 | withHandlers({ 11 | onClick: props => e => { 12 | e.preventDefault(); 13 | if (props.organism) { 14 | const cursorId = props.Cursor.toJS()[0]; 15 | const templateShapeId = UUID.create().toString(); 16 | const organism = JSON.stringify({ 17 | ...props.organism.toJS(), 18 | id: templateShapeId 19 | }); 20 | e.nativeEvent.which === 3 && localStorage.setItem(cursorId, organism); 21 | } 22 | 23 | e.stopPropagation(); 24 | props.editSettings({ 25 | type: props.title, 26 | mapper: props.settingsMapper, 27 | Cursor: props.Cursor.push('settings') 28 | }); 29 | } 30 | }) 31 | )(({ 32 | editingItem, 33 | className, 34 | title, 35 | onClick 36 | }) => ( 37 |
38 | 47 |
48 | )); 49 | -------------------------------------------------------------------------------- /app/Editor/components/Customizer/index.js: -------------------------------------------------------------------------------- 1 | import { PropTypes } from 'react'; 2 | import cx from 'classnames'; 3 | import { compose, withState, withHandlers, getContext } from 'recompose'; 4 | 5 | import styles from './styles.css'; 6 | import withEditorState from '../../helpers/editorState'; 7 | import Button from './button'; 8 | 9 | export default compose( 10 | withEditorState, 11 | getContext({ 12 | editorDisabled: PropTypes.bool 13 | }), 14 | withState('over', 'setOver', false), 15 | withHandlers({ 16 | mouseOver: props => e => { 17 | e.stopPropagation(); 18 | if (props.editingItem.isContentEditing) return false; 19 | return props.setOver(true); 20 | }, 21 | mouseOut: props => e => { 22 | e.stopPropagation(); 23 | props.setOver(false); 24 | } 25 | }) 26 | )(({ 27 | outside, 28 | children, 29 | preview, 30 | over, 31 | title, 32 | mouseOver, 33 | mouseOut, 34 | editorDisabled, 35 | ...props 36 | }) => { 37 | if (editorDisabled) return
{children}
; 38 | return ( 39 |
43 |
44 | { 45 | preview && preview(
{children}
) || children} 46 |
47 | { 48 | props.editingItem.canEdit && 49 |
50 |
51 |
52 |
62 | } 63 |
64 | ) 65 | } 66 | ); 67 | -------------------------------------------------------------------------------- /app/Editor/components/Customizer/styles.css: -------------------------------------------------------------------------------- 1 | .wrap { 2 | position: relative; 3 | } 4 | 5 | .border { 6 | opacity: 0; 7 | transition: opacity .1s ease-in-out; 8 | } 9 | 10 | .horizontalBorder { 11 | &:before, &:after{ 12 | content: ' '; 13 | position: absolute; 14 | left: 0; 15 | right: 0; 16 | border-top: 2px dashed #39B6B3; 17 | } 18 | &:before { 19 | top: 0; 20 | } 21 | &:after { 22 | bottom: 0; 23 | } 24 | 25 | &_Molecule { 26 | &:before, &:after { 27 | left: -10px; 28 | right: -10px; 29 | } 30 | &:before { 31 | top: -20px; 32 | } 33 | &:after { 34 | bottom: -20px; 35 | } 36 | } 37 | } 38 | 39 | .verticalBorder { 40 | &:before, &:after { 41 | content: ' '; 42 | position: absolute; 43 | top: 0; 44 | bottom: 0; 45 | border-left: 2px dashed #39B6B3; 46 | } 47 | &:before { 48 | left: 0; 49 | } 50 | &:after { 51 | right: 0; 52 | } 53 | 54 | &_Molecule { 55 | &:before, &:after { 56 | top: -10px; 57 | bottom: -10px; 58 | } 59 | &:before { 60 | left: -10px; 61 | } 62 | &:after { 63 | right: -10px; 64 | } 65 | } 66 | } 67 | 68 | .title { 69 | position: absolute; 70 | left: -1px; 71 | top: -1px; 72 | text-transform: uppercase; 73 | font-size: 11px; 74 | padding: 3px; 75 | color: white; 76 | background-color: #39B6B3; 77 | box-shadow: none; 78 | border: none; 79 | transition: width .4s ease-out, background .3s ease-out; 80 | cursor: pointer; 81 | &:focus { 82 | outline: none; 83 | } 84 | &.outside { 85 | top: auto; 86 | bottom: 100%; 87 | margin-bottom: -2px; 88 | } 89 | } 90 | 91 | .titleSettings { 92 | padding: 4px 25px 4px 10px; 93 | & i { 94 | position: absolute; 95 | top: 3px; 96 | right: 6px; 97 | display: block; 98 | width: 13px; 99 | height: 13px; 100 | font-style: normal; 101 | & svg { 102 | width: 100%; 103 | height: 100%; 104 | fill: #fff; 105 | } 106 | } 107 | &_active { 108 | background: color(#39B6B3 l(40%)); 109 | text-align: center; 110 | & i { 111 | display: none; 112 | } 113 | } 114 | } 115 | 116 | .buttonContainer_Molecule { 117 | & .title.outside { 118 | margin: 0 0 18px -9px; 119 | } 120 | } 121 | 122 | .over{ 123 | opacity: 1; 124 | } 125 | 126 | .button_hidden { 127 | display: none; 128 | } 129 | -------------------------------------------------------------------------------- /app/Editor/components/EditorPreview/index.js: -------------------------------------------------------------------------------- 1 | import styles from './styles.css'; 2 | 3 | export default ({ 4 | src, 5 | props: { type } 6 | }) => {type}; 7 | -------------------------------------------------------------------------------- /app/Editor/components/EditorPreview/styles.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Uscreen-video/atomic-builder/ca83a73d7b496e29032d5549fd19e09ee2fdb3cc/app/Editor/components/EditorPreview/styles.css -------------------------------------------------------------------------------- /app/Editor/components/EditorSidebar/components.js: -------------------------------------------------------------------------------- 1 | export spacing from 'Editor/components/SettingsComponents/BoxSpacing'; 2 | export background from 'Editor/components/SettingsComponents/FileUploader'; 3 | export shadow from 'Editor/components/SettingsComponents/Shadow'; 4 | export border from 'Editor/components/SettingsComponents/Border'; 5 | export align from 'Editor/components/SettingsComponents/Align'; 6 | export link from 'Editor/components/SettingsComponents/Link'; 7 | export font from 'Editor/components/SettingsComponents/Font'; 8 | -------------------------------------------------------------------------------- /app/Editor/components/EditorSidebar/index.js: -------------------------------------------------------------------------------- 1 | import { Component, PropTypes } from 'react'; 2 | import { compose, toClass, withHandlers } from 'recompose'; 3 | import cx from 'classnames'; 4 | import withClickHandler from 'react-onclickoutside'; 5 | 6 | import editorState from 'Editor/helpers/editorState'; 7 | import * as components from './components'; 8 | 9 | import Immutable from 'immutable'; 10 | 11 | import styles from './styles.css'; 12 | 13 | const EditorSidebar = compose( 14 | withHandlers({ 15 | setSettings: props => (key, value) => { 16 | const cursor = props.editingItem.get('Cursor').push(key); 17 | props.updateEditorState(cursor, value); 18 | } 19 | }) 20 | )(({ 21 | editingItem, 22 | setSettings, 23 | organisms 24 | }) => { 25 | const mapper = editingItem.get('mapper'); 26 | const cursor = editingItem.get('Cursor'); 27 | return ( 28 |
29 |

Settings for {editingItem.type}

30 |
    31 | { 32 | Object.keys(mapper).map(key => { 33 | let component = null; 34 | const { type, title, value: defaultValue } = mapper[key]; 35 | const settings = organisms.getIn(cursor, 'settings'); 36 | const groups = organisms.getIn(cursor, 'groups'); 37 | const settingValue = settings.get(key); 38 | const value = ( 39 | Immutable.Iterable.isIterable(settingValue) 40 | ? settingValue.toJS() 41 | : settingValue 42 | ) || defaultValue; 43 | const SettingsComponent = components[type]; 44 | if (!SettingsComponent) return null; 45 | return ( 46 |
  • 47 | 52 |
  • 53 | ); 54 | }) 55 | } 56 |
57 |
58 | ); 59 | } 60 | ); 61 | 62 | export default editorState(withClickHandler( 63 | class OutsideClickHandler extends Component { 64 | handleClickOutside() { 65 | if (this.props.editingItem.isSidebarOpen) this.props.releaseItem(); 66 | } 67 | 68 | render() { 69 | return ( 70 | 71 | ); 72 | } 73 | } 74 | )); 75 | -------------------------------------------------------------------------------- /app/Editor/components/EditorSidebar/styles.css: -------------------------------------------------------------------------------- 1 | .sidebar { 2 | position: fixed; 3 | top: 0; 4 | bottom: 0; 5 | left: 0; 6 | width: 350px; 7 | background: #39B6B3; 8 | transform: translateX(-350px); 9 | color: #fff; 10 | font-size: 11px; 11 | z-index: 999; 12 | opacity: 0; 13 | 14 | will-change: transform; 15 | transition: transform .3s ease-in; 16 | overflow-Y: scroll; 17 | 18 | &_active { 19 | display: block; 20 | opacity: 1; 21 | transform: translateX(0); 22 | } 23 | 24 | &__header { 25 | display: block; 26 | text-align: center; 27 | padding: 10px 0; 28 | margin: 0; 29 | background: color(#39B6B3 l(40%)); 30 | font: 700 16px 'Roboto Condensed', sans-serif; 31 | text-transform: uppercase; 32 | } 33 | 34 | ul { 35 | list-style: none; 36 | margin: 0; 37 | padding: 0; 38 | } 39 | li { 40 | display: block; 41 | text-align: center; 42 | padding: 10px 0; 43 | border-top: 1px solid color(#39B6B3 l(60%)); 44 | cursor: pointer; 45 | &:hover { 46 | background: color(#39B6B3 l(60%)); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /app/Editor/components/Eraser/index.js: -------------------------------------------------------------------------------- 1 | import cx from 'classnames'; 2 | import { compose } from 'recompose'; 3 | import editorState from 'Editor/helpers/editorState'; 4 | import dndHandler from 'Editor/dnd/handler'; 5 | import styles from './styles.css'; 6 | 7 | export default compose( 8 | editorState, 9 | dndHandler('eraser') 10 | )(({ 11 | isOver 12 | }) => { 13 | return ( 14 |
18 | 19 | 20 | 21 |
22 | ); 23 | } 24 | ); 25 | -------------------------------------------------------------------------------- /app/Editor/components/Eraser/styles.css: -------------------------------------------------------------------------------- 1 | .eraserWrap { 2 | position: fixed; 3 | bottom: 10px; 4 | right: 10px; 5 | width: 90px; 6 | height: 100px; 7 | background: #39B6B3; 8 | padding: 20px 15px; 9 | box-sizing: border-box; 10 | border-radius: 50px; 11 | transition: background .5s ease-out; 12 | box-shadow: 3px 3px 0 #666 inset; 13 | z-index: 9999; 14 | &_over { 15 | background: #333; 16 | box-shadow: none; 17 | } 18 | } 19 | 20 | .trashIcon { 21 | display: block; 22 | width: 100%; 23 | height: 100%; 24 | animation: shake 0.82s cubic-bezier(.36,.07,.19,.97) infinite; 25 | transform: rotate(0); 26 | backface-visibility: hidden; 27 | perspective: 1000px; 28 | & svg { 29 | width: 100%; 30 | height: 100%; 31 | fill: #fff; 32 | } 33 | } 34 | 35 | @keyframes shake { 36 | 10%, 90% { 37 | transform: rotate(-3deg); 38 | } 39 | 20%, 80% { 40 | transform: rotate(7deg); 41 | } 42 | 30%, 50%, 70% { 43 | transform: rotate(-15deg); 44 | } 45 | 40%, 60% { 46 | transform: rotate(15deg); 47 | } 48 | } -------------------------------------------------------------------------------- /app/Editor/components/Icon.js: -------------------------------------------------------------------------------- 1 | const icons = require.context('!!svg-sprite!../icons', false, /^\.\/.*\.svg$/); 2 | 3 | export function hasIcon(icon) { 4 | return icons.keys().includes(`./${icon}.svg`); 5 | } 6 | 7 | export default ({ value, className, fill, width = 16, height = 16 }) => ( 8 | 9 | 10 | 11 | ); 12 | -------------------------------------------------------------------------------- /app/Editor/components/PagePreview/index.js: -------------------------------------------------------------------------------- 1 | import cx from 'classnames'; 2 | import { compose, withState, withHandlers } from 'recompose'; 3 | import editorState from 'Editor/helpers/editorState'; 4 | import styles from './styles.css'; 5 | 6 | export default compose( 7 | editorState, 8 | withState('preview', 'setPreview', props => !props.editingItem.canEdit), 9 | withHandlers({ 10 | onClick: props => e => { 11 | e.stopPropagation(); 12 | const status = !props.preview; 13 | props.setPreview(status); 14 | if (status) { 15 | props.disableEdit(); 16 | } else { 17 | props.enableEdit(); 18 | } 19 | props.exportEditor(); 20 | } 21 | }) 22 | )(({ 23 | onClick, 24 | preview 25 | }) => { 26 | return ( 27 |
32 | 36 | 37 | 38 |
39 | ); 40 | } 41 | ); 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /app/Editor/components/PagePreview/styles.css: -------------------------------------------------------------------------------- 1 | .previewWrap { 2 | position: fixed; 3 | top: 10px; 4 | right: 10px; 5 | width: 40px; 6 | height: 40px; 7 | background: #39B6B3; 8 | padding: 5px; 9 | box-sizing: border-box; 10 | border-radius: 50px; 11 | transition: background .5s ease-out; 12 | cursor: pointer; 13 | z-index: 9999; 14 | &_over { 15 | background: #333; 16 | box-shadow: none; 17 | } 18 | } 19 | 20 | .previewIcon { 21 | display: block; 22 | width: 100%; 23 | height: 100%; 24 | backface-visibility: visible; 25 | perspective: 1000px; 26 | &_active { 27 | animation: flip 2s cubic-bezier(.36,.07,.19,.97) infinite; 28 | } 29 | & svg { 30 | width: 100%; 31 | height: 100%; 32 | fill: #fff; 33 | } 34 | } 35 | 36 | @keyframes shake { 37 | 10%, 90% { 38 | transform: rotate(-3deg); 39 | } 40 | 20%, 80% { 41 | transform: rotate(7deg); 42 | } 43 | 30%, 50%, 70% { 44 | transform: rotate(-15deg); 45 | } 46 | 40%, 60% { 47 | transform: rotate(15deg); 48 | } 49 | } 50 | 51 | @keyframes flip { 52 | from { 53 | transform: rotate3d(0, 1, 0, -360deg); 54 | } 55 | 56 | 40% { 57 | transform: rotate3d(0, 1, 0, -190deg); 58 | } 59 | 60 | 50% { 61 | transform: rotate3d(0, 1, 0, -170deg); 62 | } 63 | 64 | to { 65 | transform: perspective(400px); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /app/Editor/components/SettingsComponents/Align/index.js: -------------------------------------------------------------------------------- 1 | import { compose, withHandlers, withProps, withState } from 'recompose'; 2 | import cx from 'classnames'; 3 | import Icon from 'Editor/components/Icon'; 4 | import SettingsTitle from '../SettingsTitle'; 5 | 6 | import styles from './styles.css'; 7 | 8 | const Button = ({ align, type, action }) => ( 9 | 12 | ); 13 | 14 | export default compose( 15 | withProps(props => ({ 16 | onChange: dir => props.onSettingsChange(props.settingKey, dir) 17 | })), 18 | withHandlers({ 19 | alighLeft: props => () => props.onChange('left'), 20 | alighRight: props => () => props.onChange('right'), 21 | alighCenter: props => () => props.onChange('center'), 22 | onClick: props => () => props.setActive(!props.active) 23 | }) 24 | )(({ 25 | alighLeft, 26 | alighRight, 27 | alighCenter, 28 | label, 29 | ...rest 30 | }) => ( 31 | 32 |
33 |
37 |
38 | )); 39 | -------------------------------------------------------------------------------- /app/Editor/components/SettingsComponents/Align/styles.css: -------------------------------------------------------------------------------- 1 | .alignButton{ 2 | background-color: color(#181818 a(90%)); 3 | border: 1px solid #181818; 4 | border-radius: 3px; 5 | margin: 3px; 6 | line-height: 40px; 7 | width: 40px; 8 | height: 40px; 9 | padding: 0; 10 | cursor: pointer; 11 | opacity: .2; 12 | outline: none; 13 | transition: opacity .1s ease-in-out; 14 | &:hover{ 15 | opacity: .9; 16 | } 17 | &.active{ 18 | opacity: .9; 19 | } 20 | svg{ 21 | fill: white; 22 | height: 25px; 23 | width: 25px; 24 | display: inline-block; 25 | vertical-align: -6px; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/Editor/components/SettingsComponents/Border/styles.css: -------------------------------------------------------------------------------- 1 | .border { 2 | &__options { 3 | display: flex; 4 | padding: 0 20px; 5 | justify-content: center; 6 | flex-wrap: wrap; 7 | margin-bottom: 5px; 8 | } 9 | &__inputBox { 10 | margin: 0 10px 10px 0; 11 | width: 20%; 12 | & > label { 13 | color: #333; 14 | font-size: 13px; 15 | } 16 | & > input { 17 | width: 100%; 18 | line-height: 20px; 19 | &:focus { 20 | outline: none; 21 | } 22 | } 23 | &_radio { 24 | display: inline-block; 25 | line-height: 30px; 26 | & > input { 27 | width: auto; 28 | } 29 | } 30 | &_number { 31 | width: 15%; 32 | margin-right: 26px; 33 | text-align: center; 34 | box-sizing: border-box; 35 | & > input { 36 | line-height: 30px; 37 | padding: 0 5px; 38 | text-align: center; 39 | } 40 | & > label { 41 | display: inline-block; 42 | font-weight: 700; 43 | margin-bottom: 3px; 44 | } 45 | } 46 | } 47 | &__labelOptions { 48 | display: inline-block; 49 | width: 30px; 50 | height: 30px; 51 | margin-left: 5px; 52 | vertical-align: middle; 53 | color: #333; 54 | border: 1px solid #333; 55 | &_dashed { 56 | border-style: dashed; 57 | } 58 | &_dotted { 59 | border-style: dotted 60 | } 61 | &_none { 62 | border: none; 63 | } 64 | } 65 | &__title { 66 | font-size: 13px; 67 | color: #333; 68 | margin: 0 0 10px; 69 | } 70 | } -------------------------------------------------------------------------------- /app/Editor/components/SettingsComponents/ColorPicker/index.js: -------------------------------------------------------------------------------- 1 | import { compose, withState, withHandlers } from 'recompose'; 2 | import cx from 'classnames'; 3 | import { SketchPicker } from 'react-color'; 4 | 5 | import styles from './styles.css'; 6 | 7 | export default compose( 8 | withState('active', 'setActive', props => (props.transparent && props.color === 'transparent') ? false : true), 9 | withState('color', 'setColor', props => props.color), 10 | withHandlers({ 11 | onClick: props => e => { 12 | e.stopPropagation(); 13 | props.setActive(!props.active); 14 | props.transparent && props.active && props.onColorChange('transparent'); 15 | }, 16 | onColorChange: props => color => { 17 | props.setColor(color); 18 | props.onColorChange(color); 19 | } 20 | }) 21 | )(({ 22 | active, 23 | onClick, 24 | color, 25 | transparent, 26 | onColorChange 27 | }) => ( 28 |
29 | { 30 | transparent && 31 |
32 | 38 | 41 |
42 | } 43 | { 44 | active && 45 |
46 | 50 |
51 | } 52 |
53 | )); 54 | -------------------------------------------------------------------------------- /app/Editor/components/SettingsComponents/ColorPicker/styles.css: -------------------------------------------------------------------------------- 1 | .colorpicker { 2 | &__label { 3 | display: inline-block; 4 | font-size: 13px; 5 | color: #333; 6 | margin-left: 10px; 7 | user-select: none 8 | } 9 | &__component { 10 | margin-top: 10px; 11 | & > div { 12 | margin: 0 auto; 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /app/Editor/components/SettingsComponents/FileUploader/styles.css: -------------------------------------------------------------------------------- 1 | .fileUploader { 2 | &__dropzoneContainer { 3 | position: relative; 4 | } 5 | &__inputBox { 6 | margin: 0 0 5px 8px; 7 | &_stack { 8 | width: 40%; 9 | & > input { 10 | width: 100%; 11 | line-height: 20px; 12 | padding: 0 5px; 13 | box-sizing: border-box; 14 | text-align: center; 15 | &:focus { 16 | outline: none; 17 | } 18 | } 19 | } 20 | & > label { 21 | font-size: 13px; 22 | color: #333; 23 | margin-left: 5px; 24 | } 25 | &_size { 26 | margin: 10px 0 20px 8px; 27 | } 28 | } 29 | &__title { 30 | font-size: 13px; 31 | font-weight: 700; 32 | color: #333; 33 | margin: 0 0 3px; 34 | } 35 | &__dropzoneMessage { 36 | position: absolute; 37 | top: 50%; 38 | left: 50%; 39 | transform: translate(-50%, -50%); 40 | color: #333; 41 | line-height: 1.2; 42 | &_white { 43 | color: #fff; 44 | text-shadow: 1px 1px 1px #000; 45 | font-size: 12px; 46 | } 47 | } 48 | &__dropzone { 49 | width: 200px; 50 | height: 154px; 51 | margin: 0 auto 20px; 52 | padding: 10px; 53 | text-align: center; 54 | box-sizing: border-box; 55 | border: 1px dashed #333; 56 | border-radius: 5px; 57 | } 58 | &__previewContainer { 59 | display: flex; 60 | position: relative; 61 | width: 100%; 62 | height: 100%; 63 | justify-content: center; 64 | } 65 | &__preview { 66 | max-width: 100%; 67 | max-height: 100%; 68 | vertical-align: top; 69 | border-radius: 5px; 70 | } 71 | &__optionsTitle { 72 | color: #333; 73 | font-size: .8rem; 74 | font-weight: 600; 75 | margin: 0 0 8px; 76 | } 77 | &__backgroundOptions { 78 | display: flex; 79 | text-align: left; 80 | padding: 0 20px; 81 | &__column { 82 | width: 50%; 83 | } 84 | } 85 | &__repeatingBox { 86 | margin-bottom: 10px; 87 | } 88 | &__positionBox { 89 | display: flex; 90 | justify-content: space-around; 91 | } 92 | &__resetButton { 93 | position: absolute; 94 | top: 0; 95 | right: 0; 96 | height: 12px; 97 | width: 12px; 98 | border-radius: 20px; 99 | background: #39B6B3; 100 | padding: 2px; 101 | text-align: center; 102 | vertical-align: middle; 103 | & svg { 104 | fill: #fff; 105 | height: 12px; 106 | width: 12px; 107 | } 108 | } 109 | } -------------------------------------------------------------------------------- /app/Editor/components/SettingsComponents/Font/styles.css: -------------------------------------------------------------------------------- 1 | .font { 2 | &__controlsContainer { 3 | padding: 0 27px; 4 | } 5 | &__controls { 6 | display: flex; 7 | justify-content: space-between; 8 | margin-bottom: 20px; 9 | line-height: 24px; 10 | } 11 | &__labelBox { 12 | text-align: left; 13 | margin-right: 20px; 14 | } 15 | &__inputTip { 16 | position: absolute; 17 | right: 7px; 18 | color: #333; 19 | top: 3px; 20 | } 21 | &__input { 22 | width: 100%; 23 | line-height: 26px; 24 | padding: 0 10px; 25 | font-family: 'Open Sans', sans-serif; 26 | box-sizing: border-box; 27 | &_icon { 28 | padding-left: 26px; 29 | width: 100%; 30 | box-sizing: border-box; 31 | &:focus + .font__inputTip, 32 | &:hover + .font__inputTip { 33 | display: none; 34 | } 35 | } 36 | } 37 | &__labelOptions { 38 | display: inline-block; 39 | height: 24px; 40 | line-height: 24px; 41 | font-size: 13px; 42 | padding: 0 5px; 43 | vertical-align: middle; 44 | color: #333; 45 | border-radius: 5px; 46 | border: 1px solid transparent; 47 | } 48 | &__labelIcon { 49 | display: block; 50 | height: 18px; 51 | position: absolute; 52 | top: 50%; 53 | left: 5px; 54 | transform: translateY(-50%); 55 | } 56 | &__inputBox { 57 | margin: 0 20px 0 0; 58 | width: 70%; 59 | &_size { 60 | width: 20%; 61 | margin-right: 0; 62 | } 63 | &_iconInput { 64 | position: relative; 65 | width: 24%; 66 | &:last-child { 67 | margin-right: 0; 68 | } 69 | } 70 | & input:focus { 71 | outline: none; 72 | } 73 | &_radio, 74 | &_checkbox { 75 | line-height: 20px; 76 | width: auto; 77 | margin: 0 5px; 78 | & > input { 79 | width: auto; 80 | appearance: none; 81 | &:checked + label { 82 | border-color: #333; 83 | } 84 | } 85 | } 86 | } 87 | &__label { 88 | display: block; 89 | margin-bottom: 3px; 90 | font-size: 13px; 91 | font-weight: 700; 92 | color: #333; 93 | } 94 | &__title { 95 | font-size: 13px; 96 | margin: 0 0 2px; 97 | color: #333; 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /app/Editor/components/SettingsComponents/Link/styles.css: -------------------------------------------------------------------------------- 1 | .link { 2 | &__radioContainer { 3 | display: flex; 4 | justify-content: center; 5 | } 6 | &__input { 7 | width: 100%; 8 | line-height: 26px; 9 | padding: 0 10px; 10 | box-sizing: border-box; 11 | &:focus { 12 | outline: none; 13 | } 14 | } 15 | &__inputBox { 16 | margin: 0 auto; 17 | width: 80%; 18 | &_radio { 19 | line-height: 30px; 20 | width: auto; 21 | margin: 0 10px; 22 | & > input { 23 | width: auto; 24 | } 25 | } 26 | } 27 | &__label { 28 | display: block; 29 | color: #333; 30 | margin-bottom: 3px; 31 | font-size: 14px; 32 | } 33 | &__title { 34 | font-size: 14px; 35 | color: #333; 36 | margin: 10px 0; 37 | } 38 | &__labelOptions { 39 | display: inline-block; 40 | height: 30px; 41 | margin-left: 5px; 42 | vertical-align: middle; 43 | color: #333; 44 | } 45 | } -------------------------------------------------------------------------------- /app/Editor/components/SettingsComponents/SettingsTitle/index.js: -------------------------------------------------------------------------------- 1 | import { compose, withState, withHandlers } from 'recompose'; 2 | import cx from 'classnames'; 3 | import ColorPicker from '../ColorPicker'; 4 | 5 | import styles from './styles.css'; 6 | 7 | export default compose( 8 | withState('active', 'setActive', false), 9 | withHandlers({ 10 | onClick: props => e => { 11 | e.stopPropagation(); 12 | props.setActive(!props.active); 13 | } 14 | }) 15 | )(({ 16 | label, 17 | active, 18 | onClick, 19 | ...props 20 | }) => ( 21 |
22 |
23 | { 24 | label && 25 | 28 | {label} 29 | 30 | } 31 | { 32 | props.color && 33 |
36 |
39 |
40 | } 41 |
42 | { 43 | active && 44 |
45 | { 46 | props.children 47 | } 48 |
49 | } 50 |
51 | )); 52 | -------------------------------------------------------------------------------- /app/Editor/components/SettingsComponents/SettingsTitle/styles.css: -------------------------------------------------------------------------------- 1 | .setting { 2 | &__label { 3 | margin-right: 10px; 4 | user-select: none; 5 | font: 400 14px 'Open Sans', sans-serif; 6 | } 7 | &__button { 8 | display: inline-block; 9 | padding: 5px; 10 | cursor: pointer; 11 | border-radius: 1px; 12 | background: #fff; 13 | box-shadow: 0 0 0 1px rgba(0, 0, 0, .098); 14 | vertical-align: middle; 15 | } 16 | &__placeholder { 17 | width: 36px; 18 | height: 14px; 19 | border-radius: 2px; 20 | } 21 | &__component { 22 | background: #FFF; 23 | padding: 20px 10px; 24 | margin-top: 8px; 25 | box-shadow: 1px 1px 5px 0 #aaa inset; 26 | border-top: 1px solid color(#39B6B3 l(50%)); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /app/Editor/components/SettingsComponents/Shadow/styles.css: -------------------------------------------------------------------------------- 1 | .shadow { 2 | &__options { 3 | display: flex; 4 | padding: 0 20px; 5 | justify-content: space-around; 6 | flex-wrap: wrap; 7 | } 8 | &__inputBox { 9 | margin: 0 10px 10px 0; 10 | width: 20%; 11 | & > label { 12 | display: block; 13 | font-size: 13px; 14 | font-weight: 700; 15 | color: #333; 16 | margin-bottom: 3px; 17 | } 18 | & > input { 19 | width: 100%; 20 | height: 30px; 21 | padding: 0 5px; 22 | text-align: center; 23 | box-sizing: border-box; 24 | &:focus { 25 | outline: none; 26 | } 27 | } 28 | } 29 | &__title { 30 | margin: 0 0 10px; 31 | font-size: 14px; 32 | color: #333; 33 | } 34 | &__resetButton { 35 | height: 12px; 36 | width: 12px; 37 | border-radius: 20px; 38 | background: #39B6B3; 39 | padding: 2px; 40 | text-align: center; 41 | vertical-align: middle; 42 | margin: 24px 0 0; 43 | & svg { 44 | fill: #fff; 45 | height: 12px; 46 | width: 12px; 47 | } 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /app/Editor/components/Wrappers/AtomWrap/index.js: -------------------------------------------------------------------------------- 1 | import Customizer from '../../Customizer/'; 2 | 3 | export default ({ 4 | Atom, 5 | activate, 6 | connectDragPreview, 7 | isDragging, 8 | ...props 9 | }) => { 10 | return ( 11 |
12 | 18 | 19 | 20 |
21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /app/Editor/components/Wrappers/AtomWrap/styles.css: -------------------------------------------------------------------------------- 1 | .button{ 2 | position: absolute; 3 | bottom: 0; 4 | } 5 | -------------------------------------------------------------------------------- /app/Editor/components/Wrappers/EditorWrap/BlankState.js: -------------------------------------------------------------------------------- 1 | import styles from './styles.css'; 2 | 3 | export default ({ isOver }) => ( 4 |
5 | { 6 | isOver && 7 |
8 |

Now drop it

9 |
10 | } 11 | { 12 | !isOver && 13 |
14 |

Drop something here

15 |

It could be Template or Organism

16 |
17 | } 18 |
19 | ); 20 | -------------------------------------------------------------------------------- /app/Editor/components/Wrappers/EditorWrap/index.js: -------------------------------------------------------------------------------- 1 | import { PropTypes } from 'react'; 2 | import cx from 'classnames'; 3 | import { compose, getContext } from 'recompose'; 4 | import editorState from 'Editor/helpers/editorState'; 5 | import BlankState from './BlankState'; 6 | import Eraser from '../../Eraser'; 7 | import PagePreview from '../../PagePreview'; 8 | import EditorSidebar from '../../EditorSidebar'; 9 | import styles from './styles.css'; 10 | 11 | const { object, func } = PropTypes; 12 | 13 | export default compose( 14 | editorState, 15 | getContext({ dragingItem: object, drop: func }) 16 | )(({ 17 | editingItem, 18 | organisms, 19 | children, 20 | isOver, 21 | drop, 22 | dragingItem, 23 | pure, 24 | ...props 25 | }) => { 26 | if (pure) return
{children}
; 27 | return ( 28 |
29 |
30 | { 31 | !organisms.size 32 | && 33 | || children 34 | } 35 |
36 | { 37 | organisms.size && 38 | } 39 | { 40 | editingItem.isSidebarOpen 41 | && 42 | } 43 | { 44 | dragingItem.isDragging && 45 | } 46 |
47 | ); 48 | }); 49 | -------------------------------------------------------------------------------- /app/Editor/components/Wrappers/EditorWrap/styles.css: -------------------------------------------------------------------------------- 1 | .wrap{ 2 | box-sizing: border-box; 3 | min-height: 100vh; 4 | position: relative; 5 | will-change: transform; 6 | transition: transform .3s ease-in; 7 | 8 | &_shifted { 9 | transform: translateX(350px); 10 | } 11 | } 12 | 13 | .BlankState{ 14 | position: absolute; 15 | border-radius: 5px; 16 | left: 7px; 17 | top: 7px; 18 | right: 7px; 19 | bottom: 7px; 20 | display: flex; 21 | align-items: center; 22 | justify-content: center; 23 | background-color: color(#78B2D1 a(70%)); 24 | &:before{ 25 | content: ' '; 26 | position: absolute; 27 | left: -4px; 28 | right: -4px; 29 | top: -4px; 30 | bottom: -4px; 31 | border: 2px dashed #7CC9F3; 32 | border-radius: 5px; 33 | } 34 | & .content{ 35 | text-align: center; 36 | padding: 50px; 37 | background: color(white a(10%)); 38 | color: white; 39 | text-shadow: 1px 1px 0px color(#333 a(10%)); 40 | & h3{ 41 | font-weight: 500; 42 | font-size: 20px; 43 | text-transform: uppercase; 44 | margin-bottom: 10px; 45 | } 46 | & p{ 47 | font-weight: 300; 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /app/Editor/components/Wrappers/MoleculeWrap/index.js: -------------------------------------------------------------------------------- 1 | import { PropTypes } from 'react'; 2 | import { compose, getContext } from 'recompose'; 3 | import withEditorState from 'Editor/helpers/editorState'; 4 | import styles from './styles.css'; 5 | import Customizer from '../../Customizer/'; 6 | 7 | const Placeholder = ({ atoms }) => !atoms.size && ( 8 |
9 |
10 | Drop atoms here 11 |
12 | ); 13 | 14 | export default compose( 15 | withEditorState, 16 | getContext({ 17 | editorDisabled: PropTypes.bool 18 | }), 19 | )(({ 20 | Molecule, 21 | editorDisabled, 22 | ...props 23 | }) => { 24 | if (editorDisabled) return ; 25 | return ( 26 | 30 | { 31 | props.editingItem.canEdit && 32 | } 33 | 34 | 35 | ) 36 | }); 37 | -------------------------------------------------------------------------------- /app/Editor/components/Wrappers/MoleculeWrap/styles.css: -------------------------------------------------------------------------------- 1 | .wrap{ 2 | position: relative; 3 | } 4 | 5 | .placeholder{ 6 | display: flex; 7 | align-items: center; 8 | justify-content: center; 9 | min-height: 300px; 10 | background: #fff; 11 | text-transform: uppercase; 12 | color: color(#78B2D1 a(70%)); 13 | border-radius: 3px; 14 | } 15 | 16 | .placeholderBorder{ 17 | border-radius: 3px; 18 | position: absolute; 19 | left: -3px; 20 | top: -3px; 21 | bottom: -3px; 22 | right: -3px; 23 | border: 2px dashed color(#78B2D1 a(70%)); 24 | } 25 | -------------------------------------------------------------------------------- /app/Editor/components/Wrappers/OrganismWrap/index.js: -------------------------------------------------------------------------------- 1 | import Customizer from '../../Customizer/'; 2 | 3 | export default ({ 4 | Organism, 5 | isDragging, 6 | connectDragPreview, 7 | ...rest 8 | }) => ( 9 | 15 | 16 | 17 | ); 18 | -------------------------------------------------------------------------------- /app/Editor/components/Wrappers/OrganismWrap/styles.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Uscreen-video/atomic-builder/ca83a73d7b496e29032d5549fd19e09ee2fdb3cc/app/Editor/components/Wrappers/OrganismWrap/styles.css -------------------------------------------------------------------------------- /app/Editor/creators/createAtom.js: -------------------------------------------------------------------------------- 1 | import { compose, defaultProps, withState, withHandlers } from 'recompose'; 2 | 3 | import AtomWrap from '../components/Wrappers/AtomWrap'; 4 | 5 | import editorState from '../helpers/editorState'; 6 | import dndHandler from '../dnd/handler'; 7 | 8 | export default ({ component, props: { settings: settingsMapper } }) => 9 | compose( 10 | 11 | // Set Component to render 12 | defaultProps({ 13 | Atom: component, 14 | settingsMapper 15 | }), 16 | 17 | // Connect to EditorState 18 | editorState, 19 | withState('content', 'setContent', props => { 20 | console.log('New atom has been created!'); 21 | return props.atom.get('content') || props.content; 22 | }), 23 | 24 | // Show controlls only on active element 25 | withState('active', 'setActive', false), 26 | withHandlers({ 27 | activate: props => () => { 28 | props.editContent(props.Cursor); 29 | props.setActive(true); 30 | }, 31 | deactivate: props => () => { 32 | props.releaseItem(); 33 | props.setActive(false); 34 | props.updateEditor('content', props.content); 35 | }, 36 | updateSettings: props => data => { 37 | const mutation = props.settings.merge(data); 38 | props.updateEditor('settings', mutation); 39 | }, 40 | onChange: props => props.setContent 41 | }), 42 | 43 | dndHandler('atom', 'atom') 44 | )(AtomWrap); 45 | -------------------------------------------------------------------------------- /app/Editor/creators/createMolecule.js: -------------------------------------------------------------------------------- 1 | import { 2 | compose, defaultProps, createEagerElement, withProps 3 | } from 'recompose'; 4 | 5 | import * as Atoms from 'Atomic/Atoms'; 6 | 7 | import MoleculeWrap from '../components/Wrappers/MoleculeWrap'; 8 | import dndState from '../dnd/state'; 9 | import editorState from '../helpers/editorState'; 10 | import dndHandler from '../dnd/handler'; 11 | 12 | const mapAtoms = (atoms, { 13 | Cursor, add, move, hover, remove, hoverIndex, molecule 14 | }) => atoms.map((atom, index) => { 15 | return createEagerElement( 16 | Atoms[atom.get('type')].Component, 17 | { 18 | index, atom, key: atom.get('id'), 19 | add, move, remove, hover, hoverIndex, 20 | Cursor: Cursor.push('atoms', index), 21 | content: molecule.getIn(['atoms', index, 'content']) || atom.get('content'), 22 | settings: molecule.getIn(['atoms', index, 'settings']) || atom.get('settings'), 23 | defaultSettings: atom.get('settings') 24 | } 25 | ); 26 | } 27 | ); 28 | 29 | export default ({ component, props: { settings: settingsMapper } }) => 30 | compose( 31 | // Prevent updates from parent state 32 | // disableUpdate(), 33 | 34 | // Set Component to render 35 | defaultProps({ 36 | Molecule: component, 37 | settingsMapper 38 | }), 39 | 40 | // Connect to EditorState 41 | editorState, 42 | 43 | // We allow to move atoms inside molece 44 | dndState('atoms', 'molecule'), 45 | 46 | // Map atoms from state to components 47 | withProps(props => { 48 | const atoms = props.atoms.isEmpty() 49 | ? props.molecule.has('atoms') && props.molecule.get('atoms') || props.atoms 50 | : props.atoms || []; 51 | return ({ 52 | children: mapAtoms(atoms, props) 53 | }) 54 | }), 55 | 56 | dndHandler('molecule') 57 | 58 | )(MoleculeWrap); 59 | -------------------------------------------------------------------------------- /app/Editor/creators/createOrganism.js: -------------------------------------------------------------------------------- 1 | import { 2 | compose, defaultProps, createEagerFactory, 3 | createEagerElement, withProps, lifecycle 4 | } from 'recompose'; 5 | 6 | import * as Molecules from 'Atomic/Molecules'; 7 | import { Map } from 'immutable'; 8 | 9 | import OrganismWrap from '../components/Wrappers/OrganismWrap'; 10 | import editorState from '../helpers/editorState'; 11 | import dndHandler from '../dnd/handler'; 12 | 13 | const mapMolecules = (molecules, { Cursor }) => molecules.map((molecule, key) => { 14 | return createEagerElement( 15 | Molecules[molecule.get('type')].Component, 16 | { 17 | key, molecule, 18 | settings: molecule.get('settings') || Map({}), 19 | theme: molecule.has('theme') && molecule.get('theme').toJS() || void 0, 20 | Cursor: Cursor.push('molecules', key) 21 | } 22 | ); 23 | } 24 | ); 25 | 26 | export default ({ component, props: { settings: settingsMapper } }) => 27 | compose( 28 | 29 | // Create lazy-evaluating component to render 30 | defaultProps({ 31 | settingsMapper, 32 | Organism: createEagerFactory(component) 33 | }), 34 | 35 | // Connect to EditorState 36 | editorState, 37 | 38 | lifecycle({ 39 | componentWillMount() { 40 | const { organism, updateEditor } = this.props; 41 | if (!!organism.get('molecules').findKey(_molecule => !_molecule.has('settings'))) { 42 | // const settings = settingsToObject(settingsMapper); 43 | const mutation = organism.get('molecules').map(_molecule => { 44 | if (_molecule.has('settings')) return _molecule; 45 | return _molecule.set('settings', Map({})); // eslint-disable-line new-cap 46 | }); 47 | updateEditor('molecules', mutation); 48 | } 49 | } 50 | }), 51 | 52 | // We pass molecules as props to organism 53 | withProps(props => ({ 54 | molecules: mapMolecules(props.organism.get('molecules'), props) 55 | })), 56 | 57 | dndHandler('organism'), 58 | 59 | )(OrganismWrap); 60 | -------------------------------------------------------------------------------- /app/Editor/creators/createPreview.js: -------------------------------------------------------------------------------- 1 | import { compose, mapProps, getContext } from 'recompose'; 2 | import { PropTypes } from 'react'; 3 | import { fromJS, Map } from 'immutable'; 4 | 5 | import Preview from '../components/EditorPreview'; 6 | import dndHandler from '../dnd/handler'; 7 | 8 | export default (type, { preview, props: rawProps, data }) => { 9 | const props = data ? data : fromJS(rawProps).withMutations(_props => { 10 | if (_props.has('settings')) { 11 | const _settings = _props.get('settings'); 12 | const settings = _settings.reduce((acc, obj, key) => 13 | acc.set(key, obj.get('value')), 14 | Map({})); 15 | _props.set('settings', settings); 16 | } 17 | }); 18 | 19 | return compose( 20 | 21 | // Notify editor if we draging something 22 | getContext({ drag: PropTypes.func, drop: PropTypes.func }), 23 | 24 | // Props to render component and actions to file in dnd 25 | mapProps(({ drag, drop }) => ({ src: preview, props, drag, drop, type })), 26 | 27 | // We allow to drag preview, after it droped we pass props to create element 28 | dndHandler('preview', type) 29 | )(Preview); 30 | }; 31 | -------------------------------------------------------------------------------- /app/Editor/creators/createShape.js: -------------------------------------------------------------------------------- 1 | import { Record } from 'immutable'; 2 | 3 | class Shape extends Record({ // eslint-disable-line new-cap 4 | }) 5 | { 6 | link(value, extendObject) { 7 | return { 8 | type: 'link', 9 | description: 'Some description', 10 | title: 'Url:', 11 | value, 12 | ...extendObject 13 | }; 14 | } 15 | spacing(value, extendObject) { 16 | return { 17 | type: 'spacing', 18 | description: 'Some description', 19 | title: 'Margins and paddings:', 20 | value, 21 | ...extendObject 22 | }; 23 | } 24 | background(value, extendObject) { 25 | return { 26 | type: 'background', 27 | description: 'Some description', 28 | title: 'Background:', 29 | value, 30 | ...extendObject 31 | }; 32 | } 33 | size(value, extendObject) { 34 | return { 35 | type: 'size', 36 | description: 'Some description', 37 | title: 'Box size:', 38 | value, 39 | ...extendObject 40 | }; 41 | } 42 | align(value, extendObject) { 43 | return { 44 | type: 'align', 45 | description: 'Some description', 46 | title: 'Align atoms:', 47 | value, 48 | ...extendObject 49 | }; 50 | } 51 | border(value, extendObject) { 52 | return { 53 | type: 'border', 54 | description: 'Some description', 55 | title: 'Border settings:', 56 | value, 57 | ...extendObject 58 | }; 59 | } 60 | shadow(value, extendObject) { 61 | return { 62 | type: 'shadow', 63 | description: 'Some description', 64 | title: 'Shadow settings:', 65 | value, 66 | ...extendObject 67 | }; 68 | } 69 | font(value, extendObject) { 70 | return { 71 | type: 'font', 72 | description: 'Some description', 73 | title: 'Font settings:', 74 | value, 75 | ...extendObject 76 | }; 77 | } 78 | } 79 | 80 | export const shape = new Shape; 81 | -------------------------------------------------------------------------------- /app/Editor/dnd/dragSource.js: -------------------------------------------------------------------------------- 1 | export const atom = { 2 | beginDrag({ index, Cursor, remove, settings, content, ...rest }) { 3 | return { 4 | index, 5 | Cursor, 6 | remove, // Add posiability to remove atom thought atom 7 | props: rest.atom.set('content', content).set('settings', settings), 8 | type: 'atom' 9 | }; 10 | }, 11 | canDrag(props) { 12 | return props.canDrag; 13 | } 14 | }; 15 | 16 | export const organism = { 17 | beginDrag({ index, props, remove, Cursor }) { 18 | return { 19 | index, 20 | props, 21 | remove, 22 | Cursor, 23 | type: 'organism' 24 | }; 25 | }, 26 | canDrag(props) { 27 | return props.canDrag; 28 | } 29 | }; 30 | 31 | export const preview = { 32 | beginDrag({ props, type }) { 33 | return ({ props, isPreview: true, type }); 34 | } 35 | }; 36 | -------------------------------------------------------------------------------- /app/Editor/dnd/helpers.js: -------------------------------------------------------------------------------- 1 | import { findDOMNode } from 'react-dom'; 2 | 3 | export const getPosition = (props, monitor, component) => { 4 | let position; 5 | const hoverBoundingRect = findDOMNode(component).getBoundingClientRect(); 6 | const hoverMiddleY = (hoverBoundingRect.bottom - hoverBoundingRect.top) / 2; 7 | const clientOffset = monitor.getClientOffset(); 8 | const hoverClientY = clientOffset.y - hoverBoundingRect.top; 9 | if (hoverClientY < hoverMiddleY) { 10 | position = 0; 11 | } 12 | if (hoverClientY > hoverMiddleY) { 13 | position = 1; 14 | } 15 | return props.index + position; 16 | }; 17 | 18 | 19 | export const isNested = (initial, extended) => 20 | void 0 === initial.find((x, index) => x !== extended.get(index)); 21 | -------------------------------------------------------------------------------- /app/Editor/dnd/spec.js: -------------------------------------------------------------------------------- 1 | export const organism = { 2 | target: 'organism', 3 | source: 'organism' 4 | }; 5 | 6 | export const template = { 7 | target: 'organism' 8 | }; 9 | 10 | export const molecule = { 11 | target: 'atom' 12 | }; 13 | 14 | export const atom = { 15 | target: 'atom', 16 | source: 'atom' 17 | }; 18 | 19 | export const preview = { 20 | source: 'organism' 21 | }; 22 | 23 | export const eraser = { 24 | target: ['organism', 'atom'] 25 | }; 26 | -------------------------------------------------------------------------------- /app/Editor/dnd/state.js: -------------------------------------------------------------------------------- 1 | import UUID from 'uuid-js'; 2 | import { compose, withState, withHandlers, lifecycle } from 'recompose'; 3 | import { fromJS, List } from 'immutable'; 4 | 5 | function createId() { 6 | return UUID.create().toString(); 7 | } 8 | 9 | export default (type, key = type, shouldCommit = true) => compose( 10 | withState(type, 'update', props => 11 | props[key] instanceof List 12 | && props[key].get(type) || List([]) // eslint-disable-line new-cap 13 | || fromJS(props[key]) 14 | ), 15 | 16 | // Update local state if something has been changed outside 17 | lifecycle({ 18 | componentWillReceiveProps(next) { 19 | if (!next[key].get) return; 20 | 21 | const entityInStore = next[key].get(type); 22 | if (entityInStore && !entityInStore.equals(this.props[type])) { 23 | this.props.update(entityInStore); 24 | } 25 | } 26 | }), 27 | 28 | withState('hoverIndex', 'hover', void 0), 29 | 30 | withHandlers({ 31 | move: ({ update, updateEditor, ...props }) => (index, movedIndex) => { 32 | const mutation = props[type].delete(movedIndex).insert(index, props[type].get(movedIndex)); 33 | console.log(`[${type}] Move:`, mutation.toJS()); 34 | update(mutation, shouldCommit && updateEditor(type, mutation)); 35 | }, 36 | add: ({ update, updateEditor, ...props }) => (index, data) => { 37 | console.log('DROP', props[type].toJS()) 38 | const mutation = props[type].insert(index, data.set('id', createId())); 39 | console.log(`[${type}] Add:`, mutation.toJS()); 40 | update(mutation, shouldCommit && updateEditor(type, mutation)); 41 | }, 42 | append: ({ update, updateEditor, ...props }) => (data) => { 43 | const mutation = props[type].push(data.set('id', createId())); 44 | console.log(`[${type}] Append:`, mutation.toJS()); 45 | update(mutation, shouldCommit && updateEditor(type, mutation)); 46 | }, 47 | remove: ({ update, updateEditor, ...props }) => (index) => { 48 | const mutation = props[type].delete(index); 49 | console.log(`[${type}] Remove:`, mutation.toJS()); 50 | update(mutation, shouldCommit && updateEditor(type, mutation)); 51 | }, 52 | }), 53 | ); 54 | -------------------------------------------------------------------------------- /app/Editor/draft/Toolbar/Button.js: -------------------------------------------------------------------------------- 1 | import { compose, withProps, withHandlers } from 'recompose'; 2 | import cx from 'classnames'; 3 | import Ink from 'react-ink'; 4 | import Icon from 'Editor/components/Icon'; 5 | 6 | import styles from './styles.css'; 7 | 8 | 9 | export default compose( 10 | withProps(props => ({ 11 | type: 'button', 12 | className: styles.button 13 | })), 14 | withHandlers({ 15 | onClick: props => props.onChange 16 | }) 17 | )(({ icon, active, color, ...rest }) => ( 18 |
  • 19 | 26 |
  • 27 | )); 28 | -------------------------------------------------------------------------------- /app/Editor/draft/Toolbar/ColorPicker.js: -------------------------------------------------------------------------------- 1 | import { compose, withHandlers, withState, lifecycle } from 'recompose'; 2 | import { EditorState, RichUtils, Entity } from 'draft-js'; 3 | import styles from './styles.css'; 4 | 5 | const previewsColor = []; 6 | 7 | function manageColors(color) { 8 | if (previewsColor.includes(color)) return; 9 | if (previewsColor.length > 10) previewsColor.shift(); 10 | previewsColor.push(color); 11 | } 12 | 13 | const Color = withHandlers({ 14 | setColor: props => e => { 15 | e.preventDefault(); 16 | props.onChange(props.color); 17 | props.onSubmit(e); 18 | } 19 | })(({ 20 | color: background, 21 | setColor 22 | }) => ( 23 |
    24 | )); 25 | 26 | 27 | export default compose( 28 | withState('color', 'setColor', ''), 29 | withHandlers({ 30 | updateColor: props => e => props.setColor(e.target.value), 31 | onSubmit: ({ editorState, onChange, color, onCancel }) => e => { 32 | e.preventDefault(); 33 | onCancel(false); 34 | manageColors(color); 35 | const entityKey = Entity.create('COLOR', 'MUTABLE', { color }); 36 | const newState = RichUtils.toggleLink(editorState, editorState.getSelection(), entityKey); 37 | onChange(EditorState.forceSelection(newState, editorState.getSelection())); 38 | } 39 | }), 40 | lifecycle({ 41 | componentWillReceiveProps(next) { 42 | if (!next.entityKey) return; 43 | const nextColor = Entity.get(next.entityKey).getData().color; 44 | if (!!nextColor && nextColor !== this.props.color) { 45 | this.props.setColor(nextColor); 46 | } 47 | } 48 | }) 49 | )(({ 50 | onSubmit, 51 | updateColor, 52 | setColor 53 | }) => ( 54 |
    55 | { 56 | !!previewsColor.length && 57 |
    58 |

    previously selected:

    59 | { 60 | previewsColor.map((color, key) => 61 | 62 | ) 63 | } 64 |
    65 | } 66 | 70 |
    71 | )); 72 | -------------------------------------------------------------------------------- /app/Editor/draft/Toolbar/LinkInput.js: -------------------------------------------------------------------------------- 1 | import { compose, withHandlers, withState, lifecycle } from 'recompose'; 2 | import { EditorState, RichUtils, Entity } from 'draft-js'; 3 | import styles from './styles.css'; 4 | 5 | 6 | export default compose( 7 | withState('link', 'setLink', ''), 8 | withHandlers({ 9 | updateLink: props => e => props.setLink(e.target.value), 10 | onSubmit: ({ editorState, onChange, link, onCancel }) => e => { 11 | e.preventDefault(); 12 | onCancel(false); 13 | const entityKey = Entity.create('LINK', 'MUTABLE', { link }); 14 | const newState = RichUtils.toggleLink(editorState, editorState.getSelection(), entityKey); 15 | onChange(EditorState.forceSelection(newState, editorState.getSelection())); 16 | } 17 | }) 18 | )(({ 19 | onSubmit, 20 | updateLink 21 | }) => ( 22 |
    23 | ref && ref.focus()} 25 | className={styles.link} 26 | onChange={updateLink} 27 | placeholder='Enter link and click enter' /> 28 |
    29 | )); 30 | -------------------------------------------------------------------------------- /app/Editor/draft/Toolbar/Overlay.js: -------------------------------------------------------------------------------- 1 | import styles from './styles.css'; 2 | import cx from 'classnames'; 3 | import Icon from 'Editor/components/Icon'; 4 | 5 | function prevent(e) { 6 | e.preventDefault(); 7 | } 8 | 9 | export default ({ children, active, onCancel }) => ( 10 |
    11 | 14 |
    15 | {children} 16 |
    17 |
    18 | ); 19 | -------------------------------------------------------------------------------- /app/Editor/draft/Toolbar/Separator.js: -------------------------------------------------------------------------------- 1 | import styles from './styles.css'; 2 | 3 | export default () =>
  • ; 4 | -------------------------------------------------------------------------------- /app/Editor/draft/Toolbar/actions.js: -------------------------------------------------------------------------------- 1 | export const base = [ 2 | { type: 'inline', label: 'B', style: 'BOLD', icon: 'bold' }, 3 | { type: 'inline', label: 'I', style: 'ITALIC', icon: 'italic' }, 4 | { type: 'inline', label: 'U', style: 'UNDERLINE', icon: 'underline' }, 5 | { type: 'separator' }, 6 | { type: 'entity', label: 'Link', style: 'link', icon: 'link' }, 7 | { type: 'entity', label: 'Font', style: 'font', icon: 'font', 8 | collection: [ 9 | 'header-one', 'header-two', 'header-three', 'header-four' 10 | ] 11 | }, 12 | { type: 'entity', label: 'Color', style: 'color', icon: 'color' }, 13 | 14 | { type: 'separator' }, 15 | 16 | { type: 'inlineBlock', label: 'textAlign', style: 'left', icon: 'align_left' }, 17 | { type: 'inlineBlock', label: 'textAlign', style: 'center', icon: 'align_center' }, 18 | { type: 'inlineBlock', label: 'textAlign', style: 'right', icon: 'align_right' }, 19 | 20 | { type: 'separator' }, 21 | 22 | { type: 'block', label: 'UL', style: 'unordered-list-item', icon: 'ul' }, 23 | { type: 'block', label: 'OL', style: 'ordered-list-item', icon: 'ol' }, 24 | { type: 'block', label: 'QT', style: 'blockquote', icon: 'quote' } 25 | ]; 26 | 27 | export const font = [ 28 | { type: 'block', label: 'H1', style: 'header-one', icon: 'h1' }, 29 | { type: 'block', label: 'H2', style: 'header-two', icon: 'h2' }, 30 | { type: 'block', label: 'H3', style: 'header-three', icon: 'h3' }, 31 | { type: 'block', label: 'H4', style: 'header-four', icon: 'h4' }, 32 | { type: 'separator' } 33 | ]; 34 | -------------------------------------------------------------------------------- /app/Editor/draft/Toolbar/index.js: -------------------------------------------------------------------------------- 1 | import { compose, withState, lifecycle, withHandlers } from 'recompose'; 2 | import cx from 'classnames'; 3 | 4 | import styles from './styles.css'; 5 | import Buttons from './Buttons'; 6 | import getSelectionCoords from '../helpers/getSelectionCoords'; 7 | import Overlay from './Overlay'; 8 | import LinkInput from './LinkInput'; 9 | import ColorPicker from './ColorPicker'; 10 | import * as actions from './actions'; 11 | 12 | export default compose( 13 | withState('show', 'changeShow', false), 14 | withState('position', 'updatePosition', {}), 15 | withState('active', 'setActive', false), 16 | withHandlers({ 17 | resetActive: props => () => props.setActive({}) 18 | }), 19 | 20 | lifecycle({ 21 | shouldComponentUpdate(next, prev) { 22 | return !prev || next.editorState.getSelection() !== prev.editorState.getSelection(); 23 | }, 24 | 25 | componentWillReceiveProps(next) { 26 | if (!next.editor || next.editorState === this.props.editorState) return; 27 | const selection = next.editorState.getSelection(); 28 | if (!selection.isCollapsed()) { 29 | const position = getSelectionCoords(next.editor); 30 | if (position) { 31 | this.props.updatePosition(position); 32 | this.props.changeShow(true); 33 | } 34 | } else { 35 | this.props.changeShow(false); 36 | } 37 | } 38 | }), 39 | 40 | )(({ 41 | show, 42 | position, 43 | active, 44 | resetActive, 45 | ...rest 46 | }) => ( 47 |
    48 |
    49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 |
    60 |
    61 | )); 62 | -------------------------------------------------------------------------------- /app/Editor/draft/helpers/blockRenderer.js: -------------------------------------------------------------------------------- 1 | import { Map } from 'immutable'; 2 | import { DefaultDraftBlockRenderMap } from 'draft-js'; 3 | 4 | const blockRenderMap = Map({ 5 | paragraph: { 6 | element: 'p' 7 | }, 8 | unstyled: { 9 | element: 'p' 10 | } 11 | }); 12 | 13 | export default DefaultDraftBlockRenderMap.merge(blockRenderMap); 14 | -------------------------------------------------------------------------------- /app/Editor/draft/helpers/blockStyleFn.js: -------------------------------------------------------------------------------- 1 | function getBlockAlignment(block) { 2 | let style = 'left'; 3 | block.findStyleRanges(e => { 4 | if (e.hasStyle('center')) style = 'center'; 5 | if (e.hasStyle('right')) style = 'right'; 6 | }); 7 | return style; 8 | } 9 | 10 | export default block => { 11 | let alignment = getBlockAlignment(block); 12 | return `alignment--${alignment}`; 13 | }; 14 | -------------------------------------------------------------------------------- /app/Editor/draft/helpers/decorator.js: -------------------------------------------------------------------------------- 1 | import { Entity, CompositeDecorator } from 'draft-js'; 2 | import findEntity from './findEntity'; 3 | 4 | const Link = ({ children, entityKey }) => { 5 | const { link } = Entity.get(entityKey).getData(); 6 | return {children}; 7 | }; 8 | 9 | const Color = ({ children, entityKey }) => { 10 | const color = Entity.get(entityKey).getData(); 11 | return {children}; 12 | }; 13 | 14 | export default new CompositeDecorator([ 15 | { 16 | strategy: findEntity('LINK'), 17 | component: Link 18 | }, 19 | { 20 | strategy: findEntity('COLOR'), 21 | component: Color 22 | } 23 | ]); 24 | -------------------------------------------------------------------------------- /app/Editor/draft/helpers/findEntity.js: -------------------------------------------------------------------------------- 1 | import { Entity } from 'draft-js'; 2 | 3 | export default key => (contentBlock, callback) => 4 | contentBlock.findEntityRanges( 5 | (character) => { 6 | const entityKey = character.getEntity(); 7 | return ( 8 | entityKey !== null && 9 | Entity.get(entityKey).getType() === key 10 | ); 11 | }, 12 | callback 13 | ); 14 | -------------------------------------------------------------------------------- /app/Editor/draft/helpers/getSelectionCoords.js: -------------------------------------------------------------------------------- 1 | import { getVisibleSelectionRect } from 'draft-js'; 2 | 3 | export default function (editor) { 4 | const editorBounds = editor.getBoundingClientRect(); 5 | const rangeBounds = getVisibleSelectionRect(window); 6 | 7 | if (!rangeBounds) return null; 8 | 9 | const rangeWidth = rangeBounds.right - rangeBounds.left; 10 | 11 | // const rangeHeight = rangeBounds.bottom - rangeBounds.top; 12 | return { 13 | left: (rangeBounds.left - editorBounds.left) + (rangeWidth / 2), 14 | bottom: editorBounds.bottom - rangeBounds.bottom + rangeBounds.height 15 | }; 16 | } 17 | -------------------------------------------------------------------------------- /app/Editor/draft/helpers/renderOptions.js: -------------------------------------------------------------------------------- 1 | export default { 2 | styleToHTML: { 3 | left: { 4 | start: '', 5 | end: '' 6 | }, 7 | center: { 8 | start: '', 9 | end: '' 10 | }, 11 | right: { 12 | start: '', 13 | end: '' 14 | } 15 | }, 16 | blockToHTML: { 17 | PARAGRAPH: { 18 | start: '

    ', 19 | end: '

    ', 20 | empty: '
    ' 21 | } 22 | }, 23 | entityToHTML: (entity, originalText) => { 24 | switch (entity.type) { 25 | case 'COLOR': 26 | return `${originalText}`; 27 | case 'LINK': 28 | return `${originalText}`; 29 | default: 30 | return originalText; 31 | } 32 | } 33 | }; 34 | -------------------------------------------------------------------------------- /app/Editor/draft/index.js: -------------------------------------------------------------------------------- 1 | import { EditorState, Editor, convertToRaw, convertFromRaw } from 'draft-js'; 2 | import { stateFromHTML } from 'draft-js-import-html'; 3 | import { compose, withState, withHandlers, shouldUpdate } from 'recompose'; 4 | import { isString } from 'lodash'; 5 | import renderOptions from 'Editor/draft/helpers/renderOptions'; 6 | 7 | import Toolbar from './Toolbar'; 8 | import blockRenderMap from './helpers/blockRenderer'; 9 | import blockStyleFn from './helpers/blockStyleFn'; 10 | import styles from './styles.css'; 11 | import decorator from './helpers/decorator'; 12 | 13 | let editor = void 0; 14 | 15 | 16 | export default compose( 17 | shouldUpdate(() => false), 18 | withState('content', 'setContent', ({ value }) => { 19 | if (!value) return EditorState.createEmpty(decorator); 20 | if (value instanceof EditorState) return value; 21 | if (isString(value)) return EditorState.createWithContent(stateFromHTML(value), decorator); 22 | return EditorState.createWithContent(convertFromRaw(value.toJS ? value.toJS() : value)); 23 | }), 24 | withHandlers({ 25 | onChange: props => state => { 26 | props.setContent(state, props.onChange(convertToRaw(state.getCurrentContent()))); 27 | } 28 | }) 29 | )(({ content, onChange }) => ( 30 |
    editor = r}> 31 | 37 | 38 |
    39 | )); 40 | -------------------------------------------------------------------------------- /app/Editor/draft/styles.css: -------------------------------------------------------------------------------- 1 | .wrap{ 2 | margin: 0 auto; 3 | position: relative; 4 | } 5 | 6 | :global .public-DraftEditorPlaceholder-root{ 7 | position: absolute; 8 | color: color(#333 a(70%)); 9 | z-index: 1; 10 | margin-top: 1.5rem; 11 | } 12 | 13 | :global .DraftEditor-editorContainer{ 14 | position: relative; 15 | z-index: 2; 16 | } 17 | -------------------------------------------------------------------------------- /app/Editor/editorContext.js: -------------------------------------------------------------------------------- 1 | import { PropTypes } from 'react'; 2 | 3 | import { compose, withState, withContext, withHandlers } from 'recompose'; 4 | import Monitor from './immutable/dndMonitor'; 5 | import EditingItem from './immutable/editingItem'; 6 | 7 | const { func, object } = PropTypes; 8 | 9 | export const withEditorContext = BaseComponent => 10 | compose( 11 | withState('dragingItem', 'setDraggingItem', Monitor), 12 | withState('editingItem', 'editItem', EditingItem), 13 | withHandlers({ 14 | drag: props => dragItem => props.setDraggingItem(props.dragingItem.init(dragItem)), 15 | drop: props => () => props.setDraggingItem(props.dragingItem.reset()), 16 | hover: props => index => props.setDraggingItem(props.dragingItem.hover(index)), 17 | 18 | editContent: props => data => props.editItem(props.editingItem.editContent(data)), 19 | editSettings: props => data => props.editItem(props.editingItem.editSettings(data)), 20 | releaseItem: props => () => props.editItem(props.editingItem.release()), 21 | disableEdit: props => () => props.editItem(props.editingItem.disable()), 22 | enableEdit: props => () => props.editItem(props.editingItem.enable()) 23 | }), 24 | 25 | withContext( 26 | { 27 | dragingItem: object, 28 | drag: func, 29 | drop: func, 30 | editContent: func, 31 | editSettings: func, 32 | releaseItem: func, 33 | editingItem: object, 34 | disableEdit: func, 35 | enableEdit: func 36 | }, 37 | ({ dragingItem, drag, drop, releaseItem, editContent, editSettings, editingItem, disableEdit, enableEdit }) => 38 | ({ dragingItem, drag, drop, releaseItem, editContent, editSettings, editingItem, disableEdit, enableEdit }) 39 | ) 40 | )(BaseComponent); 41 | -------------------------------------------------------------------------------- /app/Editor/helpers/disableUpdate.js: -------------------------------------------------------------------------------- 1 | import { shouldUpdate } from 'recompose'; 2 | 3 | export default key => shouldUpdate((prev, next) => { 4 | return !prev.Cursor.equals(next.Cursor) 5 | }); 6 | -------------------------------------------------------------------------------- /app/Editor/helpers/editorState.js: -------------------------------------------------------------------------------- 1 | import { compose, getContext, withPropsOnChange } from 'recompose'; 2 | import { PropTypes } from 'react'; 3 | 4 | const { func, object } = PropTypes; 5 | 6 | export default compose( 7 | getContext({ 8 | updateEditorState: func, 9 | editContent: func, 10 | editSettings: func, 11 | releaseItem: func, 12 | editingItem: object, 13 | disableEdit: func, 14 | enableEdit: func 15 | }), 16 | 17 | withPropsOnChange(['Cursor'], props => ({ 18 | updateEditor: (key, state) => { 19 | const cursor = props.Cursor.push(key); 20 | console.log(cursor.join(' > ')); 21 | return props.updateEditorState(cursor, state); 22 | } 23 | })) 24 | ); 25 | -------------------------------------------------------------------------------- /app/Editor/helpers/fileProcessing.js: -------------------------------------------------------------------------------- 1 | import * as AWS from "aws-sdk"; 2 | import { imgSrcToBlob } from 'blob-util'; 3 | 4 | AWS.config.update({ 5 | accessKeyId: 'AKIAJGPORHCLVXDNN7OQ', 6 | secretAccessKey: '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' 7 | }); 8 | 9 | AWS.config.region = 'us-east-1'; 10 | 11 | export const uploadToAmazon = (file, aws, storeId, progressFunc) => 12 | new Promise(resolve => { 13 | const s3 = new AWS.S3(); 14 | s3.putObject({ 15 | Bucket: 'node2', 16 | Key: '', 17 | Body: file[0].preview, 18 | ACL:'public-read' 19 | }, function (err, data) { 20 | if (err) {throw err; } 21 | else { 22 | console.log('data', data); 23 | } 24 | }); 25 | }); -------------------------------------------------------------------------------- /app/Editor/helpers/imagesConvert.js: -------------------------------------------------------------------------------- 1 | import { imgSrcToDataURL, imgSrcToBlob } from 'blob-util'; 2 | 3 | const imgConvert = (images, fn, result = {}) => 4 | new Promise(resolve => { 5 | const promises = []; 6 | 7 | Object.keys(images).forEach(key => { 8 | if (!images[key].preview) return; 9 | 10 | promises.push( 11 | new Promise(async res => { 12 | const file = await fn(images[key].preview); 13 | return res({ [key]: file }); 14 | } 15 | )); 16 | }); 17 | 18 | Promise.all(promises).then(array => 19 | resolve(array.reduce((acc, image) => ({ ...acc, ...image }), result)) 20 | ); 21 | }); 22 | 23 | export const imagesToBase64 = (images) => imgConvert(images, imgSrcToDataURL); 24 | export const imagesToBlob = (images) => imgConvert(images, imgSrcToBlob); -------------------------------------------------------------------------------- /app/Editor/helpers/onClickOutside.js: -------------------------------------------------------------------------------- 1 | import { Component } from 'react'; 2 | import { findDOMNode } from 'react-dom'; 3 | import createHelper from 'recompose/createHelper'; 4 | 5 | const EVENTS = ['mousedown', 'touchstart']; 6 | const isNodeFound = (current, componentNode) => current === componentNode; 7 | 8 | const createClickHandler = callback => (componentNode, props) => e => { 9 | let current = e.target; 10 | 11 | while (current.parentNode) { 12 | if (isNodeFound(current, componentNode)) return; 13 | current = current.parentNode; 14 | } 15 | 16 | if (current !== document) return; 17 | callback(props); 18 | }; 19 | 20 | const onClickOutside = handler => BaseComponent => { 21 | const handlerCreator = createClickHandler(handler); 22 | 23 | return class HandleComponent extends Component { 24 | fn = void 0; 25 | componentDidMount() { 26 | this.fn = handlerCreator(findDOMNode(this.instance), this.props); 27 | if (typeof document !== 'undefined') { 28 | EVENTS.forEach(eventName => document.addEventListener(eventName, this.fn)); 29 | } 30 | } 31 | 32 | componentWillUnmount() { 33 | if (typeof document !== 'undefined') { 34 | EVENTS.forEach(eventName => document.removeEventListener(eventName, this.fn)); 35 | this.fn = void 0; 36 | } 37 | } 38 | 39 | render() { 40 | return ( 41 |
    this.instance = r}> 42 | 43 |
    44 | ); 45 | } 46 | }; 47 | }; 48 | 49 | export default createHelper(onClickOutside, 'onClickOutside'); 50 | -------------------------------------------------------------------------------- /app/Editor/helpers/previewWindow.js: -------------------------------------------------------------------------------- 1 | let openWindow = void 0; 2 | 3 | const mock = (content, css, script) => { 4 | return ` 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | Atomic Builder Preview 13 | 14 | 15 | ${content} 16 | 17 | 18 | 19 | `; 20 | }; 21 | 22 | const createWindow = content => { 23 | if (openWindow) openWindow.close(); 24 | openWindow = window.open('about:blank', 'Export from Atomic Builder'); 25 | const styles = document.getElementsByTagName('link') || []; 26 | const scripts = document.getElementsByTagName('script') || []; 27 | let css = ''; 28 | let script = ''; 29 | 30 | for (const i in styles){ 31 | const href = styles[i].getAttribute && styles[i].getAttribute('href'); 32 | if (href && href.includes('main')) css = href; 33 | } 34 | for (const i in scripts){ 35 | const src = scripts[i].getAttribute && scripts[i].getAttribute('src'); 36 | if (src && src.includes('widgets')) script = src; 37 | } 38 | const html = mock(content, css, script); 39 | openWindow.document.write(html); 40 | if (process.env.NODE_ENV === 'development') { 41 | const style = document.head.getElementsByTagName('style')[0]; 42 | openWindow.document.head.appendChild(style.cloneNode(true)); 43 | } 44 | openWindow.onBeforeUnload = () => openWindow = void 0; 45 | }; 46 | 47 | export default { 48 | create: createWindow, 49 | isOpen: () => !!openWindow 50 | }; 51 | -------------------------------------------------------------------------------- /app/Editor/icons/align_center.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /app/Editor/icons/align_left.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /app/Editor/icons/align_right.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /app/Editor/icons/bold.svg: -------------------------------------------------------------------------------- 1 | export default () => ( 2 | 3 | ); -------------------------------------------------------------------------------- /app/Editor/icons/chevron_left.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/Editor/icons/color.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/Editor/icons/cross.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /app/Editor/icons/font.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/Editor/icons/h1.svg: -------------------------------------------------------------------------------- 1 | H1 -------------------------------------------------------------------------------- /app/Editor/icons/h2.svg: -------------------------------------------------------------------------------- 1 | H2 -------------------------------------------------------------------------------- /app/Editor/icons/h3.svg: -------------------------------------------------------------------------------- 1 | H3 -------------------------------------------------------------------------------- /app/Editor/icons/h4.svg: -------------------------------------------------------------------------------- 1 | H4 -------------------------------------------------------------------------------- /app/Editor/icons/italic.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/Editor/icons/letterSpacing.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/Editor/icons/line-through.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/Editor/icons/lineHeight.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/Editor/icons/link.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/Editor/icons/lowercase.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/Editor/icons/ol.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/Editor/icons/quote.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/Editor/icons/size.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/Editor/icons/ul.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/Editor/icons/underline.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/Editor/icons/uppercase.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/Editor/immutable/animation.js: -------------------------------------------------------------------------------- 1 | import { Record } from 'immutable'; 2 | import { spring } from 'react-motion'; 3 | 4 | const springConfig = { stiffness: 300, damping: 50 }; 5 | 6 | export const computeStyles = ({ scale, y, x, ...rest }) => ({ 7 | transform: `translate3d(${x}px, ${y}px, 0) scale(${scale})`, 8 | WebkitTransform: `translate3d(${x}px, ${y}px, 0) scale(${scale})`, 9 | overflow: 'visible', 10 | position: 'relative', 11 | ...rest 12 | }); 13 | 14 | export default class Animation extends Record({ // eslint-disable-line new-cap 15 | scale: spring(1, springConfig), 16 | y: spring(0, springConfig), 17 | x: spring(0, springConfig), 18 | opacity: spring(1, springConfig) 19 | }) 20 | { 21 | animate(props, value, useConfig = true) { 22 | return this.set(props, useConfig ? spring(value, springConfig) : value); 23 | } 24 | get run() { 25 | return this.toJS() 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/Editor/immutable/dndMonitor.js: -------------------------------------------------------------------------------- 1 | import { Record } from 'immutable'; 2 | 3 | class Monitor extends Record({ // eslint-disable-line new-cap 4 | originalIndex: void 0, 5 | hoverIndex: void 0, 6 | cursor: void 0, 7 | isMounted: void 0, 8 | dragging: false, 9 | height: 0 10 | }) 11 | { 12 | init({ height, index, cursor }) { 13 | return this 14 | .set('height', height) 15 | .set('originalIndex', index) 16 | .set('cursor', cursor) 17 | .set('dragging', true); 18 | } 19 | 20 | hover(index) { 21 | return this 22 | .set('hoverIndex', index); 23 | } 24 | 25 | reset() { 26 | return this.set('dragging', false); 27 | } 28 | 29 | get isMounted() { 30 | return this.get('originalIndex') !== void 0; 31 | } 32 | 33 | get isDragging() { 34 | return this.get('dragging'); 35 | } 36 | } 37 | 38 | export default new Monitor(); 39 | -------------------------------------------------------------------------------- /app/Editor/immutable/editingItem.js: -------------------------------------------------------------------------------- 1 | import { Record } from 'immutable'; 2 | 3 | class EditingItem extends Record({ // eslint-disable-line new-cap 4 | active: false, 5 | type: '', 6 | editingContent: false, 7 | sidebarOpen: false, 8 | mapper: void 0, 9 | Cursor: void 0, 10 | canEdit: true 11 | }) 12 | { 13 | editSettings({ type, mapper, Cursor }) { 14 | return this 15 | .set('sidebarOpen', true) 16 | .set('type', type) 17 | .set('mapper', mapper) 18 | .set('Cursor', Cursor); 19 | } 20 | 21 | editContent(Cursor) { 22 | return this 23 | .set('sidebarOpen', false) 24 | .set('editingContent', true) 25 | .set('Cursor', Cursor); 26 | } 27 | 28 | release() { 29 | return this 30 | .set('sidebarOpen', false) 31 | .set('editingContent', false); 32 | } 33 | 34 | disable() { 35 | return this 36 | .set('canEdit', false); 37 | } 38 | 39 | enable() { 40 | return this 41 | .set('canEdit', true); 42 | } 43 | 44 | get isSidebarOpen() { 45 | return this.get('sidebarOpen'); 46 | } 47 | 48 | get isContentEditing() { 49 | return this.get('editingContent'); 50 | } 51 | 52 | get canDrag() { 53 | return this.get('canEdit') ? this.get('editingContent') : false; 54 | } 55 | } 56 | 57 | export default new EditingItem(); 58 | -------------------------------------------------------------------------------- /app/Editor/molecule.js: -------------------------------------------------------------------------------- 1 | import createMolecule from './creators/createMolecule'; 2 | 3 | export default data => ({ 4 | Component: createMolecule(data) 5 | }); 6 | -------------------------------------------------------------------------------- /app/Editor/organism.js: -------------------------------------------------------------------------------- 1 | import createPreview from './creators/createPreview'; 2 | import createOrganism from './creators/createOrganism'; 3 | 4 | export default data => ({ 5 | Preview: createPreview('organism', data), 6 | Component: createOrganism(data) 7 | }); 8 | -------------------------------------------------------------------------------- /app/app.js: -------------------------------------------------------------------------------- 1 | /** 2 | * app.js 3 | * 4 | * This is the entry file for the application, only setup and boilerplate 5 | * code. 6 | */ 7 | 8 | // Import all the third party stuff 9 | import React from 'react'; 10 | import ReactDOM from 'react-dom'; 11 | import { Provider } from 'react-redux'; 12 | import configureStore from './store'; 13 | 14 | // Create redux store with history 15 | // this uses the singleton browserHistory provided by react-router 16 | // Optionally, this could be changed to leverage a created history 17 | // e.g. `const browserHistory = useRouterHistory(createBrowserHistory)();` 18 | const initialState = {}; 19 | const store = configureStore(initialState); 20 | 21 | // Set up the router, wrapping all Routes in the App component 22 | import Root from './components/Root/'; 23 | 24 | ReactDOM.render( 25 | 26 | 27 | , 28 | document.getElementById('app') 29 | ); 30 | 31 | if (module.hot) { 32 | module.hot.accept('./components/Root/', () => { 33 | // If you use Webpack 2 in ES modules mode, you can 34 | // use here rather than require() a . 35 | const NextApp = require('./components/Root/').default; 36 | ReactDOM.render( 37 | 38 | 39 | , 40 | document.getElementById('app') 41 | ); 42 | }); 43 | } 44 | 45 | window.React = React; 46 | window.Perf = require('react-addons-perf'); 47 | -------------------------------------------------------------------------------- /app/components/Menu/Toggler/index.js: -------------------------------------------------------------------------------- 1 | import Icon from 'Editor/components/Icon'; 2 | import cx from 'classnames'; 3 | 4 | import styles from './styles.css'; 5 | 6 | export default ({ 7 | onClick, 8 | active 9 | }) => ( 10 | 13 | ); 14 | -------------------------------------------------------------------------------- /app/components/Menu/Toggler/styles.css: -------------------------------------------------------------------------------- 1 | .toogler{ 2 | width: 36px; 3 | height: 36px; 4 | border: none; 5 | z-index: 2; 6 | position: relative; 7 | border-radius: 50%; 8 | cursor: pointer; 9 | background-color: #f58577; 10 | outline: none; 11 | transform: rotate(45deg); 12 | transition: transform .3s ease-in-out, background-color .2s linear; 13 | svg{ 14 | width: 24px; 15 | display: inline-block; 16 | height: 25px; 17 | vertical-align: -5px; 18 | fill: white; 19 | } 20 | &.active{ 21 | transform: rotate(0deg); 22 | background-color: #51d4ac; 23 | transition: transform .3s ease-in, background-color .2s linear; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/components/Menu/styles.css: -------------------------------------------------------------------------------- 1 | .wrap{ 2 | position: fixed; 3 | top: -60px; 4 | width: 100%; 5 | z-index: 2; 6 | padding: 10px 0; 7 | display: flex; 8 | align-items: center; 9 | justify-content: center; 10 | &:before{ 11 | content: ' '; 12 | position: absolute; 13 | top: 0; 14 | bottom: -60px; 15 | left: 0; 16 | right: 0; 17 | visibility: hidden; 18 | background-color: color(#2E353D a(0%)); 19 | transition: background-color .1s ease-in; 20 | } 21 | } 22 | .wrapActive:before{ 23 | visibility: visible; 24 | background-color: color(#2E353D a(100%)); 25 | } 26 | .buttonsWrap{ 27 | margin: 0 auto; 28 | width: 100%; 29 | display: flex; 30 | justify-content: center; 31 | } 32 | .buttons{ 33 | padding: 15px 0; 34 | } 35 | 36 | .tabs{ 37 | position: relative; 38 | top: 60px; 39 | } 40 | 41 | .button{ 42 | color: color(white a(60%)); 43 | border: 0; 44 | margin-top: 5px; 45 | padding: 5px 10px; 46 | background: transparent; 47 | text-transform: uppercase; 48 | cursor: pointer; 49 | font-weight: 300; 50 | letter-spacing: 1px; 51 | outline: none; 52 | &:hover{ 53 | color: white; 54 | } 55 | &.active{ 56 | color: white; 57 | } 58 | &.hidden{ 59 | &:after{ 60 | opacity: 0; 61 | } 62 | } 63 | } 64 | 65 | .button__container { 66 | position: absolute!important; 67 | display: flex; 68 | top: 0; 69 | transition: color .2s ease-in-out; 70 | z-index: 1; 71 | &_left { 72 | right: 100%; 73 | } 74 | &_right { 75 | left: 100%; 76 | } 77 | } 78 | .button_organism{ 79 | } 80 | .button_atom{ 81 | } 82 | .button_templates { 83 | } 84 | .listWrap{ 85 | position: absolute; 86 | width: 100%; 87 | left: 0; 88 | top: 100%; 89 | margin-top: 60px; 90 | display: flex; 91 | justify-content: center; 92 | background: #485562; 93 | visibility: hidden; 94 | } 95 | .activeList{ 96 | visibility: visible; 97 | } 98 | 99 | .list{ 100 | height: 100px; 101 | padding: 10px; 102 | & > div { 103 | height: 100%; 104 | display: inline-block; 105 | margin: 0 5px; 106 | } 107 | & img { 108 | border-radius: 2px; 109 | height: 100%; 110 | max-width: 100%; 111 | } 112 | &__templates { 113 | & > div { 114 | width: 140px; 115 | height: 100px; 116 | vertical-align: top; 117 | text-align: center; 118 | } 119 | & img { 120 | max-width: 100%; 121 | } 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /app/components/Root/index.js: -------------------------------------------------------------------------------- 1 | import 'normalize.css/normalize.css'; 2 | 3 | // import Builder from '../Builder'; 4 | 5 | import { compose, withHandlers } from 'recompose'; 6 | import { connect } from 'react-redux'; 7 | 8 | import { Menu } from '../Menu'; 9 | import { withEditorContext } from 'Editor/editorContext'; 10 | import createEditor from 'Editor/creators/createEditor'; 11 | import selector from 'modules/builder/selectors'; 12 | import { DragDropContext } from 'react-dnd'; 13 | import HTML5Backend from 'react-dnd-html5-backend'; 14 | 15 | import styles from './styles.css'; 16 | const Editor = createEditor({ edit: true }); 17 | 18 | export const Root = compose( 19 | connect(selector), 20 | withEditorContext, 21 | DragDropContext(HTML5Backend), // eslint-disable-line new-cap 22 | )(({ blocks, ...props }) => { 23 | return ( 24 |
    25 | { 26 | props.editingItem.canEdit && 27 | } 28 | 29 |
    30 | ); 31 | } 32 | ); 33 | 34 | export default Root; -------------------------------------------------------------------------------- /app/components/Root/styles.css: -------------------------------------------------------------------------------- 1 | .wrap{ 2 | width: 100%; 3 | min-height: 100vh; 4 | } 5 | -------------------------------------------------------------------------------- /app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | React.js Boilerplate 12 | 13 | 14 | 15 |
    16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /app/modules/builder/actions.js: -------------------------------------------------------------------------------- 1 | import { createAction } from 'redux-act'; 2 | 3 | export const createBlock = createAction('builder.create.bclock'); 4 | -------------------------------------------------------------------------------- /app/modules/builder/index.js: -------------------------------------------------------------------------------- 1 | import { createReducer } from 'redux-act'; 2 | import * as actions from './actions'; 3 | 4 | const initialState = []; 5 | 6 | export default createReducer({ 7 | [actions.createBlock]: (s) => ({ ...s }), 8 | }, initialState); 9 | -------------------------------------------------------------------------------- /app/modules/builder/selectors.js: -------------------------------------------------------------------------------- 1 | import { createSelector } from 'reselect'; 2 | 3 | export default createSelector( 4 | s => s.builder, 5 | molecules => ({ blocks: molecules }) 6 | ); 7 | -------------------------------------------------------------------------------- /app/reducers.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Combine all reducers in this file and export the combined reducers. 3 | * If we were to do this in store.js, reducers wouldn't be hot reloadable. 4 | */ 5 | 6 | import { combineReducers } from 'redux'; 7 | 8 | import builder from './modules/builder'; 9 | 10 | /** 11 | * Creates the main reducer with the asynchronously loaded ones 12 | */ 13 | export default function createReducer(asyncReducers) { 14 | return combineReducers({ 15 | builder, 16 | ...asyncReducers, 17 | }); 18 | } 19 | -------------------------------------------------------------------------------- /app/store.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Create the store with asynchronously loaded reducers 3 | */ 4 | 5 | import { createStore, applyMiddleware, compose } from 'redux'; 6 | import createReducer from './reducers'; 7 | 8 | const devtools = window.devToolsExtension || (() => noop => noop); 9 | 10 | export default function configureStore(initialState = {}) { 11 | const middlewares = []; 12 | 13 | const enhancers = [ 14 | applyMiddleware(...middlewares), 15 | devtools(), 16 | ]; 17 | 18 | const store = createStore( 19 | createReducer(), 20 | initialState, 21 | compose(...enhancers) 22 | ); 23 | 24 | // Extensions 25 | store.asyncReducers = {}; // Async reducer registry 26 | 27 | // Make reducers hot reloadable, see http://mxs.is/googmo 28 | /* istanbul ignore next */ 29 | if (module.hot) { 30 | module.hot.accept('./reducers', () => { 31 | System.import('./reducers').then((reducerModule) => { 32 | const createReducers = reducerModule.default; 33 | const nextReducers = createReducers(store.asyncReducers); 34 | 35 | store.replaceReducer(nextReducers); 36 | }); 37 | }); 38 | } 39 | 40 | return store; 41 | } 42 | -------------------------------------------------------------------------------- /app/utils/asyncInjectors.js: -------------------------------------------------------------------------------- 1 | import createReducer from '../reducers'; 2 | 3 | /** 4 | * Inject an asynchronously loaded reducer 5 | */ 6 | export function injectAsyncReducer(store) { 7 | return (name, asyncReducer) => { 8 | store.asyncReducers[name] = asyncReducer; // eslint-disable-line no-param-reassign 9 | store.replaceReducer(createReducer(store.asyncReducers)); 10 | }; 11 | } 12 | 13 | /** 14 | * Inject an asynchronously loaded saga 15 | */ 16 | export function injectAsyncSagas(store) { 17 | return (sagas) => sagas.map(store.runSaga); 18 | } 19 | 20 | /** 21 | * Helper for creating injectors 22 | */ 23 | export function getAsyncInjectors(store) { 24 | return { 25 | injectReducer: injectAsyncReducer(store), 26 | injectSagas: injectAsyncSagas(store), 27 | }; 28 | } 29 | -------------------------------------------------------------------------------- /app/utils/request.js: -------------------------------------------------------------------------------- 1 | import 'whatwg-fetch'; 2 | 3 | /** 4 | * Parses the JSON returned by a network request 5 | * 6 | * @param {object} response A response from a network request 7 | * 8 | * @return {object} The parsed JSON from the request 9 | */ 10 | function parseJSON(response) { 11 | return response.json(); 12 | } 13 | 14 | /** 15 | * Checks if a network request came back fine, and throws an error if not 16 | * 17 | * @param {object} response A response from a network request 18 | * 19 | * @return {object|undefined} Returns either the response, or throws an error 20 | */ 21 | function checkStatus(response) { 22 | if (response.status >= 200 && response.status < 300) { 23 | return response; 24 | } 25 | 26 | const error = new Error(response.statusText); 27 | error.response = response; 28 | throw error; 29 | } 30 | 31 | /** 32 | * Requests a URL, returning a promise 33 | * 34 | * @param {string} url The URL we want to request 35 | * @param {object} [options] The options we want to pass to "fetch" 36 | * 37 | * @return {object} An object containing either "data" or "err" 38 | */ 39 | export default function request(url, options) { 40 | return fetch(url, options) 41 | .then(checkStatus) 42 | .then(parseJSON) 43 | .then((data) => ({ data })) 44 | .catch((err) => ({ err })); 45 | } 46 | -------------------------------------------------------------------------------- /app/utils/tests/asyncInjectors.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Test async injectors 3 | */ 4 | 5 | import expect from 'expect'; 6 | import configureStore from '../../store'; 7 | import { memoryHistory } from 'react-router'; 8 | import { put } from 'redux-saga/effects'; 9 | import { fromJS } from 'immutable'; 10 | 11 | import { 12 | injectAsyncReducer, 13 | injectAsyncSagas, 14 | getAsyncInjectors, 15 | } from '../asyncInjectors'; 16 | 17 | // Fixtures 18 | 19 | const initialState = fromJS({ reduced: 'soon' }); 20 | 21 | const reducer = (state = initialState, action) => { 22 | switch (action.type) { 23 | case 'TEST': 24 | return state.set('reduced', action.payload); 25 | default: 26 | return state; 27 | } 28 | }; 29 | 30 | const sagas = [ 31 | function* testSaga() { 32 | yield put({ type: 'TEST', payload: 'yup' }); 33 | }, 34 | ]; 35 | 36 | describe('asyncInjectors', () => { 37 | let store; 38 | 39 | describe('getAsyncInjectors', () => { 40 | before(() => { 41 | store = configureStore({}, memoryHistory); 42 | }); 43 | 44 | it('given a store, should return all async injectors', () => { 45 | const { injectReducer, injectSagas } = getAsyncInjectors(store); 46 | 47 | injectReducer('test', reducer); 48 | injectSagas(sagas); 49 | 50 | const actual = store.getState().get('test'); 51 | const expected = initialState.merge({ reduced: 'yup' }); 52 | 53 | expect(actual.toJS()).toEqual(expected.toJS()); 54 | }); 55 | }); 56 | 57 | describe('helpers', () => { 58 | before(() => { 59 | store = configureStore({}, memoryHistory); 60 | }); 61 | 62 | describe('injectAsyncReducer', () => { 63 | it('given a store, it should provide a function to inject a reducer', () => { 64 | const injectReducer = injectAsyncReducer(store); 65 | 66 | injectReducer('test', reducer); 67 | 68 | const actual = store.getState().get('test'); 69 | const expected = initialState; 70 | 71 | expect(actual.toJS()).toEqual(expected.toJS()); 72 | }); 73 | }); 74 | 75 | describe('injectAsyncSagas', () => { 76 | it('given a store, it should provide a function to inject a saga', () => { 77 | const injectSagas = injectAsyncSagas(store); 78 | 79 | injectSagas(sagas); 80 | 81 | const actual = store.getState().get('test'); 82 | const expected = initialState.merge({ reduced: 'yup' }); 83 | 84 | expect(actual.toJS()).toEqual(expected.toJS()); 85 | }); 86 | }); 87 | }); 88 | }); 89 | -------------------------------------------------------------------------------- /app/utils/tests/request.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Test the request function 3 | */ 4 | 5 | import request from '../request'; 6 | import sinon from 'sinon'; 7 | import expect from 'expect'; 8 | 9 | describe('request', () => { 10 | // Before each test, stub the fetch function 11 | beforeEach(() => { 12 | sinon.stub(window, 'fetch'); 13 | }); 14 | 15 | // After each test, restore the fetch function 16 | afterEach(() => { 17 | window.fetch.restore(); 18 | }); 19 | 20 | describe('stubbing successful response', () => { 21 | // Before each test, pretend we got a successful response 22 | beforeEach(() => { 23 | const res = new Response('{"hello":"world"}', { 24 | status: 200, 25 | headers: { 26 | 'Content-type': 'application/json', 27 | }, 28 | }); 29 | 30 | window.fetch.returns(Promise.resolve(res)); 31 | }); 32 | 33 | it('should format the response correctly', (done) => { 34 | request('/thisurliscorrect') 35 | .catch(done) 36 | .then((json) => { 37 | expect(json.data.hello).toEqual('world'); 38 | done(); 39 | }); 40 | }); 41 | }); 42 | 43 | describe('stubbing error response', () => { 44 | // Before each test, pretend we got an unsuccessful response 45 | beforeEach(() => { 46 | const res = new Response('', { 47 | status: 404, 48 | statusText: 'Not Found', 49 | headers: { 50 | 'Content-type': 'application/json', 51 | }, 52 | }); 53 | 54 | window.fetch.returns(Promise.resolve(res)); 55 | }); 56 | 57 | it('should catch errors', (done) => { 58 | request('/thisdoesntexist') 59 | .then((json) => { 60 | expect(json.err.response.status).toEqual(404); 61 | expect(json.err.response.statusText).toEqual('Not Found'); 62 | done(); 63 | }); 64 | }); 65 | }); 66 | }); 67 | -------------------------------------------------------------------------------- /app/widgets.js: -------------------------------------------------------------------------------- 1 | import ReactDOM from 'react-dom'; 2 | import Player from 'react-player'; 3 | 4 | const videos = document.querySelectorAll('div.atomic-video'); 5 | 6 | if (videos && videos.length) { 7 | videos.forEach((node, i) => { 8 | const data = node.dataset; 9 | videos[i].innerHTML = ''; 10 | const wrapper = document.createElement('div'); 11 | videos[i].appendChild(wrapper) 12 | // const holder = videos[i].appendChild 13 | ReactDOM.render( 14 |
    , 15 | wrapper 16 | ); 17 | }); 18 | } 19 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | # http://www.appveyor.com/docs/appveyor-yml 2 | 3 | # Set build version format here instead of in the admin panel 4 | version: "{build}" 5 | 6 | # Do not build on gh tags 7 | skip_tags: true 8 | 9 | # Test against these versions of Node.js 10 | environment: 11 | 12 | matrix: 13 | # Node versions to run 14 | - nodejs_version: "5.0" 15 | 16 | # Install scripts--runs after repo cloning 17 | install: 18 | # Install chrome 19 | - choco install -y googlechrome 20 | # Install the latest stable version of Node 21 | - ps: Install-Product node $env:nodejs_version 22 | - npm -g install npm 23 | - set PATH=%APPDATA%\npm;%PATH% 24 | - npm install 25 | 26 | # Disable automatic builds 27 | build: off 28 | 29 | # Post-install test scripts 30 | test_script: 31 | # Output debugging info 32 | - node --version 33 | - npm --version 34 | # run build and run tests 35 | - npm run build 36 | 37 | # remove, as appveyor doesn't support secure variables on pr builds 38 | # so `COVERALLS_REPO_TOKEN` cannot be set, without hard-coding in this file 39 | #on_success: 40 | #- npm run coveralls 41 | 42 | -------------------------------------------------------------------------------- /docs/Atomic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Uscreen-video/atomic-builder/ca83a73d7b496e29032d5549fd19e09ee2fdb3cc/docs/Atomic.png -------------------------------------------------------------------------------- /internals/config.js: -------------------------------------------------------------------------------- 1 | const resolve = require('path').resolve; 2 | const pullAll = require('lodash/pullAll'); 3 | const uniq = require('lodash/uniq'); 4 | 5 | const ReactBoilerplate = { 6 | // This refers to the react-boilerplate version this project is based on. 7 | version: '3.0.0', 8 | 9 | /** 10 | * The DLL Plugin provides a dramatic speed increase to webpack build and hot module reloading 11 | * by caching the module metadata for all of our npm dependencies. We enable it by default 12 | * in development. 13 | * 14 | * 15 | * To disable the DLL Plugin, set this value to false. 16 | */ 17 | dllPlugin: { 18 | defaults: { 19 | /** 20 | * we need to exclude dependencies which are not intended for the browser 21 | * by listing them here. 22 | */ 23 | exclude: [ 24 | 'chalk', 25 | 'compression', 26 | 'cross-env', 27 | 'express', 28 | 'ip', 29 | 'minimist', 30 | 'sanitize.css', 31 | ], 32 | 33 | /** 34 | * Specify any additional dependencies here. We include core-js and lodash 35 | * since a lot of our dependencies depend on them and they get picked up by webpack. 36 | */ 37 | include: ['core-js', 'eventsource-polyfill', 'babel-polyfill', 'lodash'], 38 | 39 | // The path where the DLL manifest and bundle will get built 40 | path: resolve('../node_modules/react-boilerplate-dlls'), 41 | }, 42 | 43 | entry(pkg) { 44 | const dependencyNames = Object.keys(pkg.dependencies); 45 | const exclude = pkg.dllPlugin.exclude || ReactBoilerplate.dllPlugin.defaults.exclude; 46 | const include = pkg.dllPlugin.include || ReactBoilerplate.dllPlugin.defaults.include; 47 | const includeDependencies = uniq(dependencyNames.concat(include)); 48 | 49 | return { 50 | reactBoilerplateDeps: pullAll(includeDependencies, exclude), 51 | }; 52 | }, 53 | }, 54 | }; 55 | 56 | module.exports = ReactBoilerplate; 57 | -------------------------------------------------------------------------------- /internals/generators/component/es6.js.hbs: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * {{ properCase name }} 4 | * 5 | */ 6 | 7 | import React from 'react'; 8 | 9 | {{#if wantCSS}} 10 | import styles from './styles.css'; 11 | {{/if}} 12 | 13 | class {{ properCase name }} extends React.Component { 14 | render() { 15 | return ( 16 | {{#if wantCSS}} 17 |
    18 | {{else}} 19 |
    20 | {{/if}} 21 |
    22 | ); 23 | } 24 | } 25 | 26 | export default {{ properCase name }}; 27 | -------------------------------------------------------------------------------- /internals/generators/component/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Component Generator 3 | */ 4 | 5 | const componentExists = require('../utils/componentExists'); 6 | 7 | module.exports = { 8 | description: 'Add an unconnected component', 9 | prompts: [{ 10 | type: 'list', 11 | name: 'type', 12 | message: 'Select the type of component', 13 | default: 'Stateless Function', 14 | choices: () => ['ES6 Class', 'Stateless Function'], 15 | }, { 16 | type: 'input', 17 | name: 'name', 18 | message: 'What should it be called?', 19 | default: 'Button', 20 | validate: value => { 21 | if ((/.+/).test(value)) { 22 | return componentExists(value) ? 'A component or container with this name already exists' : true; 23 | } 24 | 25 | return 'The name is required'; 26 | }, 27 | }, { 28 | type: 'confirm', 29 | name: 'wantCSS', 30 | default: true, 31 | message: 'Does it have styling?', 32 | }], 33 | actions: data => { 34 | // Generate index.js and index.test.js 35 | const actions = [{ 36 | type: 'add', 37 | path: '../../app/components/{{properCase name}}/index.js', 38 | templateFile: data.type === 'ES6 Class' ? './component/es6.js.hbs' : './component/stateless.js.hbs', 39 | abortOnFail: true, 40 | }, { 41 | type: 'add', 42 | path: '../../app/components/{{properCase name}}/tests/index.test.js', 43 | templateFile: './component/test.js.hbs', 44 | abortOnFail: true, 45 | }]; 46 | 47 | // If they want a CSS file, add styles.css 48 | if (data.wantCSS) { 49 | actions.push({ 50 | type: 'add', 51 | path: '../../app/components/{{properCase name}}/styles.css', 52 | templateFile: './component/styles.css.hbs', 53 | abortOnFail: true, 54 | }); 55 | } 56 | 57 | return actions; 58 | }, 59 | }; 60 | -------------------------------------------------------------------------------- /internals/generators/component/stateless.js.hbs: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * {{ properCase name }} 4 | * 5 | */ 6 | 7 | import React from 'react'; 8 | 9 | {{#if wantCSS}} 10 | import styles from './styles.css'; 11 | {{/if}} 12 | 13 | function {{ properCase name }}() { 14 | return ( 15 | {{#if wantCSS}} 16 |
    17 | {{else}} 18 |
    19 | {{/if}} 20 |
    21 | ); 22 | } 23 | 24 | export default {{ properCase name }}; 25 | -------------------------------------------------------------------------------- /internals/generators/component/styles.css.hbs: -------------------------------------------------------------------------------- 1 | .{{ camelCase name }} { 2 | 3 | } 4 | -------------------------------------------------------------------------------- /internals/generators/component/test.js.hbs: -------------------------------------------------------------------------------- 1 | import {{ properCase name }} from '../index'; 2 | 3 | import expect from 'expect'; 4 | import { shallow } from 'enzyme'; 5 | import React from 'react'; 6 | 7 | describe('<{{ properCase name }} />', () => { 8 | 9 | }); 10 | -------------------------------------------------------------------------------- /internals/generators/container/actions.js.hbs: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * {{ properCase name }} actions 4 | * 5 | */ 6 | 7 | import { 8 | DEFAULT_ACTION, 9 | } from './constants'; 10 | 11 | export function defaultAction() { 12 | return { 13 | type: DEFAULT_ACTION, 14 | }; 15 | } 16 | -------------------------------------------------------------------------------- /internals/generators/container/actions.test.js.hbs: -------------------------------------------------------------------------------- 1 | import expect from 'expect'; 2 | import { 3 | defaultAction, 4 | } from '../actions'; 5 | import { 6 | DEFAULT_ACTION, 7 | } from '../constants'; 8 | 9 | describe('{{ properCase name }} actions', () => { 10 | describe('Default Action', () => { 11 | it('has a type of DEFAULT_ACTION', () => { 12 | const expected = { 13 | type: DEFAULT_ACTION, 14 | }; 15 | expect(defaultAction()).toEqual(expected); 16 | }); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /internals/generators/container/constants.js.hbs: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * {{ properCase name }} constants 4 | * 5 | */ 6 | 7 | export const DEFAULT_ACTION = 'app/{{ properCase name }}/DEFAULT_ACTION'; 8 | -------------------------------------------------------------------------------- /internals/generators/container/index.js.hbs: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * {{ properCase name }} 4 | * 5 | */ 6 | 7 | import React from 'react'; 8 | import { connect } from 'react-redux'; 9 | {{#if wantActionsAndReducer}} 10 | import select{{properCase name}} from './selectors'; 11 | {{/if}} 12 | {{#if wantCSS}} 13 | import styles from './styles.css'; 14 | {{/if}} 15 | 16 | export class {{ properCase name }} extends React.Component { // eslint-disable-line react/prefer-stateless-function 17 | render() { 18 | return ( 19 | {{#if wantCSS}} 20 |
    21 | {{else}} 22 |
    23 | {{/if}} 24 | This is {{properCase name}} container ! 25 |
    26 | ); 27 | } 28 | } 29 | 30 | {{#if wantActionsAndReducer}} 31 | const mapStateToProps = select{{properCase name}}(); 32 | {{/if}} 33 | 34 | function mapDispatchToProps(dispatch) { 35 | return { 36 | dispatch, 37 | }; 38 | } 39 | 40 | {{#if wantActionsAndReducer}} 41 | export default connect(mapStateToProps, mapDispatchToProps)({{ properCase name }}); 42 | {{else}} 43 | export default connect(mapDispatchToProps)({{ properCase name }}); 44 | {{/if}} 45 | -------------------------------------------------------------------------------- /internals/generators/container/reducer.js.hbs: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * {{ properCase name }} reducer 4 | * 5 | */ 6 | 7 | import { fromJS } from 'immutable'; 8 | import { 9 | DEFAULT_ACTION, 10 | } from './constants'; 11 | 12 | const initialState = fromJS({}); 13 | 14 | function {{ camelCase name }}Reducer(state = initialState, action) { 15 | switch (action.type) { 16 | case DEFAULT_ACTION: 17 | return state; 18 | default: 19 | return state; 20 | } 21 | } 22 | 23 | export default {{ camelCase name }}Reducer; 24 | -------------------------------------------------------------------------------- /internals/generators/container/reducer.test.js.hbs: -------------------------------------------------------------------------------- 1 | import expect from 'expect'; 2 | import {{ camelCase name }}Reducer from '../reducer'; 3 | import { fromJS } from 'immutable'; 4 | 5 | describe('{{ camelCase name }}Reducer', () => { 6 | it('returns the initial state', () => { 7 | expect({{ camelCase name }}Reducer(undefined, {})).toEqual(fromJS({})); 8 | }); 9 | }); 10 | -------------------------------------------------------------------------------- /internals/generators/container/sagas.js.hbs: -------------------------------------------------------------------------------- 1 | import { take, call, put, select } from 'redux-saga/effects'; 2 | 3 | // All sagas to be loaded 4 | export default [ 5 | defaultSaga, 6 | ]; 7 | 8 | // Individual exports for testing 9 | export function* defaultSaga() { 10 | 11 | } 12 | -------------------------------------------------------------------------------- /internals/generators/container/sagas.test.js.hbs: -------------------------------------------------------------------------------- 1 | /** 2 | * Test sagas 3 | */ 4 | 5 | import expect from 'expect'; 6 | import { take, call, put, select } from 'redux-saga/effects'; 7 | import { defaultSaga } from '../sagas'; 8 | 9 | const generator = defaultSaga(); 10 | 11 | describe('defaultSaga Saga', () => { 12 | it('should .....', () => { 13 | 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /internals/generators/container/selectors.js.hbs: -------------------------------------------------------------------------------- 1 | import { createSelector } from 'reselect'; 2 | 3 | /** 4 | * Direct selector to the {{ camelCase name }} state domain 5 | */ 6 | const select{{ properCase name }}Domain = () => state => state.get('{{ camelCase name }}'); 7 | 8 | /** 9 | * Other specific selectors 10 | */ 11 | 12 | 13 | /** 14 | * Default selector used by {{ properCase name }} 15 | */ 16 | 17 | const select{{ properCase name }} = () => createSelector( 18 | select{{ properCase name }}Domain(), 19 | (substate) => substate.toJS() 20 | ); 21 | 22 | export default select{{ properCase name }}; 23 | export { 24 | select{{ properCase name }}Domain, 25 | }; 26 | -------------------------------------------------------------------------------- /internals/generators/container/selectors.test.js.hbs: -------------------------------------------------------------------------------- 1 | import { 2 | select{{ properCase name }}, 3 | } from '../selectors'; 4 | import { fromJS } from 'immutable'; 5 | import expect from 'expect'; 6 | 7 | const selector = select{{ properCase name}}(); 8 | 9 | describe('select{{ properCase name }}', () => { 10 | 11 | }); 12 | -------------------------------------------------------------------------------- /internals/generators/container/styles.css.hbs: -------------------------------------------------------------------------------- 1 | .{{ camelCase name }} { 2 | 3 | } 4 | -------------------------------------------------------------------------------- /internals/generators/container/test.js.hbs: -------------------------------------------------------------------------------- 1 | import {{ properCase name }} from '../index'; 2 | 3 | import expect from 'expect'; 4 | import { shallow } from 'enzyme'; 5 | import React from 'react'; 6 | 7 | describe('<{{ properCase name }} />', () => { 8 | 9 | }); 10 | -------------------------------------------------------------------------------- /internals/generators/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * generator/index.js 3 | * 4 | * Exports the generators so plop knows them 5 | */ 6 | 7 | const fs = require('fs'); 8 | const componentGenerator = require('./component/index.js'); 9 | const containerGenerator = require('./container/index.js'); 10 | const routeGenerator = require('./route/index.js'); 11 | 12 | module.exports = (plop) => { 13 | plop.setGenerator('component', componentGenerator); 14 | plop.setGenerator('container', containerGenerator); 15 | plop.setGenerator('route', routeGenerator); 16 | plop.addHelper('directory', (comp) => { 17 | try { 18 | fs.accessSync(`app/containers/${comp}`, fs.F_OK); 19 | return `containers/${comp}`; 20 | } catch (e) { 21 | return `components/${comp}`; 22 | } 23 | }); 24 | plop.addHelper('curly', (object, open) => (open ? '{' : '}')); 25 | }; 26 | -------------------------------------------------------------------------------- /internals/generators/route/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Route Generator 3 | */ 4 | 5 | const fs = require('fs'); 6 | const componentExists = require('../utils/componentExists'); 7 | 8 | function reducerExists(comp) { 9 | try { 10 | fs.accessSync(`app/containers/${comp}/reducer.js`, fs.F_OK); 11 | return true; 12 | } catch (e) { 13 | return false; 14 | } 15 | } 16 | 17 | module.exports = { 18 | description: 'Add a route', 19 | prompts: [{ 20 | type: 'input', 21 | name: 'component', 22 | message: 'Which component should the route show?', 23 | validate: value => { 24 | if ((/.+/).test(value)) { 25 | return componentExists(value) ? true : `"${value}" doesn't exist.`; 26 | } 27 | 28 | return 'The path is required'; 29 | }, 30 | }, { 31 | type: 'input', 32 | name: 'path', 33 | message: 'Enter the path of the route.', 34 | default: '/about', 35 | validate: value => { 36 | if ((/.+/).test(value)) { 37 | return true; 38 | } 39 | 40 | return 'path is required'; 41 | }, 42 | }], 43 | 44 | // Add the route to the routes.js file above the error route 45 | // TODO smarter route adding 46 | actions: data => { 47 | const actions = []; 48 | if (reducerExists(data.component)) { 49 | actions.push({ 50 | type: 'modify', 51 | path: '../../app/routes.js', 52 | pattern: /(\s{\n\s{0,}path: '\*',)/g, 53 | templateFile: './route/routeWithReducer.hbs', 54 | }); 55 | } else { 56 | actions.push({ 57 | type: 'modify', 58 | path: '../../app/routes.js', 59 | pattern: /(\s{\n\s{0,}path: '\*',)/g, 60 | templateFile: './route/route.hbs', 61 | }); 62 | } 63 | 64 | return actions; 65 | }, 66 | }; 67 | -------------------------------------------------------------------------------- /internals/generators/route/route.hbs: -------------------------------------------------------------------------------- 1 | { 2 | path: '{{ path }}', 3 | getComponent(location, cb) { 4 | System.import('{{{directory (properCase component)}}}') 5 | .then(loadModule(cb)) 6 | .catch(errorLoading); 7 | }, 8 | },$1 9 | -------------------------------------------------------------------------------- /internals/generators/route/routeWithReducer.hbs: -------------------------------------------------------------------------------- 1 | { 2 | path: '{{ path }}', 3 | name: '{{ camelCase component }}', 4 | getComponent(nextState, cb) { 5 | const importModules = Promise.all([ 6 | System.import('containers/{{ properCase component }}/reducer'), 7 | System.import('containers/{{ properCase component }}/sagas'), 8 | System.import('containers/{{ properCase component }}'), 9 | ]); 10 | 11 | const renderRoute = loadModule(cb); 12 | 13 | importModules.then(([reducer, sagas, component]) => { 14 | injectReducer('{{ camelCase component }}', reducer.default); 15 | injectSagas(sagas.default); 16 | renderRoute(component); 17 | }); 18 | 19 | importModules.catch(errorLoading); 20 | }, 21 | },$1 22 | -------------------------------------------------------------------------------- /internals/generators/utils/componentExists.js: -------------------------------------------------------------------------------- 1 | /** 2 | * componentExists 3 | * 4 | * Check whether the given component exist in either the components or containers directory 5 | */ 6 | 7 | const fs = require('fs'); 8 | const pageComponents = fs.readdirSync('app/components'); 9 | const pageContainers = fs.readdirSync('app/containers'); 10 | const components = pageComponents.concat(pageContainers); 11 | 12 | function componentExists(comp) { 13 | return components.indexOf(comp) >= 0; 14 | } 15 | 16 | module.exports = componentExists; 17 | -------------------------------------------------------------------------------- /internals/scripts/analyze.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var shelljs = require('shelljs'); 4 | var animateProgress = require('./helpers/progress'); 5 | var chalk = require('chalk'); 6 | var addCheckMark = require('./helpers/checkmark'); 7 | 8 | var progress = animateProgress('Generating stats'); 9 | 10 | // Generate stats.json file with webpack 11 | shelljs.exec( 12 | 'webpack --config internals/webpack/webpack.prod.babel.js --profile --json > stats.json', 13 | addCheckMark.bind(null, callback) // Output a checkmark on completion 14 | ); 15 | 16 | // Called after webpack has finished generating the stats.json file 17 | function callback() { 18 | clearInterval(progress); 19 | process.stdout.write( 20 | '\n\nOpen ' + chalk.magenta('http://webpack.github.io/analyse/') + ' in your browser and upload the stats.json file!' + 21 | chalk.blue('\n(Tip: ' + chalk.italic('CMD + double-click') + ' the link!)\n\n') 22 | ); 23 | } 24 | -------------------------------------------------------------------------------- /internals/scripts/clean.js: -------------------------------------------------------------------------------- 1 | require('shelljs/global'); 2 | 3 | /** 4 | * Adds mark check symbol 5 | */ 6 | function addCheckMark(callback) { 7 | process.stdout.write(' ✓'); 8 | callback(); 9 | } 10 | 11 | if (!which('git')) { 12 | echo('Sorry, this script requires git'); 13 | exit(1); 14 | } 15 | 16 | if (!test('-e', 'internals/templates')) { 17 | echo('The example is deleted already.'); 18 | exit(1); 19 | } 20 | 21 | process.stdout.write('Cleanup started...'); 22 | 23 | // Cleanup components folder 24 | rm('-rf', 'app/components/*'); 25 | 26 | // Cleanup containers folder 27 | rm('-rf', 'app/containers/*'); 28 | mkdir('app/containers/App'); 29 | mkdir('app/containers/NotFoundPage'); 30 | mkdir('app/containers/HomePage'); 31 | cp('internals/templates/appContainer.js', 'app/containers/App/index.js'); 32 | cp('internals/templates/styles.css', 'app/containers/App/styles.css'); 33 | cp('internals/templates/notFoundPage.js', 'app/containers/NotFoundPage/index.js'); 34 | cp('internals/templates/homePage.js', 'app/containers/HomePage/index.js'); 35 | 36 | // Copy selectors 37 | mkdir('app/containers/App/tests'); 38 | cp('internals/templates/selectors.js', 39 | 'app/containers/App/selectors.js'); 40 | cp('internals/templates/selectors.test.js', 41 | 'app/containers/App/tests/selectors.test.js'); 42 | 43 | // Utils 44 | rm('-rf', 'app/utils'); 45 | mkdir('app/utils'); 46 | mkdir('app/utils/tests'); 47 | cp('internals/templates/asyncInjectors.js', 48 | 'app/utils/asyncInjectors.js'); 49 | cp('internals/templates/asyncInjectors.test.js', 50 | 'app/utils/tests/asyncInjectors.test.js'); 51 | 52 | // Replace the files in the root app/ folder 53 | cp('internals/templates/app.js', 'app/app.js'); 54 | cp('internals/templates/index.html', 'app/index.html'); 55 | cp('internals/templates/reducers.js', 'app/reducers.js'); 56 | cp('internals/templates/routes.js', 'app/routes.js'); 57 | cp('internals/templates/store.js', 'app/store.js'); 58 | cp('internals/templates/store.test.js', 'app/store.test.js'); 59 | 60 | // Remove the templates folder 61 | rm('-rf', 'internals/templates'); 62 | 63 | process.stdout.write(' ✓'); 64 | 65 | // Commit the changes 66 | if (exec('git add . --all && git commit -qm "Remove default example"').code !== 0) { 67 | echo('\nError: Git commit failed'); 68 | exit(1); 69 | } 70 | 71 | echo('\nCleanup done. Happy Coding!!!'); 72 | -------------------------------------------------------------------------------- /internals/scripts/dependencies.js: -------------------------------------------------------------------------------- 1 | /*eslint-disable*/ 2 | 3 | // No need to build the DLL in production 4 | if (process.env.NODE_ENV === 'production') { 5 | process.exit(0) 6 | } 7 | 8 | require('shelljs/global') 9 | 10 | const path = require('path') 11 | const fs = require('fs') 12 | const exists = fs.existsSync 13 | const writeFile = fs.writeFileSync 14 | 15 | const defaults = require('lodash/defaultsDeep') 16 | const pkg = require(path.join(process.cwd(), 'package.json')) 17 | const config = require('../config') 18 | const dllConfig = defaults(pkg.dllPlugin, config.dllPlugin.defaults) 19 | const outputPath = path.join(process.cwd(), dllConfig.path) 20 | const dllManifestPath = path.join(outputPath, 'package.json') 21 | 22 | /** 23 | * I use node_modules/react-boilerplate-dlls by default just because 24 | * it isn't going to be version controlled and babel wont try to parse it. 25 | */ 26 | mkdir('-p', outputPath) 27 | 28 | echo('Building the Webpack DLL...') 29 | 30 | /** 31 | * Create a manifest so npm install doesnt warn us 32 | */ 33 | if (!exists(dllManifestPath)) { 34 | writeFile( 35 | dllManifestPath, 36 | JSON.stringify(defaults({ 37 | name: 'react-boilerplate-dlls', 38 | private: true, 39 | author: pkg.author, 40 | repository: pkg.repository, 41 | version: pkg.version 42 | }), null, 2), 43 | 44 | 'utf8' 45 | ) 46 | } 47 | 48 | // the BUILDING_DLL env var is set to avoid confusing the development environment 49 | exec('cross-env BUILDING_DLL=true webpack --display-chunks --color --config internals/webpack/webpack.dll.babel.js') 50 | -------------------------------------------------------------------------------- /internals/scripts/helpers/checkmark.js: -------------------------------------------------------------------------------- 1 | var chalk = require('chalk'); 2 | 3 | /** 4 | * Adds mark check symbol 5 | */ 6 | function addCheckMark(callback) { 7 | process.stdout.write(chalk.green(' ✓')); 8 | callback(); 9 | } 10 | 11 | module.exports = addCheckMark; 12 | -------------------------------------------------------------------------------- /internals/scripts/helpers/progress.js: -------------------------------------------------------------------------------- 1 | var readline = require('readline'); 2 | 3 | /** 4 | * Adds an animated progress indicator 5 | * 6 | * @param {string} message The message to write next to the indicator 7 | * @param {number} amountOfDots The amount of dots you want to animate 8 | */ 9 | function animateProgress(message, amountOfDots) { 10 | if (typeof amountOfDots !== 'number') { 11 | amountOfDots = 3; 12 | } 13 | 14 | var i = 0; 15 | return setInterval(function () { 16 | readline.cursorTo(process.stdout, 0); 17 | i = (i + 1) % (amountOfDots + 1); 18 | var dots = new Array(i + 1).join('.'); 19 | process.stdout.write(message + dots); 20 | }, 500); 21 | } 22 | 23 | module.exports = animateProgress; 24 | -------------------------------------------------------------------------------- /internals/scripts/npmcheckversion.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | var exec = require('child_process').exec; 3 | exec('npm -v', function (err, stdout, stderr) { 4 | if (err) throw err; 5 | if (parseFloat(stdout) < 3) { 6 | throw new Error('[ERROR: React Boilerplate] You need npm version @>=3'); 7 | process.exit(1); 8 | } 9 | }); 10 | -------------------------------------------------------------------------------- /internals/scripts/pagespeed.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | process.stdin.resume(); 4 | process.stdin.setEncoding('utf8'); 5 | 6 | var ngrok = require('ngrok'); 7 | var psi = require('psi'); 8 | var chalk = require('chalk'); 9 | 10 | log('\nStarting ngrok tunnel'); 11 | 12 | startTunnel(runPsi); 13 | 14 | function runPsi(url) { 15 | log('\nStarting PageSpeed Insights'); 16 | psi.output(url).then(function (err) { 17 | process.exit(0); 18 | }); 19 | } 20 | 21 | function startTunnel(cb) { 22 | ngrok.connect(3000, function (err, url) { 23 | if (err) { 24 | log(chalk.red('\nERROR\n' + err)); 25 | process.exit(0); 26 | } 27 | 28 | log('\nServing tunnel from: ' + chalk.magenta(url)); 29 | cb(url); 30 | }); 31 | } 32 | 33 | function log(string) { 34 | process.stdout.write(string); 35 | } 36 | -------------------------------------------------------------------------------- /internals/templates/appContainer.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * App.react.js 4 | * 5 | * This component is the skeleton around the actual pages, and should only 6 | * contain code that should be seen on all pages. (e.g. navigation bar) 7 | * 8 | * NOTE: while this component should technically be a stateless functional 9 | * component (SFC), hot reloading does not currently support SFCs. If hot 10 | * reloading is not a neccessity for you then you can refactor it and remove 11 | * the linting exception. 12 | */ 13 | 14 | import React from 'react'; 15 | 16 | import styles from './styles.css'; 17 | 18 | export default class App extends React.Component { // eslint-disable-line react/prefer-stateless-function 19 | 20 | static propTypes = { 21 | children: React.PropTypes.node, 22 | }; 23 | 24 | render() { 25 | return ( 26 |
    27 | {this.props.children} 28 |
    29 | ); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /internals/templates/asyncInjectors.js: -------------------------------------------------------------------------------- 1 | import createReducer from 'reducers.js'; 2 | 3 | /** 4 | * Inject an asynchronously loaded reducer 5 | */ 6 | export function injectAsyncReducer(store) { 7 | return (name, asyncReducer) => { 8 | store.asyncReducers[name] = asyncReducer; // eslint-disable-line 9 | store.replaceReducer(createReducer(store.asyncReducers)); 10 | }; 11 | } 12 | 13 | /** 14 | * Inject an asynchronously loaded saga 15 | */ 16 | export function injectAsyncSagas(store) { 17 | return (sagas) => sagas.map(store.runSaga); 18 | } 19 | 20 | /** 21 | * Helper for creating injectors 22 | */ 23 | export function getAsyncInjectors(store) { 24 | return { 25 | injectReducer: injectAsyncReducer(store), 26 | injectSagas: injectAsyncSagas(store), 27 | }; 28 | } 29 | -------------------------------------------------------------------------------- /internals/templates/asyncInjectors.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Test async injectors 3 | */ 4 | 5 | import expect from 'expect'; 6 | import configureStore from 'store.js'; 7 | import { memoryHistory } from 'react-router'; 8 | import { put } from 'redux-saga/effects'; 9 | import { fromJS } from 'immutable'; 10 | 11 | import { 12 | injectAsyncReducer, 13 | injectAsyncSagas, 14 | getAsyncInjectors, 15 | } from 'utils/asyncInjectors'; 16 | 17 | // Fixtures 18 | 19 | const initialState = fromJS({ reduced: 'soon' }); 20 | 21 | const reducer = (state = initialState, action) => { 22 | switch (action.type) { 23 | case 'TEST': 24 | return state.set('reduced', action.payload); 25 | default: 26 | return state; 27 | } 28 | }; 29 | 30 | const sagas = [ 31 | function* testSaga() { 32 | yield put({ type: 'TEST', payload: 'yup' }); 33 | }, 34 | ]; 35 | 36 | describe('asyncInjectors', () => { 37 | let store; 38 | 39 | describe('getAsyncInjectors', () => { 40 | before(() => { 41 | store = configureStore({}, memoryHistory); 42 | }); 43 | 44 | it('given a store, should return all async injectors', () => { 45 | const { injectReducer, injectSagas } = getAsyncInjectors(store); 46 | 47 | injectReducer('test', reducer); 48 | injectSagas(sagas); 49 | 50 | const actual = store.getState().get('test'); 51 | const expected = initialState.merge({ reduced: 'yup' }); 52 | 53 | expect(actual.toJS()).toEqual(expected.toJS()); 54 | }); 55 | }); 56 | 57 | describe('helpers', () => { 58 | before(() => { 59 | store = configureStore({}, memoryHistory); 60 | }); 61 | 62 | describe('injectAsyncReducer', () => { 63 | it('given a store, it should provide a function to inject a reducer', () => { 64 | const injectReducer = injectAsyncReducer(store); 65 | 66 | injectReducer('test', reducer); 67 | 68 | const actual = store.getState().get('test'); 69 | const expected = initialState; 70 | 71 | expect(actual.toJS()).toEqual(expected.toJS()); 72 | }); 73 | }); 74 | 75 | describe('injectAsyncSagas', () => { 76 | it('given a store, it should provide a function to inject a saga', () => { 77 | const injectSagas = injectAsyncSagas(store); 78 | 79 | injectSagas(sagas); 80 | 81 | const actual = store.getState().get('test'); 82 | const expected = initialState.merge({ reduced: 'yup' }); 83 | 84 | expect(actual.toJS()).toEqual(expected.toJS()); 85 | }); 86 | }); 87 | }); 88 | }); 89 | -------------------------------------------------------------------------------- /internals/templates/homePage.js: -------------------------------------------------------------------------------- 1 | /* 2 | * HomePage 3 | * 4 | * This is the first thing users see of our App, at the '/' route 5 | * 6 | * NOTE: while this component should technically be a stateless functional 7 | * component (SFC), hot reloading does not currently support SFCs. If hot 8 | * reloading is not a neccessity for you then you can refactor it and remove 9 | * the linting exception. 10 | */ 11 | 12 | import React from 'react'; 13 | 14 | export default class HomePage extends React.Component { // eslint-disable-line react/prefer-stateless-function 15 | 16 | render() { 17 | return ( 18 |

    This is the Homepage!

    19 | ); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /internals/templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | React.js Boilerplate 12 | 13 | 14 | 15 |
    16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /internals/templates/notFoundPage.js: -------------------------------------------------------------------------------- 1 | /** 2 | * NotFoundPage 3 | * 4 | * This is the page we show when the user visits a url that doesn't have a route 5 | * 6 | * NOTE: while this component should technically be a stateless functional 7 | * component (SFC), hot reloading does not currently support SFCs. If hot 8 | * reloading is not a neccessity for you then you can refactor it and remove 9 | * the linting exception. 10 | */ 11 | 12 | import React from 'react'; 13 | 14 | export default class NotFound extends React.Component { // eslint-disable-line react/prefer-stateless-function 15 | 16 | render() { 17 | return ( 18 |

    Page Not Found

    19 | ); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /internals/templates/reducers.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Combine all reducers in this file and export the combined reducers. 3 | * If we were to do this in store.js, reducers wouldn't be hot reloadable. 4 | */ 5 | 6 | import { combineReducers } from 'redux-immutable'; 7 | import { fromJS } from 'immutable'; 8 | import { LOCATION_CHANGE } from 'react-router-redux'; 9 | 10 | /* 11 | * routeReducer 12 | * 13 | * The reducer merges route location changes into our immutable state. 14 | * The change is necessitated by moving to react-router-redux@4 15 | * 16 | */ 17 | 18 | // Initial routing state 19 | const routeInitialState = fromJS({ 20 | locationBeforeTransitions: null, 21 | }); 22 | 23 | /** 24 | * Merge route into the global application state 25 | */ 26 | function routeReducer(state = routeInitialState, action) { 27 | switch (action.type) { 28 | /* istanbul ignore next */ 29 | case LOCATION_CHANGE: 30 | return state.merge({ 31 | locationBeforeTransitions: action.payload, 32 | }); 33 | default: 34 | return state; 35 | } 36 | } 37 | 38 | /** 39 | * Creates the main reducer with the asynchronously loaded ones 40 | */ 41 | export default function createReducer(asyncReducers) { 42 | return combineReducers({ 43 | route: routeReducer, 44 | ...asyncReducers, 45 | }); 46 | } 47 | -------------------------------------------------------------------------------- /internals/templates/routes.js: -------------------------------------------------------------------------------- 1 | // These are the pages you can go to. 2 | // They are all wrapped in the App component, which should contain the navbar etc 3 | // See http://blog.mxstbr.com/2016/01/react-apps-with-pages for more information 4 | // about the code splitting business 5 | // import { getAsyncInjectors } from 'utils/asyncInjectors'; 6 | 7 | const errorLoading = (err) => { 8 | console.error('Dynamic page loading failed', err); // eslint-disable-line no-console 9 | }; 10 | 11 | const loadModule = (cb) => (componentModule) => { 12 | cb(null, componentModule.default); 13 | }; 14 | 15 | export default function createRoutes() { 16 | // Create reusable async injectors using getAsyncInjectors factory 17 | // const { injectReducer, injectSagas } = getAsyncInjectors(store); 18 | 19 | return [ 20 | { 21 | path: '/', 22 | name: 'home', 23 | getComponent(nextState, cb) { 24 | const importModules = Promise.all([ 25 | System.import('containers/HomePage'), 26 | ]); 27 | 28 | const renderRoute = loadModule(cb); 29 | 30 | importModules.then(([component]) => { 31 | renderRoute(component); 32 | }); 33 | 34 | importModules.catch(errorLoading); 35 | }, 36 | }, { 37 | path: '*', 38 | name: 'notfound', 39 | getComponent(nextState, cb) { 40 | System.import('containers/NotFoundPage') 41 | .then(loadModule(cb)) 42 | .catch(errorLoading); 43 | }, 44 | }, 45 | ]; 46 | } 47 | -------------------------------------------------------------------------------- /internals/templates/selectors.js: -------------------------------------------------------------------------------- 1 | // selectLocationState expects a plain JS object for the routing state 2 | const selectLocationState = () => { 3 | let prevRoutingState; 4 | let prevRoutingStateJS; 5 | 6 | return (state) => { 7 | const routingState = state.get('route'); // or state.route 8 | 9 | if (!routingState.equals(prevRoutingState)) { 10 | prevRoutingState = routingState; 11 | prevRoutingStateJS = routingState.toJS(); 12 | } 13 | 14 | return prevRoutingStateJS; 15 | }; 16 | }; 17 | 18 | export { 19 | selectLocationState, 20 | }; 21 | -------------------------------------------------------------------------------- /internals/templates/selectors.test.js: -------------------------------------------------------------------------------- 1 | import { fromJS } from 'immutable'; 2 | import expect from 'expect'; 3 | 4 | import { selectLocationState } from 'containers/App/selectors'; 5 | 6 | describe('selectLocationState', () => { 7 | it('should select the route as a plain JS object', () => { 8 | const route = fromJS({ 9 | locationBeforeTransitions: null, 10 | }); 11 | const mockedState = fromJS({ 12 | route, 13 | }); 14 | expect(selectLocationState()(mockedState)).toEqual(route.toJS()); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /internals/templates/store.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Create the store with asynchronously loaded reducers 3 | */ 4 | 5 | import { createStore, applyMiddleware, compose } from 'redux'; 6 | import { fromJS } from 'immutable'; 7 | import { routerMiddleware } from 'react-router-redux'; 8 | import createSagaMiddleware from 'redux-saga'; 9 | import createReducer from './reducers'; 10 | 11 | const sagaMiddleware = createSagaMiddleware(); 12 | const devtools = window.devToolsExtension || (() => noop => noop); 13 | 14 | export default function configureStore(initialState = {}, history) { 15 | // Create the store with two middlewares 16 | // 1. sagaMiddleware: Makes redux-sagas work 17 | // 2. routerMiddleware: Syncs the location/URL path to the state 18 | const middlewares = [ 19 | sagaMiddleware, 20 | routerMiddleware(history), 21 | ]; 22 | 23 | const enhancers = [ 24 | applyMiddleware(...middlewares), 25 | devtools(), 26 | ]; 27 | 28 | const store = createStore( 29 | createReducer(), 30 | fromJS(initialState), 31 | compose(...enhancers) 32 | ); 33 | 34 | // Create hook for async sagas 35 | store.runSaga = sagaMiddleware.run; 36 | 37 | // Make reducers hot reloadable, see http://mxs.is/googmo 38 | /* istanbul ignore next */ 39 | if (module.hot) { 40 | System.import('./reducers').then((reducerModule) => { 41 | const createReducers = reducerModule.default; 42 | const nextReducers = createReducers(store.asyncReducers); 43 | 44 | store.replaceReducer(nextReducers); 45 | }); 46 | } 47 | 48 | // Initialize it with no other reducers 49 | store.asyncReducers = {}; 50 | return store; 51 | } 52 | -------------------------------------------------------------------------------- /internals/templates/store.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Test store addons 3 | */ 4 | 5 | import expect from 'expect'; 6 | import configureStore from './store'; 7 | import { browserHistory } from 'react-router'; 8 | 9 | describe('configureStore', () => { 10 | let store; 11 | 12 | before(() => { 13 | store = configureStore({}, browserHistory); 14 | }); 15 | 16 | describe('asyncReducers', () => { 17 | it('should contain an object for async reducers', () => { 18 | expect(typeof store.asyncReducers).toEqual('object'); 19 | }); 20 | }); 21 | 22 | describe('runSaga', () => { 23 | it('should contain a hook for `sagaMiddleware.run`', () => { 24 | expect(typeof store.runSaga).toEqual('function'); 25 | }); 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /internals/templates/styles.css: -------------------------------------------------------------------------------- 1 | /** 2 | * styles.css 3 | * 4 | * App container styles 5 | */ 6 | 7 | .container { 8 | display: block; 9 | } 10 | -------------------------------------------------------------------------------- /internals/testing/karma.conf.js: -------------------------------------------------------------------------------- 1 | const webpackConfig = require('../webpack/webpack.test.babel'); 2 | const argv = require('minimist')(process.argv.slice(2)); 3 | const path = require('path'); 4 | 5 | module.exports = (config) => { 6 | config.set({ 7 | frameworks: ['mocha'], 8 | reporters: ['coverage', 'mocha'], 9 | browsers: process.env.TRAVIS // eslint-disable-line no-nested-ternary 10 | ? ['ChromeTravis'] 11 | : process.env.APPVEYOR 12 | ? ['IE'] : ['Chrome'], 13 | 14 | autoWatch: false, 15 | singleRun: true, 16 | 17 | client: { 18 | mocha: { 19 | grep: argv.grep, 20 | }, 21 | }, 22 | 23 | files: [ 24 | { 25 | pattern: './test-bundler.js', 26 | watched: false, 27 | served: true, 28 | included: true, 29 | }, 30 | ], 31 | 32 | preprocessors: { 33 | ['./test-bundler.js']: ['webpack', 'sourcemap'], // eslint-disable-line no-useless-computed-key 34 | }, 35 | 36 | webpack: webpackConfig, 37 | 38 | // make Webpack bundle generation quiet 39 | webpackMiddleware: { 40 | noInfo: true, 41 | stats: 'errors-only', 42 | }, 43 | 44 | customLaunchers: { 45 | ChromeTravis: { 46 | base: 'Chrome', 47 | flags: ['--no-sandbox'], 48 | }, 49 | }, 50 | 51 | coverageReporter: { 52 | dir: path.join(process.cwd(), 'coverage'), 53 | reporters: [ 54 | { type: 'lcov', subdir: 'lcov' }, 55 | { type: 'html', subdir: 'html' }, 56 | { type: 'text-summary' }, 57 | ], 58 | }, 59 | 60 | }); 61 | }; 62 | -------------------------------------------------------------------------------- /internals/testing/test-bundler.js: -------------------------------------------------------------------------------- 1 | // needed for regenerator-runtime 2 | // (ES7 generator support is required by redux-saga) 3 | import 'babel-polyfill'; 4 | 5 | // If we need to use Chai, we'll have already chaiEnzyme loaded 6 | import chai from 'chai'; 7 | import chaiEnzyme from 'chai-enzyme'; 8 | chai.use(chaiEnzyme()); 9 | 10 | // Include all .js files under `app`, except app.js, reducers.js, routes.js and 11 | // store.js. This is for isparta code coverage 12 | const context = require.context('../../app', true, /^^((?!(app|reducers|routes|store)).)*\.js$/); 13 | context.keys().forEach(context); 14 | -------------------------------------------------------------------------------- /internals/webpack/webpack.dll.babel.js: -------------------------------------------------------------------------------- 1 | /** 2 | * WEBPACK DLL GENERATOR 3 | * 4 | * This profile is used to cache webpack's module 5 | * contexts for external library and framework type 6 | * dependencies which will usually not change often enough 7 | * to warrant building them from scratch every time we use 8 | * the webpack process. 9 | */ 10 | 11 | const { join } = require('path'); 12 | const defaults = require('lodash/defaultsDeep'); 13 | const webpack = require('webpack'); 14 | const pkg = require(join(process.cwd(), 'package.json')); 15 | const dllPlugin = require('../config').dllPlugin; 16 | 17 | if (!pkg.dllPlugin) { process.exit(0); } 18 | 19 | const dllConfig = defaults(pkg.dllPlugin, dllPlugin.defaults); 20 | const outputPath = join(process.cwd(), dllConfig.path); 21 | 22 | module.exports = { 23 | context: process.cwd(), 24 | entry: dllConfig.dlls ? dllConfig.dlls : dllPlugin.entry(pkg), 25 | devtool: 'eval', 26 | output: { 27 | filename: '[name].dll.js', 28 | path: outputPath, 29 | library: '[name]', 30 | }, 31 | plugins: [ 32 | new webpack.DllPlugin({ name: '[name]', path: join(outputPath, '[name].json') }), // eslint-disable-line no-new 33 | ], 34 | }; 35 | -------------------------------------------------------------------------------- /server/index.js: -------------------------------------------------------------------------------- 1 | /* eslint consistent-return:0 */ 2 | 3 | const express = require('express'); 4 | const logger = require('./logger'); 5 | 6 | const argv = require('minimist')(process.argv.slice(2)); 7 | const setup = require('./middlewares/frontendMiddleware'); 8 | const isDev = process.env.NODE_ENV !== 'production'; 9 | const ngrok = (isDev && process.env.ENABLE_TUNNEL) || argv.tunnel ? require('ngrok') : false; 10 | const resolve = require('path').resolve; 11 | const app = express(); 12 | 13 | // If you need a backend, e.g. an API, add your custom backend-specific middleware here 14 | // app.use('/api', myApi); 15 | 16 | // In production we need to pass these values in instead of relying on webpack 17 | setup(app, { 18 | outputPath: resolve(process.cwd(), 'build'), 19 | publicPath: '/', 20 | }); 21 | 22 | // get the intended port number, use port 3000 if not provided 23 | const port = argv.port || process.env.PORT || 3000; 24 | 25 | // Start your app. 26 | app.listen(port, (err) => { 27 | if (err) { 28 | return logger.error(err.message); 29 | } 30 | 31 | // Connect to ngrok in dev mode 32 | if (ngrok) { 33 | ngrok.connect(port, (innerErr, url) => { 34 | if (innerErr) { 35 | return logger.error(innerErr); 36 | } 37 | 38 | logger.appStarted(port, url); 39 | }); 40 | } else { 41 | logger.appStarted(port); 42 | } 43 | }); 44 | -------------------------------------------------------------------------------- /server/logger.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | 3 | const chalk = require('chalk'); 4 | const ip = require('ip'); 5 | 6 | const divider = chalk.gray('\n-----------------------------------'); 7 | 8 | /** 9 | * Logger middleware, you can customize it to make messages more personal 10 | */ 11 | const logger = { 12 | 13 | // Called whenever there's an error on the server we want to print 14 | error: err => { 15 | console.error(chalk.red(err)); 16 | }, 17 | 18 | // Called when express.js app starts on given port w/o errors 19 | appStarted: (port, tunnelStarted) => { 20 | console.log(`Server started ${chalk.green('✓')}`); 21 | 22 | // If the tunnel started, log that and the URL it's available at 23 | if (tunnelStarted) { 24 | console.log(`Tunnel initialised ${chalk.green('✓')}`); 25 | } 26 | 27 | console.log(` 28 | ${chalk.bold('Access URLs:')}${divider} 29 | Localhost: ${chalk.magenta(`http://localhost:${port}`)} 30 | LAN: ${chalk.magenta(`http://${ip.address()}:${port}`) + 31 | (tunnelStarted ? `\n Proxy: ${chalk.magenta(tunnelStarted)}` : '')}${divider} 32 | ${chalk.blue(`Press ${chalk.italic('CTRL-C')} to stop`)} 33 | `); 34 | }, 35 | }; 36 | 37 | module.exports = logger; 38 | -------------------------------------------------------------------------------- /server/middlewares/frontendMiddleware.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable global-require */ 2 | const express = require('express'); 3 | const path = require('path'); 4 | const compression = require('compression'); 5 | const pkg = require(path.resolve(process.cwd(), 'package.json')); 6 | 7 | // Dev middleware 8 | const addDevMiddlewares = (app, webpackConfig) => { 9 | const webpack = require('webpack'); 10 | const webpackDevMiddleware = require('webpack-dev-middleware'); 11 | const webpackHotMiddleware = require('webpack-hot-middleware'); 12 | const compiler = webpack(webpackConfig); 13 | const middleware = webpackDevMiddleware(compiler, { 14 | noInfo: true, 15 | publicPath: webpackConfig.output.publicPath, 16 | silent: true, 17 | stats: 'errors-only', 18 | }); 19 | 20 | app.use(middleware); 21 | app.use(webpackHotMiddleware(compiler)); 22 | 23 | // Since webpackDevMiddleware uses memory-fs internally to store build 24 | // artifacts, we use it instead 25 | const fs = middleware.fileSystem; 26 | 27 | if (pkg.dllPlugin) { 28 | app.get(/\.dll\.js$/, (req, res) => { 29 | const filename = req.path.replace(/^\//, ''); 30 | res.sendFile(path.join(process.cwd(), pkg.dllPlugin.path, filename)); 31 | }); 32 | } 33 | 34 | app.get('*', (req, res) => { 35 | fs.readFile(path.join(compiler.outputPath, 'index.html'), (err, file) => { 36 | if (err) { 37 | res.sendStatus(404); 38 | } else { 39 | res.send(file.toString()); 40 | } 41 | }); 42 | }); 43 | }; 44 | 45 | // Production middlewares 46 | const addProdMiddlewares = (app, options) => { 47 | const publicPath = options.publicPath || '/'; 48 | const outputPath = options.outputPath || path.resolve(process.cwd(), 'build'); 49 | 50 | // compression middleware compresses your server responses which makes them 51 | // smaller (applies also to assets). You can read more about that technique 52 | // and other good practices on official Express.js docs http://mxs.is/googmy 53 | app.use(compression()); 54 | app.use(publicPath, express.static(outputPath)); 55 | 56 | app.get('*', (req, res) => res.sendFile(path.resolve(outputPath, 'index.html'))); 57 | }; 58 | 59 | /** 60 | * Front-end middleware 61 | */ 62 | module.exports = (app, options) => { 63 | const isProd = process.env.NODE_ENV === 'production'; 64 | 65 | if (isProd) { 66 | addProdMiddlewares(app, options); 67 | } else { 68 | const webpackConfig = require('../../internals/webpack/webpack.dev.babel'); 69 | addDevMiddlewares(app, webpackConfig); 70 | } 71 | 72 | return app; 73 | }; 74 | --------------------------------------------------------------------------------