├── _config.yml ├── public ├── favicon.ico ├── logo192.png ├── logo512.png ├── robots.txt ├── manifest.json └── index.html ├── src ├── Flexo │ ├── assets │ │ ├── showrinAvatar.png │ │ ├── arrow_left.svg │ │ ├── arrow_right.svg │ │ ├── close.svg │ │ ├── share.svg │ │ ├── childOverlay.svg │ │ ├── copy.svg │ │ ├── check.svg │ │ ├── bottomRotate.svg │ │ ├── rightRotate.svg │ │ ├── logo.svg │ │ ├── loading_art.svg │ │ ├── sharing_link.svg │ │ ├── generating_link.svg │ │ └── building_layout.svg │ ├── index.js │ ├── stylesheets │ │ ├── _fonts.scss │ │ ├── _mixins.scss │ │ ├── flexo.scss │ │ ├── components │ │ │ ├── _axis.scss │ │ │ ├── _button.scss │ │ │ ├── _bottom-bar.scss │ │ │ ├── _flex-container.scss │ │ │ ├── _flex-child.scss │ │ │ ├── _navbar.scss │ │ │ ├── _toggle.scss │ │ │ ├── _toast.scss │ │ │ ├── _sidebar.scss │ │ │ ├── _onboarding.scss │ │ │ └── _loading-screen.scss │ │ ├── _variables.scss │ │ └── _reset.scss │ ├── components │ │ ├── Axis.jsx │ │ ├── index.js │ │ ├── LoadingScreen.jsx │ │ ├── styleOptions.js │ │ ├── Button.jsx │ │ ├── FlexChild.jsx │ │ ├── BottomBar.jsx │ │ ├── Toggle.jsx │ │ ├── Navbar.jsx │ │ ├── FlexContainer.jsx │ │ ├── Toast.jsx │ │ ├── Onboarding.jsx │ │ └── Sidebar.jsx │ └── Flexo.jsx ├── setupTests.js ├── App.jsx ├── index.js ├── dbConfig.js ├── serviceWorker.js └── contextSetup.jsx ├── .gitignore ├── LICENSE ├── package.json └── README.md /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-cayman -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Showrin/flexo/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Showrin/flexo/HEAD/public/logo192.png -------------------------------------------------------------------------------- /public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Showrin/flexo/HEAD/public/logo512.png -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /src/Flexo/assets/showrinAvatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Showrin/flexo/HEAD/src/Flexo/assets/showrinAvatar.png -------------------------------------------------------------------------------- /src/Flexo/index.js: -------------------------------------------------------------------------------- 1 | import './stylesheets/flexo.scss'; 2 | 3 | import Flexo from './Flexo'; 4 | 5 | export default Flexo; 6 | -------------------------------------------------------------------------------- /src/Flexo/stylesheets/_fonts.scss: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css2?family=Inconsolata:wght@700&family=Khula:wght@400;600;700;800&display=swap'); 2 | -------------------------------------------------------------------------------- /src/setupTests.js: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom/extend-expect'; 6 | -------------------------------------------------------------------------------- /src/App.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { ContextProvider } from './contextSetup'; 3 | import Flexo from './Flexo'; 4 | 5 | function App() { 6 | return ( 7 |
8 | 9 | 10 | 11 |
12 | ); 13 | } 14 | 15 | export default App; 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /src/Flexo/stylesheets/_mixins.scss: -------------------------------------------------------------------------------- 1 | @mixin is-mobile { 2 | @media only screen and (max-width: $mobile) { 3 | @content 4 | } 5 | }; 6 | 7 | @mixin is-tab { 8 | @media only screen and (min-width: $mobile) { 9 | @content 10 | } 11 | }; 12 | 13 | @mixin is-tab-landscape { 14 | @media only screen and (min-width: $tab) { 15 | @content 16 | } 17 | }; 18 | 19 | @mixin is-desktop { 20 | @media only screen and (min-width: $tab-landscape) { 21 | @content 22 | } 23 | }; -------------------------------------------------------------------------------- /src/Flexo/assets/arrow_left.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/Flexo/assets/arrow_right.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | import * as serviceWorker from './serviceWorker'; 5 | 6 | ReactDOM.render( 7 | 8 | 9 | , 10 | document.getElementById('root') 11 | ); 12 | 13 | // If you want your app to work offline and load faster, you can change 14 | // unregister() to register() below. Note this comes with some pitfalls. 15 | // Learn more about service workers: https://bit.ly/CRA-PWA 16 | serviceWorker.unregister(); 17 | -------------------------------------------------------------------------------- /src/Flexo/assets/close.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/Flexo/components/Axis.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import classNames from 'classnames'; 4 | 5 | const Axis = (props) => { 6 | const { direction, type } = props; 7 | 8 | return ( 9 |
16 | ); 17 | }; 18 | 19 | Axis.propTypes = { 20 | direction: PropTypes.oneOf(['horizontal', 'vertical']).isRequired, 21 | type: PropTypes.oneOf(['main', 'cross']).isRequired, 22 | }; 23 | 24 | export default Axis; 25 | -------------------------------------------------------------------------------- /src/Flexo/stylesheets/flexo.scss: -------------------------------------------------------------------------------- 1 | @import './fonts'; 2 | @import './variables'; 3 | @import './mixins'; 4 | @import './reset'; 5 | 6 | // ------------ Components ------------ 7 | @import './components/navbar'; 8 | @import './components/flex-container'; 9 | @import './components/bottom-bar'; 10 | @import './components/toggle'; 11 | @import './components/axis'; 12 | @import './components/flex-child'; 13 | @import './components/sidebar'; 14 | @import './components/button'; 15 | @import './components/loading-screen'; 16 | @import './components/toast'; 17 | @import './components/onboarding'; 18 | -------------------------------------------------------------------------------- /src/Flexo/assets/share.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/Flexo/assets/childOverlay.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /src/Flexo/components/index.js: -------------------------------------------------------------------------------- 1 | export { default as Navbar } from './Navbar'; 2 | export { default as FlexContainer } from './FlexContainer'; 3 | export { default as BottomBar } from './BottomBar'; 4 | export { default as Toggle } from './Toggle'; 5 | export { default as Axis } from './Axis'; 6 | export { default as FlexChild } from './FlexChild'; 7 | export { default as Sidebar } from './Sidebar'; 8 | export { default as Button } from './Button'; 9 | export { default as LoadingScreen } from './LoadingScreen'; 10 | export { default as Toast } from './Toast'; 11 | export { default as Onboarding } from './Onboarding'; 12 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "Flexo", 3 | "name": "Flexo - A Flexbox Playground", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#132533", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /src/dbConfig.js: -------------------------------------------------------------------------------- 1 | import firebase from 'firebase/app'; 2 | import 'firebase/database'; 3 | import 'firebase/analytics'; 4 | 5 | const dbConfig = { 6 | apiKey: 'AIzaSyCJLzsi8YM5btEU-0UlWLTBo0UmU64P6WI', 7 | authDomain: 'app-flexo.firebaseapp.com', 8 | databaseURL: 'https://app-flexo.firebaseio.com', 9 | projectId: 'app-flexo', 10 | storageBucket: 'app-flexo.appspot.com', 11 | messagingSenderId: '393815376344', 12 | appId: '1:393815376344:web:0ef62eb529db9ea5ceb2ad', 13 | measurementId: 'G-HLJLXTQZT2', 14 | }; 15 | 16 | firebase.initializeApp(dbConfig); 17 | firebase.analytics(); 18 | 19 | export default firebase; 20 | -------------------------------------------------------------------------------- /src/Flexo/stylesheets/components/_axis.scss: -------------------------------------------------------------------------------- 1 | .axis { 2 | box-shadow: 0 0 6px $black-light; 3 | pointer-events: none; 4 | position: fixed; 5 | z-index: 100; 6 | 7 | &--horizontal { 8 | height: 4px; 9 | left: 0; 10 | top: 50%; 11 | transform: translateY(-50%); 12 | width: 100%; 13 | } 14 | 15 | &--vertical { 16 | height: calc(100% - 200px); 17 | left: 50%; 18 | top: 100px; 19 | transform: translateX(-50%); 20 | width: 4px; 21 | } 22 | 23 | &--main { 24 | background-color: $danger; 25 | } 26 | 27 | &--cross { 28 | background-color: $success; 29 | } 30 | 31 | @include is-mobile { 32 | &--vertical { 33 | height: calc(100% - 140px); 34 | top: 70px; 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Flexo/assets/copy.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/Flexo/assets/check.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/Flexo/stylesheets/components/_button.scss: -------------------------------------------------------------------------------- 1 | .button { 2 | background-color: $secondary; 3 | border-radius: 50px; 4 | border: none; 5 | box-shadow: $shadow-1; 6 | padding: 2px 20px; 7 | transition: background-color 150ms $timing-func-1, 8 | transform 150ms $timing-func-1; 9 | 10 | &__label { 11 | color: $white; 12 | display: inline-block; 13 | font-family: $font-khula; 14 | font-size: 16px; 15 | font-weight: $font-bold; 16 | height: calc(#{$font-16} + 6px); 17 | } 18 | 19 | &:hover, 20 | &--active, 21 | &--increment { 22 | background-color: $success; 23 | } 24 | 25 | &--decrement { 26 | background-color: $danger; 27 | 28 | &:hover { 29 | background-color: $danger; 30 | } 31 | } 32 | 33 | &:active { 34 | transform: scale(0.95); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Flexo/assets/bottomRotate.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/Flexo/stylesheets/components/_bottom-bar.scss: -------------------------------------------------------------------------------- 1 | .bottom-bar { 2 | align-items: center; 3 | display: flex; 4 | height: 100px; 5 | padding: $pad-sm $pad-xl; 6 | width: 100%; 7 | 8 | .toggle { 9 | margin-right: 60px; 10 | 11 | &:last-of-type { 12 | margin-right: 0; 13 | } 14 | } 15 | 16 | @include is-mobile { 17 | height: 70px; 18 | justify-content: space-between; 19 | padding: $pad-10; 20 | 21 | .toggle { 22 | margin-right: 0; 23 | 24 | &:nth-last-of-type(2) { 25 | margin-right: 0; 26 | } 27 | 28 | &:last-of-type { 29 | display: none; 30 | } 31 | } 32 | } 33 | 34 | @include is-tab { 35 | justify-content: space-between; 36 | padding: $pad-20; 37 | } 38 | 39 | @include is-tab-landscape { 40 | justify-content: flex-start; 41 | padding: $pad-sm $pad-xl; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Flexo/components/LoadingScreen.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import classNames from 'classnames'; 3 | import Consumer from '../../contextSetup'; 4 | import { ReactComponent as LoadingArt } from '../assets/loading_art.svg'; 5 | 6 | const LoadingScreen = () => { 7 | return ( 8 | 9 | {(context) => { 10 | const { 11 | isLoading, 12 | removeLoadingScreenFromDOM, 13 | } = context.loadingState; 14 | 15 | return ( 16 |
22 |
23 | 24 |
25 |
26 | Loading the View 27 |
28 |
29 |
30 | ); 31 | }} 32 | 33 | ); 34 | }; 35 | 36 | export default LoadingScreen; 37 | -------------------------------------------------------------------------------- /src/Flexo/assets/rightRotate.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/Flexo/components/styleOptions.js: -------------------------------------------------------------------------------- 1 | export default { 2 | display: ['flex', 'inline-flex'], 3 | children: ['+1', '+3', '+5', '+7', '-1', '-3', '-5', '-7'], 4 | flexDirection: ['row', 'row-reverse', 'column', 'column-reverse'], 5 | flexWrap: ['nowrap', 'wrap', 'wrap-reverse'], 6 | justifyContent: [ 7 | 'flex-start', 8 | 'flex-end', 9 | 'center', 10 | 'space-between', 11 | 'space-around', 12 | 'space-evenly', 13 | ], 14 | alignItems: ['stretch', 'flex-start', 'flex-end', 'center', 'baseline'], 15 | alignContent: [ 16 | 'stretch', 17 | 'flex-start', 18 | 'flex-end', 19 | 'center', 20 | 'space-between', 21 | 'space-around', 22 | 'space-evenly', 23 | ], 24 | order: ['+1', '+3', '+5', '+7', '-1', '-3', '-5', '-7'], 25 | flexBasis: ['auto', '20%', '40%', '60%', '80%', '100%'], 26 | flexGrow: ['+1', '+3', '+5', '+7', '-1', '-3', '-5', '-7'], 27 | flexShrink: ['+1', '+3', '+5', '+7', '-1', '-3', '-5', '-7'], 28 | alignSelf: [ 29 | 'auto', 30 | 'stretch', 31 | 'flex-start', 32 | 'flex-end', 33 | 'center', 34 | 'baseline', 35 | ], 36 | }; 37 | -------------------------------------------------------------------------------- /src/Flexo/components/Button.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import classNames from 'classnames'; 4 | 5 | const Button = (props) => { 6 | const { 7 | label, 8 | active, 9 | isIncrement, 10 | isDecrement, 11 | onClick, 12 | className, 13 | } = props; 14 | 15 | return ( 16 | 26 | ); 27 | }; 28 | 29 | Button.defaultProps = { 30 | className: null, 31 | label: null, 32 | active: false, 33 | isIncrement: false, 34 | isDecrement: false, 35 | onClick: () => {}, 36 | }; 37 | 38 | Button.propTypes = { 39 | className: PropTypes.string, 40 | label: PropTypes.node, 41 | active: PropTypes.bool, 42 | isIncrement: PropTypes.bool, 43 | isDecrement: PropTypes.bool, 44 | onClick: PropTypes.func, 45 | }; 46 | 47 | export default Button; 48 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Showrin Barua 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/Flexo/components/FlexChild.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import classNames from 'classnames'; 4 | import Consumer from '../../contextSetup'; 5 | import { ReactComponent as Overlay } from '../assets/childOverlay.svg'; 6 | 7 | const FlexChild = (props) => { 8 | const { id, childStyles } = props; 9 | 10 | return ( 11 | 12 | {(context) => { 13 | const { selectedElement } = context.appState; 14 | const { openSidebar, handleSelectedElement } = context; 15 | const onClickHandler = (e) => { 16 | e.stopPropagation(); 17 | handleSelectedElement('child', id); 18 | openSidebar(); 19 | }; 20 | 21 | return ( 22 |
31 |
{id}
32 | 33 |
34 | ); 35 | }} 36 |
37 | ); 38 | }; 39 | 40 | FlexChild.propTypes = { 41 | id: PropTypes.number.isRequired, 42 | childStyles: PropTypes.shape({}).isRequired, 43 | }; 44 | 45 | export default FlexChild; 46 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "flexo", 3 | "version": "2.0.0", 4 | "private": true, 5 | "main": "index.js", 6 | "dependencies": { 7 | "@testing-library/jest-dom": "^4.2.4", 8 | "@testing-library/react": "^9.3.2", 9 | "@testing-library/user-event": "^7.1.2", 10 | "classnames": "^2.2.6", 11 | "firebase": "^7.17.1", 12 | "node-sass": "^4.14.1", 13 | "react": "^16.13.1", 14 | "react-dom": "^16.13.1", 15 | "react-scripts": "3.4.1", 16 | "universal-cookie": "^4.0.3" 17 | }, 18 | "scripts": { 19 | "start": "react-scripts start", 20 | "build": "react-scripts build", 21 | "test": "react-scripts test", 22 | "eject": "react-scripts eject" 23 | }, 24 | "repository": { 25 | "type": "git", 26 | "url": "https://github.com/Showrin/flexo.git" 27 | }, 28 | "keywords": [ 29 | "React", 30 | "Flexbox", 31 | "SCSS", 32 | "CSS", 33 | "Component", 34 | "SPA" 35 | ], 36 | "author": "Showrin Barua", 37 | "license": "ISC", 38 | "eslintConfig": { 39 | "extends": "react-app" 40 | }, 41 | "browserslist": { 42 | "production": [ 43 | ">0.2%", 44 | "not dead", 45 | "not op_mini all" 46 | ], 47 | "development": [ 48 | "last 1 chrome version", 49 | "last 1 firefox version", 50 | "last 1 safari version" 51 | ] 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/Flexo/components/BottomBar.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Consumer from '../../contextSetup'; 3 | import Toggle from './Toggle'; 4 | 5 | const BottomBar = () => { 6 | return ( 7 | 8 | {(context) => { 9 | const { 10 | showMainAxis, 11 | showCrossAxis, 12 | addPadding, 13 | } = context.appState; 14 | const { 15 | handleMainAxisToggle, 16 | handleCrossAxisToggle, 17 | handlePaddingToggle, 18 | closeSidebar, 19 | handleSelectedElement, 20 | } = context; 21 | 22 | return ( 23 |
{ 26 | handleSelectedElement('', null); 27 | return closeSidebar(); 28 | }} 29 | > 30 | 36 | 42 | 48 |
49 | ); 50 | }} 51 |
52 | ); 53 | }; 54 | 55 | export default BottomBar; 56 | -------------------------------------------------------------------------------- /src/Flexo/stylesheets/components/_flex-container.scss: -------------------------------------------------------------------------------- 1 | .flex-container { 2 | background-color: $secondary-light; 3 | height: calc(100% - 200px); 4 | overflow: auto; 5 | position: relative; 6 | transition: padding 300ms $timing-func-1, 7 | background-color 150ms $timing-func-1; 8 | cursor: pointer; 9 | 10 | &__share-button.button { 11 | align-items: center; 12 | background-color: $danger; 13 | border-radius: 50%; 14 | bottom: 120px; 15 | box-shadow: 0 6px 10px rgba($danger, 0.75); 16 | display: flex; 17 | height: 57px; 18 | justify-content: center; 19 | padding: 0; 20 | position: fixed; 21 | right: 30px; 22 | width: 57px; 23 | 24 | .button__label { 25 | font-size: 25px; 26 | height: auto; 27 | 28 | svg { 29 | display: block; 30 | fill: $white; 31 | height: 1em; 32 | stroke-width: 0; 33 | width: 1em; 34 | } 35 | } 36 | 37 | &:hover { 38 | background-color: $danger; 39 | box-shadow: 0 6px 10px rgba($danger, 0.75); 40 | } 41 | } 42 | 43 | &--selected { 44 | background-color: $success-extra-light; 45 | } 46 | 47 | &--with-padding { 48 | padding: $pad-lg $pad-xl; 49 | } 50 | 51 | @include is-mobile { 52 | height: calc(100% - 140px); 53 | 54 | &__share-button.button { 55 | bottom: 85px; 56 | right: 10px; 57 | } 58 | 59 | &--with-padding { 60 | padding: 0; 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/Flexo/components/Toggle.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import classNames from 'classnames'; 4 | 5 | const Toggle = (props) => { 6 | const { onClick, label, toggleColor, isActive } = props; 7 | const [toggleState, setToggleState] = useState({ 8 | isActive, 9 | }); 10 | 11 | useEffect(() => setToggleState({ isActive }), [isActive]); 12 | 13 | const onClickHandler = () => { 14 | return setToggleState( 15 | (preState) => ({ isActive: !preState.isActive }), 16 | onClick(toggleState) 17 | ); 18 | }; 19 | 20 | return ( 21 |
22 |
30 |
31 |
32 | {label && {label}} 33 |
34 | ); 35 | }; 36 | 37 | Toggle.defaultProps = { 38 | label: null, 39 | onClick: () => {}, 40 | toggleColor: 'black', 41 | isActive: true, 42 | }; 43 | 44 | Toggle.propTypes = { 45 | label: PropTypes.oneOfType([PropTypes.node, PropTypes.string]), 46 | onClick: PropTypes.func, 47 | toggleColor: PropTypes.oneOf(['black', 'danger', 'success']), 48 | isActive: PropTypes.bool, 49 | }; 50 | 51 | export default Toggle; 52 | -------------------------------------------------------------------------------- /src/Flexo/stylesheets/components/_flex-child.scss: -------------------------------------------------------------------------------- 1 | .flex-child { 2 | align-items: center; 3 | background-color: $primary; 4 | border-radius: $border-radius; 5 | cursor: pointer; 6 | display: inline-flex; 7 | justify-content: center; 8 | margin: 1px; 9 | min-height: 110px; 10 | min-width: 110px; 11 | position: relative; 12 | transition: background-color 150ms $timing-func-1, 13 | transform 150ms $timing-func-1; 14 | 15 | &:hover, 16 | &--selected { 17 | background-color: $success; 18 | } 19 | 20 | &:active { 21 | transform: scale(0.9); 22 | } 23 | 24 | &__overlay { 25 | display: block; 26 | height: 85%; 27 | left: 50%; 28 | position: absolute; 29 | top: 50%; 30 | transform: translate(-50%, -50%); 31 | width: 90%; 32 | } 33 | 34 | &__id { 35 | color: $white; 36 | font-size: $font-50; 37 | font-weight: $font-extra-bold; 38 | height: calc(#{$font-50} + 12px); 39 | } 40 | 41 | @include is-mobile { 42 | min-height: 100px; 43 | min-width: 100px; 44 | 45 | &__overlay { 46 | height: 85%; 47 | width: 85%; 48 | } 49 | 50 | &__id { 51 | height: calc(#{$font-50} + 8px); 52 | } 53 | } 54 | 55 | @include is-tab { 56 | min-height: 150px; 57 | min-width: 150px; 58 | 59 | &__overlay { 60 | height: 85%; 61 | width: 85%; 62 | } 63 | } 64 | 65 | @include is-desktop { 66 | min-height: 110px; 67 | min-width: 235px; 68 | 69 | &__overlay { 70 | height: 85%; 71 | width: 90%; 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/Flexo/components/Navbar.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Consumer from '../../contextSetup'; 3 | import { ReactComponent as FlexoLogo } from '../assets/logo.svg'; 4 | import creatorAvatar from '../assets/showrinAvatar.png'; 5 | 6 | const Navbar = () => { 7 | return ( 8 | 9 | {(context) => { 10 | const { closeSidebar, handleSelectedElement } = context; 11 | 12 | return ( 13 | 48 | ); 49 | }} 50 | 51 | ); 52 | }; 53 | 54 | export default Navbar; 55 | -------------------------------------------------------------------------------- /src/Flexo/stylesheets/components/_navbar.scss: -------------------------------------------------------------------------------- 1 | .navbar { 2 | align-items: center; 3 | display: flex; 4 | height: 100px; 5 | justify-content: space-between; 6 | padding: 0 $pad-xl; 7 | width: 100%; 8 | 9 | &__logo { 10 | width: 160px; 11 | 12 | svg { 13 | display: block; 14 | } 15 | } 16 | 17 | &__creator { 18 | align-items: center; 19 | display: inline-flex; 20 | width: 185px; 21 | 22 | &-info { 23 | align-items: center; 24 | display: flex; 25 | flex-wrap: wrap; 26 | justify-content: flex-end; 27 | margin-right: 12px; 28 | 29 | &-text, 30 | &-link { 31 | font-size: $font-12; 32 | height: 14px; 33 | } 34 | 35 | &-link { 36 | font-weight: $font-semi-bold; 37 | } 38 | 39 | &-name { 40 | font-size: $font-16; 41 | font-weight: $font-bold; 42 | height: 20px; 43 | margin: 2px 0; 44 | } 45 | } 46 | 47 | &-avatar { 48 | img { 49 | height: 62px; 50 | width: 62px; 51 | } 52 | } 53 | } 54 | 55 | @include is-mobile { 56 | height: 70px; 57 | padding: $pad-10; 58 | 59 | &__logo { 60 | width: 120px; 61 | } 62 | 63 | &__creator { 64 | &-info { 65 | margin-right: 8px; 66 | 67 | &-text, 68 | &-link { 69 | font-size: $font-14; 70 | } 71 | 72 | &-name { 73 | height: 16px; 74 | margin: 0; 75 | } 76 | } 77 | 78 | &-avatar img { 79 | height: 50px; 80 | width: 50px; 81 | } 82 | } 83 | } 84 | 85 | @include is-tab { 86 | padding: 0 $pad-20; 87 | } 88 | 89 | @include is-tab-landscape { 90 | padding: 0 $pad-xl; 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/Flexo/stylesheets/components/_toggle.scss: -------------------------------------------------------------------------------- 1 | $indicator-width: 25px; 2 | $rail-height: $indicator-width / 2; 3 | $rail-width: ($indicator-width - 3) * 2; 4 | 5 | .toggle { 6 | align-items: center; 7 | display: inline-flex; 8 | 9 | input[type='checkbox'] { 10 | visibility: hidden; 11 | } 12 | 13 | &__rail { 14 | background-color: $gray-light; 15 | border-radius: $border-radius; 16 | cursor: pointer; 17 | height: $rail-height; 18 | position: relative; 19 | width: $rail-width; 20 | 21 | &-indicator { 22 | background-color: $gray; 23 | border-radius: 50%; 24 | cursor: pointer; 25 | display: block; 26 | height: $indicator-width; 27 | left: 0; 28 | position: absolute; 29 | top: 50%; 30 | transform: translateY(-50%); 31 | transition: left 150ms $timing-func-1, 32 | background-color 150ms $timing-func-1; 33 | width: $indicator-width; 34 | } 35 | 36 | &--active { 37 | .toggle__rail-indicator { 38 | left: calc(100% - #{$indicator-width}); 39 | } 40 | 41 | &.toggle__rail--danger { 42 | background-color: $danger-light; 43 | 44 | .toggle__rail-indicator { 45 | background-color: $danger; 46 | } 47 | } 48 | 49 | &.toggle__rail--success { 50 | background-color: $success-light; 51 | 52 | .toggle__rail-indicator { 53 | background-color: $success; 54 | } 55 | } 56 | 57 | &.toggle__rail--black { 58 | background-color: $black-light; 59 | 60 | .toggle__rail-indicator { 61 | background-color: $black; 62 | } 63 | } 64 | } 65 | } 66 | 67 | &__label { 68 | font-size: $font-20; 69 | font-weight: $font-bold; 70 | height: calc(#{$font-20} + 6px); 71 | margin-left: 10px; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 16 | 20 | 21 | 25 | 29 | 30 | 31 | Flexo | Flexbox Playground | Learn CSS Flexbox 32 | 33 | 34 | 35 |
36 | 37 | 38 | -------------------------------------------------------------------------------- /src/Flexo/Flexo.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { ReactComponent as LayoutBuildingIllustration } from './assets/building_layout.svg'; 3 | import { ReactComponent as LinkGeneratingIllustration } from './assets/generating_link.svg'; 4 | import { ReactComponent as LinkSharingIllustration } from './assets/sharing_link.svg'; 5 | import Cookies from 'universal-cookie'; 6 | import { 7 | Navbar, 8 | FlexContainer, 9 | BottomBar, 10 | Sidebar, 11 | LoadingScreen, 12 | Onboarding, 13 | } from './components'; 14 | 15 | const isItFirstVisit = () => { 16 | const cookies = new Cookies(); 17 | 18 | if (cookies.get('visitedEarlier')) { 19 | return false; 20 | } 21 | 22 | cookies.set('visitedEarlier', true, { path: '/' }); 23 | return true; 24 | }; 25 | 26 | const Flexo = () => ( 27 | <> 28 | , 33 | title: 'Learn By Building', 34 | description: 35 | 'This is a tool for building layouts using CSS Flexbox. You can change the properties of any block and see immediate changes happened with the layout.', 36 | }, 37 | { 38 | illustration: , 39 | title: 'Generate Unique View Link', 40 | description: 41 | 'When you are done with your layout, just press the share button. It’ll give you an unique link for the view you’ve made.', 42 | }, 43 | { 44 | illustration: , 45 | title: 'Share With Love', 46 | description: 47 | 'Share the link with your friends and let them know about your approach to make this view.', 48 | }, 49 | ]} 50 | /> 51 | 52 | 53 | 54 | 55 | 56 | 57 | ); 58 | 59 | export default Flexo; 60 | -------------------------------------------------------------------------------- /src/Flexo/stylesheets/components/_toast.scss: -------------------------------------------------------------------------------- 1 | .toast { 2 | background-color: $white; 3 | border-radius: 5px; 4 | bottom: 200px; 5 | box-shadow: 0px 3px 10px rgba(#132533, 0.29); 6 | cursor: initial; 7 | font-size: $font-18; 8 | padding: 15px; 9 | position: fixed; 10 | right: 30px; 11 | transform-origin: bottom right; 12 | transform: scale(0); 13 | transition: transform 0.2s $timing-func-1; 14 | width: 400px; 15 | z-index: 500; 16 | 17 | svg { 18 | height: 1em; 19 | stroke-width: 0; 20 | width: 1em; 21 | } 22 | 23 | &__header { 24 | display: flex; 25 | width: 100%; 26 | 27 | &-text { 28 | color: $black; 29 | display: inline-block; 30 | flex-grow: 1; 31 | font-size: $font-18; 32 | font-weight: $font-bold; 33 | } 34 | 35 | &-close { 36 | cursor: pointer; 37 | font-size: $font-16; 38 | transition: transform 0.15s $timing-func-1; 39 | 40 | &:hover { 41 | transform: scale(1.3); 42 | } 43 | } 44 | } 45 | 46 | &__content { 47 | align-items: center; 48 | background-color: rgba($black, 0.05); 49 | border-radius: inherit; 50 | cursor: pointer; 51 | display: flex; 52 | font-size: $font-16; 53 | margin-top: 10px; 54 | padding: 5px; 55 | transition: transform 0.3s $timing-elastic-func; 56 | width: 100%; 57 | 58 | &-link { 59 | color: $success; 60 | flex-grow: 1; 61 | font-size: $font-16; 62 | font-weight: $font-semi-bold; 63 | height: calc(#{$font-16} + 6px); 64 | overflow: hidden; 65 | text-overflow: ellipsis; 66 | white-space: nowrap; 67 | } 68 | 69 | &-copy { 70 | cursor: pointer; 71 | margin-left: 10px; 72 | } 73 | 74 | &-check { 75 | cursor: pointer; 76 | display: none; 77 | margin-left: 10px; 78 | } 79 | 80 | &:active { 81 | transform: scale(0.9); 82 | } 83 | } 84 | 85 | &--active { 86 | transform: scale(1); 87 | } 88 | 89 | @include is-mobile { 90 | bottom: 160px; 91 | right: 10px; 92 | width: calc(100% - 20px); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/Flexo/stylesheets/_variables.scss: -------------------------------------------------------------------------------- 1 | // ---------- Colors ---------- 2 | $primary: #4c6796 !default; 3 | $primary-light: rgba($primary, 0.35) !default; 4 | $secondary: #668ba2 !default; 5 | $secondary-light: #f3f6f8 !default; 6 | $danger: #f45c5e !default; 7 | $danger-light: rgba($danger, 0.35) !default; 8 | $success: #66a274 !default; 9 | $success-light: rgba($success, 0.35) !default; 10 | $success-extra-light: rgba($success, 0.15) !default; 11 | $black: #132533 !default; 12 | $black-light: rgba($black, 0.35) !default; 13 | $gray: #c3c3c3; 14 | $gray-light: rgba(#c3c3c3, 0.35) !default; 15 | $white: #ffffff !default; 16 | 17 | // ---------- Fonts ---------- 18 | $font-khula: 'Khula', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 19 | 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 20 | sans-serif !default; 21 | $font-inconsolata: 'Inconsolata', source-code-pro, Menlo, Monaco, Consolas, 22 | 'Courier New', monospace !default; 23 | 24 | // ---------- Font Sizes ---------- 25 | $font-size-root: 1rem !default; 26 | $font-12: $font-size-root * 0.75 !default; 27 | $font-14: $font-size-root * 0.875 !default; 28 | $font-16: $font-size-root * 1 !default; 29 | $font-18: $font-size-root * 1.125 !default; 30 | $font-20: $font-size-root * 1.25 !default; 31 | $font-24: $font-size-root * 1.5 !default; 32 | $font-28: $font-size-root * 1.75 !default; 33 | $font-32: $font-size-root * 2 !default; 34 | $font-40: $font-size-root * 2.5 !default; 35 | $font-48: $font-size-root * 3 !default; 36 | $font-50: $font-size-root * 3.125 !default; 37 | 38 | // ---------- Font Weights ---------- 39 | $font-normal: 400 !default; 40 | $font-semi-bold: 600 !default; 41 | $font-bold: 700 !default; 42 | $font-extra-bold: 800 !default; 43 | 44 | // ---------- Line Heights ---------- 45 | $lh-sm: 1.25 !default; 46 | $lh: 1.5 !default; 47 | $lh-lg: 2 !default; 48 | 49 | // ---------- Paddings ---------- 50 | $pad-sm: 18px; 51 | $pad-lg: 50px; 52 | $pad-xl: 88px; 53 | $pad-10: 10px; 54 | $pad-20: 20px; 55 | 56 | // ---------- Borders ---------- 57 | $border-radius: 15px; 58 | 59 | // ---------- Shadow ---------- 60 | $shadow-1: 0 3px 6px rgba($black, 0.29); 61 | 62 | // ---------- Timing Functions ---------- 63 | $timing-func-1: cubic-bezier(0.4, 0, 0.2, 1); 64 | $timing-elastic-func: cubic-bezier(0.42, 1.36, 0.14, 1.15); 65 | 66 | // ---------- Breakpoints (Endpoints) ---------- 67 | $mobile: 600px; 68 | $tab: 900px; 69 | $tab-landscape: 1200px; 70 | $desktop: 1800px; 71 | -------------------------------------------------------------------------------- /src/Flexo/components/FlexContainer.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import classNames from 'classnames'; 3 | import Axis from './Axis'; 4 | import Consumer from '../../contextSetup'; 5 | import FlexChild from './FlexChild'; 6 | import Button from './Button'; 7 | import Toast from './Toast'; 8 | import { ReactComponent as ShareIcon } from '../assets/share.svg'; 9 | 10 | const FlexContainer = () => { 11 | return ( 12 | 13 | {(context) => { 14 | const { 15 | showMainAxis, 16 | showCrossAxis, 17 | addPadding, 18 | selectedElement, 19 | containerStyles, 20 | children, 21 | } = context.appState; 22 | const { 23 | openSidebar, 24 | handleSelectedElement, 25 | pushViewIntoDB, 26 | } = context; 27 | 28 | const onClickHandler = () => { 29 | handleSelectedElement('container', null); 30 | openSidebar(); 31 | }; 32 | 33 | const handleShareButtonClick = (e) => { 34 | e.stopPropagation(); 35 | return pushViewIntoDB(); 36 | }; 37 | 38 | return ( 39 |
48 | {showMainAxis && 49 | (containerStyles.flexDirection === 'row' || 50 | containerStyles.flexDirection === 'row-reverse' ? ( 51 | 52 | ) : ( 53 | 54 | ))} 55 | {showCrossAxis && 56 | (containerStyles.flexDirection === 'row' || 57 | containerStyles.flexDirection === 'row-reverse' ? ( 58 | 59 | ) : ( 60 | 61 | ))} 62 | {children.map((child) => { 63 | const { id, childStyles } = child; 64 | return ( 65 | 70 | ); 71 | })} 72 |
79 | ); 80 | }} 81 |
82 | ); 83 | }; 84 | 85 | export default FlexContainer; 86 | -------------------------------------------------------------------------------- /src/Flexo/components/Toast.jsx: -------------------------------------------------------------------------------- 1 | import React, { useRef } from 'react'; 2 | import classNames from 'classnames'; 3 | import Consumer from '../../contextSetup'; 4 | import { ReactComponent as CloseIcon } from '../assets/close.svg'; 5 | import { ReactComponent as CheckIcon } from '../assets/check.svg'; 6 | import { ReactComponent as CopyIcon } from '../assets/copy.svg'; 7 | 8 | const Toast = (props) => { 9 | const sharingLinkRef = useRef(); 10 | const copyIconRef = useRef(); 11 | const checkIconRef = useRef(); 12 | 13 | return ( 14 | 15 | {(context) => { 16 | const { toastState, setToastState } = context; 17 | const closeToast = () => setToastState({ isShown: false }); 18 | 19 | const animateCheckAndCopyIcon = () => { 20 | copyIconRef.current.style.display = 'none'; 21 | checkIconRef.current.style.display = 'block'; 22 | 23 | setTimeout(() => { 24 | copyIconRef.current.style.display = 'block'; 25 | checkIconRef.current.style.display = 'none'; 26 | }, 3000); 27 | }; 28 | 29 | const copySharingLink = () => { 30 | if (document.body.createTextRange) { 31 | // This block for IE support 32 | const range = document.body.createTextRange(); 33 | 34 | range.moveToElementText(sharingLinkRef.current); 35 | range.select(); 36 | document.execCommand('Copy'); 37 | } else if (window.getSelection) { 38 | const selection = window.getSelection(); 39 | const range = document.createRange(); 40 | 41 | range.selectNodeContents(sharingLinkRef.current); 42 | selection.removeAllRanges(); 43 | selection.addRange(range); 44 | document.execCommand('Copy'); 45 | } 46 | 47 | animateCheckAndCopyIcon(); 48 | }; 49 | 50 | return ( 51 |
e.stopPropagation()} 56 | > 57 |
58 | 59 | Share this view with others 60 | 61 | 65 |
66 |
70 |
75 | {toastState.shareID} 76 |
77 | 81 | 85 |
86 |
87 | ); 88 | }} 89 |
90 | ); 91 | }; 92 | 93 | export default Toast; 94 | -------------------------------------------------------------------------------- /src/Flexo/stylesheets/components/_sidebar.scss: -------------------------------------------------------------------------------- 1 | .sidebar { 2 | background-color: rgba($black, 0.95); 3 | color: $white; 4 | overflow: hidden; 5 | position: absolute; 6 | transition: transform 300ms $timing-func-1; 7 | z-index: 1000; 8 | 9 | &__header { 10 | align-items: center; 11 | display: flex; 12 | height: 135px; 13 | padding: 0 $pad-lg; 14 | text-transform: capitalize; 15 | 16 | &-title { 17 | font-size: $font-48; 18 | font-weight: $font-bold; 19 | height: calc(#{$font-48} + 18px); 20 | } 21 | 22 | &-icon { 23 | cursor: pointer; 24 | display: block; 25 | font-size: 30px; 26 | height: 1em; 27 | stroke-width: 0; 28 | transition: transform 250ms $timing-func-1; 29 | width: 1em; 30 | 31 | &:hover { 32 | transform: scale(1.3); 33 | } 34 | 35 | &--rotate { 36 | fill: $secondary; 37 | margin-left: auto; 38 | } 39 | 40 | &--close { 41 | fill: $danger; 42 | margin-left: 40px; 43 | } 44 | } 45 | } 46 | 47 | &__body { 48 | display: flex; 49 | flex-wrap: wrap; 50 | height: calc(100% - 135px); 51 | margin-right: -15px; 52 | overflow-x: hidden; 53 | overflow-y: auto; 54 | padding: 0 $pad-lg; 55 | 56 | &-col { 57 | margin-bottom: 20px; 58 | 59 | &-property { 60 | display: inline-block; 61 | margin-bottom: 10px; 62 | } 63 | 64 | .button { 65 | margin: 0 20px 20px 0; 66 | } 67 | } 68 | } 69 | 70 | &--bottom { 71 | border-top-left-radius: $border-radius; 72 | border-top-right-radius: $border-radius; 73 | bottom: 0; 74 | height: 60%; 75 | left: 0; 76 | transform: translateY(100%); 77 | width: 100%; 78 | 79 | .sidebar__body { 80 | &-col { 81 | width: 50%; 82 | } 83 | } 84 | 85 | &.sidebar--open { 86 | transform: translateY(0); 87 | } 88 | } 89 | 90 | &--right { 91 | border-top-left-radius: $border-radius; 92 | bottom: 0; 93 | height: 100%; 94 | max-width: 100%; 95 | right: 0; 96 | top: 0; 97 | transform: translateX(100%); 98 | width: 650px; 99 | 100 | .sidebar__body { 101 | &-col { 102 | width: 100%; 103 | 104 | &-property { 105 | font-size: $font-28; 106 | } 107 | } 108 | } 109 | 110 | &.sidebar--open { 111 | transform: translateX(0); 112 | } 113 | } 114 | 115 | @include is-mobile { 116 | &__header { 117 | height: 70px; 118 | padding: 0 $pad-20; 119 | 120 | &-title { 121 | font-size: $font-40; 122 | height: calc(#{$font-40} + 8px); 123 | } 124 | 125 | &-icon { 126 | font-size: 20px; 127 | 128 | &--close { 129 | margin-left: 20px; 130 | } 131 | } 132 | } 133 | 134 | &__body { 135 | height: calc(100% - 70px - 10px); 136 | margin-top: 10px; 137 | padding: 0 $pad-20; 138 | 139 | &-col { 140 | margin-bottom: 10px; 141 | 142 | &-property { 143 | display: inline-block; 144 | margin-bottom: 10px; 145 | } 146 | 147 | .button { 148 | margin: 0 15px 20px 0; 149 | 150 | &__label { 151 | font-size: $font-18; 152 | height: calc(#{$font-18} + 6px); 153 | } 154 | } 155 | } 156 | } 157 | 158 | &--right { 159 | border-top-left-radius: 0; 160 | } 161 | 162 | &--bottom { 163 | height: 70%; 164 | 165 | .sidebar__body { 166 | &-col { 167 | width: 100%; 168 | } 169 | } 170 | } 171 | } 172 | 173 | @include is-tab { 174 | &--bottom { 175 | .sidebar__body { 176 | &-col { 177 | width: 100%; 178 | } 179 | } 180 | } 181 | } 182 | 183 | @include is-desktop { 184 | &--bottom { 185 | .sidebar__body { 186 | &-col { 187 | width: 50%; 188 | } 189 | } 190 | } 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /src/Flexo/assets/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/Flexo/components/Onboarding.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react'; 2 | import classNames from 'classnames'; 3 | import PropTypes from 'prop-types'; 4 | import { ReactComponent as LeftArrow } from '../assets/arrow_left.svg'; 5 | import { ReactComponent as RightArrow } from '../assets/arrow_right.svg'; 6 | import Button from './Button'; 7 | 8 | const Onboarding = (props) => { 9 | const { pages, showOnboarding } = props; 10 | const [onboardingState, setOnboardingState] = useState({ 11 | activePageIndex: 0, 12 | showOnboarding: showOnboarding || false, 13 | removeOnboardingContainerFromDOM: false, 14 | }); 15 | 16 | const slideToNext = () => { 17 | return setOnboardingState((preState) => { 18 | if (preState.activePageIndex === pages.length - 1) { 19 | return { ...preState, showOnboarding: false }; 20 | } 21 | 22 | return { 23 | ...preState, 24 | activePageIndex: preState.activePageIndex + 1, 25 | }; 26 | }); 27 | }; 28 | 29 | const slideToPrev = () => { 30 | return setOnboardingState((preState) => ({ 31 | ...preState, 32 | activePageIndex: preState.activePageIndex - 1, 33 | })); 34 | }; 35 | 36 | const skipOnboard = () => { 37 | return setOnboardingState((preState) => ({ 38 | ...preState, 39 | showOnboarding: false, 40 | })); 41 | }; 42 | 43 | useEffect(() => { 44 | if (!onboardingState.showOnboarding) { 45 | setTimeout(() => { 46 | setOnboardingState((preState) => ({ 47 | ...preState, 48 | removeOnboardingContainerFromDOM: true, 49 | })); 50 | }, 300); 51 | } 52 | }, [onboardingState.showOnboarding]); 53 | 54 | return ( 55 |
62 |
63 |
70 | 71 |
72 | 73 |
81 | Skip 82 |
83 |
84 | {pages.map((page, index) => ( 85 |
onboardingState.activePageIndex, 94 | })} 95 | > 96 |
97 | {page.illustration} 98 |
99 |
100 |
101 | {page.title} 102 |
103 |
104 | {page.description} 105 |
106 |
107 |
108 | ))} 109 |
110 |
111 | {pages.map((page, index) => ( 112 |
123 | ))} 124 |
125 |
131 |
132 | ); 133 | }; 134 | 135 | Onboarding.defaultProps = { 136 | showOnboarding: true, 137 | }; 138 | 139 | Onboarding.propTypes = { 140 | pages: PropTypes.array.isRequired, 141 | showOnboarding: PropTypes.bool, 142 | }; 143 | 144 | export default Onboarding; 145 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Imgur](https://i.imgur.com/E3Qx3cc.png) 2 | 3 | # Flexo 4 | 5 | ## An Exclusive playground to learn CSS Flexbox 6 | 7 | **FLEXO** is a web app, made to simplify the learning path of CSS Flexbox. One can learn flexbox in an interactive way and also can teach other easily, just using this app. Developers can make a rough sketch here and also can know what properties they have to use before diving into the code. It'll make their coding path smooth.

8 | Are you ready???
9 | **Let's play with Flexbox 🥳 🥳 🥳**

10 | Visit this link ---> [https://www.flexo.icu](https://www.flexo.icu) 11 | 12 | ### Important Links 13 | 14 | - **Production Link:** [https://www.flexo.icu](https://www.flexo.icu) 15 | - **Design Guide Link:** [https://zeroheight.com/2615b42d7](https://zeroheight.com/2615b42d7) 16 | - **Test Domain:** [https://test.flexo.icu](https://test.flexo.icu) 17 | 18 | ### Little demonstration of Flexo 19 | 20 | ![Flexo Layou Operating Process](https://i.imgur.com/6k8XWyj.png)

21 | ![Flexo Sharing Process](https://i.imgur.com/BPnT2RF.png)

22 | ![Flexo Work-flow](https://i.imgur.com/wjtha63.gif) 23 | 24 | ### Supported Features 25 | 26 | Currently Flexo app supported the following features: 27 | 28 | - **Unlimited flex-child creation** 29 | - **Interation of main and cross flex axis with the flex direction** 30 | - **Real-time effect of changing one or more property of Flex-container or a flex-child** 31 | - **View sharing over the hyperlink** (One can make a layout, generate sharing link, send it to other and taping this link, that person can see the same view with its properties) 32 | - **Two positions of sidebar.** Users can switch between them according to their comfort. 33 | - One from left side of the screen 34 | - One comes from bottom side of the screen 35 | 36 |
37 | 38 | > **It takes lots of work (typing flex properties one after one) while teaching other.**

39 | 40 | > **Here is FLEXO. A mighty tool to solve this problem. Flexo app can be used for teaching and learning purpose.**

41 | 42 | > **With the help of Flexo, one can easily know what properties he/she has to use before diving into the code.** 43 | 44 |
45 | 46 | ### Supported Flexbox Properties 47 | 48 | | **Flex-Container** | **Flex-Child** | 49 | | ------------------ | -------------- | 50 | | display | order | 51 | | flex-direction | flex-basis | 52 | | flex-wrap | flex-grow | 53 | | justify-content | flex-shrink | 54 | | align-items | align-self | 55 | | align-content | | 56 | 57 | ## Want to contribute? 58 | 59 | ### Setup Project 60 | 61 | It just requires some simple steps to setup this project on your local machine. Simply, 62 | 63 | - Clone the repo 64 | - Run `yarn install` 65 | - Then run `yarn start` 66 | - Yahooo! You are ready to contribute 🎉 🎉 🎉 67 | 68 | ### How to give PR (Pull Request) 69 | 70 | To submit your code for review, follow these steps : 71 | 72 | - Create a brunch from **master branch** 73 | - Commit your changes in this branch 74 | - Push your branch to remote 75 | - Give this PR for review 76 | - If your changes get approval then author will merge this into the master 77 | 78 | ### How to test your changes on Flexo Test Domain 79 | 80 | **Flexo has a test domain. This domain is used for testing purpose.** When a new feature has been developed or new enhancements or fixes have been made, they are **deployed to the test domain first. Here all the functionalities, design issues, bug fixes, etc. have been checked.** When the changes pass all the test, then they are ready to be merged in the **production branch (master)**.
81 | 82 | You can also check your changes in the test branch. All you have to do is: 83 | 84 | - Create a branch named **'test'** on your local machine by running **`git checkout -b test`** 85 | - Pull from **'test'** branch from the origin by running **`git pull origin test`** 86 | - Then merge your branch(which containing your changes) into the test branch by running **`git merge `** 87 | - Then push the test branch to the origin's test branch by running **`git push origin test`** 88 | - Wait for few minutes and go to **[https://test.flexo.icu](https://test.flexo.icu)** 89 | - You should see your changes here 90 | - If there is any problem, simply leave a message for admin 91 | -------------------------------------------------------------------------------- /src/Flexo/components/Sidebar.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import classNames from 'classnames'; 3 | import Consumer from '../../contextSetup'; 4 | import { ReactComponent as CloseIcon } from '../assets/close.svg'; 5 | import { ReactComponent as BottomRotate } from '../assets/bottomRotate.svg'; 6 | import { ReactComponent as RightRotate } from '../assets/rightRotate.svg'; 7 | import styledOptions from './styleOptions'; 8 | import Button from './Button'; 9 | 10 | const Sidebar = () => { 11 | const convertPropertyNameFromJsxToCss = (propertyName) => { 12 | return propertyName 13 | .split('') 14 | .map((character) => 15 | character >= 'A' && character <= 'Z' 16 | ? `-${character.toLowerCase()}` 17 | : character 18 | ) 19 | .join(''); 20 | }; 21 | 22 | return ( 23 | 24 | {(context) => { 25 | const { 26 | selectedElement, 27 | showSidebar, 28 | sidebarPosition, 29 | containerStyles, 30 | children, 31 | } = context.appState; 32 | const { 33 | closeSidebar, 34 | handleCssPropertyChange, 35 | changeSidebarPosition, 36 | } = context; 37 | 38 | return ( 39 |
48 |
49 |
50 | {`Flex ${selectedElement.type}${ 51 | selectedElement.id 52 | ? `-{${selectedElement.id}}` 53 | : '' 54 | }`} 55 |
56 | {sidebarPosition === 'right' ? ( 57 | { 60 | changeSidebarPosition('bottom'); 61 | }} 62 | /> 63 | ) : ( 64 | { 67 | changeSidebarPosition('right'); 68 | }} 69 | /> 70 | )} 71 | 75 |
76 |
77 | {selectedElement.type === 'container' && 78 | Object.keys(containerStyles).map((style) => ( 79 |
83 | {`${convertPropertyNameFromJsxToCss( 84 | style 85 | )} > ${containerStyles[style]}`} 86 |
87 | {styledOptions[style] && 88 | styledOptions[ 89 | style 90 | ].map((option) => ( 91 |
120 |
121 | ))} 122 | {selectedElement.type === 'child' && 123 | Object.keys( 124 | children[selectedElement.id - 1].childStyles 125 | ).map((style) => ( 126 |
130 | {`${convertPropertyNameFromJsxToCss( 131 | style 132 | )} > ${ 133 | children[selectedElement.id - 1] 134 | .childStyles[style] 135 | }`} 136 |
137 | {styledOptions[style] && 138 | styledOptions[ 139 | style 140 | ].map((option) => ( 141 |
171 |
172 | ))} 173 |
174 |
175 | ); 176 | }} 177 |
178 | ); 179 | }; 180 | 181 | export default Sidebar; 182 | -------------------------------------------------------------------------------- /src/Flexo/stylesheets/_reset.scss: -------------------------------------------------------------------------------- 1 | *, 2 | *::after, 3 | *::before { 4 | -webkit-tap-highlight-color: transparent; 5 | box-sizing: border-box; 6 | } 7 | 8 | html { 9 | -ms-text-size-adjust: 100%; 10 | -webkit-text-size-adjust: 100%; 11 | font-family: $font-khula; 12 | font-size: 16px; 13 | font-weight: $font-normal; 14 | 15 | @include is-mobile { 16 | font-size: 12px; 17 | } 18 | } 19 | 20 | body { 21 | -moz-osx-font-smoothing: grayscale; 22 | -webkit-font-smoothing: antialiased; 23 | color: $black; 24 | line-height: $lh; 25 | } 26 | 27 | html, 28 | body { 29 | height: 100%; 30 | overflow: hidden; 31 | position: relative; 32 | } 33 | 34 | #root, 35 | .App { 36 | height: 100%; 37 | } 38 | 39 | html, 40 | body, 41 | div, 42 | span, 43 | applet, 44 | object, 45 | iframe, 46 | h1, 47 | h2, 48 | h3, 49 | h4, 50 | h5, 51 | h6, 52 | p, 53 | blockquote, 54 | pre, 55 | a, 56 | abbr, 57 | acronym, 58 | address, 59 | big, 60 | cite, 61 | code, 62 | del, 63 | dfn, 64 | em, 65 | img, 66 | ins, 67 | kbd, 68 | q, 69 | s, 70 | samp, 71 | small, 72 | strike, 73 | strong, 74 | sub, 75 | sup, 76 | tt, 77 | var, 78 | b, 79 | u, 80 | i, 81 | center, 82 | dl, 83 | dt, 84 | dd, 85 | ol, 86 | ul, 87 | li, 88 | fieldset, 89 | form, 90 | label, 91 | legend, 92 | table, 93 | caption, 94 | tbody, 95 | tfoot, 96 | thead, 97 | tr, 98 | th, 99 | td, 100 | article, 101 | aside, 102 | canvas, 103 | details, 104 | embed, 105 | figure, 106 | figcaption, 107 | footer, 108 | header, 109 | hgroup, 110 | menu, 111 | nav, 112 | output, 113 | ruby, 114 | section, 115 | summary, 116 | time, 117 | mark, 118 | audio, 119 | video { 120 | -moz-osx-font-smoothing: grayscale; 121 | -webkit-font-smoothing: antialiased; 122 | border: 0; 123 | margin: 0; 124 | padding: 0; 125 | vertical-align: baseline; 126 | } 127 | 128 | :focus { 129 | outline: 0; 130 | } 131 | 132 | article, 133 | aside, 134 | details, 135 | figcaption, 136 | figure, 137 | footer, 138 | header, 139 | hgroup, 140 | menu, 141 | nav, 142 | section { 143 | display: block; 144 | } 145 | 146 | ol, 147 | ul { 148 | list-style: none; 149 | } 150 | 151 | blockquote, 152 | q { 153 | quotes: none; 154 | } 155 | 156 | blockquote:before, 157 | blockquote:after, 158 | q:before, 159 | q:after { 160 | content: ''; 161 | content: none; 162 | } 163 | 164 | code { 165 | font-family: $font-inconsolata; 166 | font-size: $font-32; 167 | } 168 | 169 | table { 170 | border-collapse: collapse; 171 | border-spacing: 0; 172 | } 173 | 174 | input[type='search']::-webkit-search-cancel-button, 175 | input[type='search']::-webkit-search-decoration, 176 | input[type='search']::-webkit-search-results-button, 177 | input[type='search']::-webkit-search-results-decoration { 178 | -moz-appearance: none; 179 | -webkit-appearance: none; 180 | } 181 | 182 | input[type='search'] { 183 | -moz-appearance: none; 184 | -moz-box-sizing: content-box; 185 | -webkit-appearance: none; 186 | -webkit-box-sizing: content-box; 187 | box-sizing: content-box; 188 | } 189 | 190 | textarea { 191 | overflow: auto; 192 | resize: vertical; 193 | vertical-align: top; 194 | } 195 | 196 | a, 197 | img { 198 | display: inline-block; 199 | } 200 | 201 | a { 202 | color: $danger; 203 | text-decoration: none; 204 | transition: color 0.2s $timing-func-1; 205 | 206 | &:hover { 207 | color: $secondary; 208 | } 209 | } 210 | 211 | [hidden] { 212 | display: none; 213 | } 214 | 215 | a:focus { 216 | outline: thin dotted; 217 | } 218 | 219 | a:active, 220 | a:hover { 221 | outline: 0; 222 | } 223 | 224 | img { 225 | -ms-interpolation-mode: bicubic; 226 | border: 0; 227 | vertical-align: middle; 228 | } 229 | 230 | form { 231 | margin: 0; 232 | } 233 | 234 | button, 235 | input, 236 | select, 237 | textarea { 238 | *vertical-align: middle; 239 | font-size: $font-16; 240 | margin: 0; 241 | vertical-align: baseline; 242 | } 243 | 244 | button, 245 | input { 246 | line-height: normal; 247 | } 248 | 249 | button, 250 | select { 251 | text-transform: none; 252 | } 253 | 254 | button, 255 | html input[type='button'], 256 | input[type='reset'], 257 | input[type='submit'] { 258 | -webkit-appearance: button; 259 | *overflow: visible; 260 | cursor: pointer; 261 | } 262 | 263 | button[disabled], 264 | html input[disabled] { 265 | cursor: default; 266 | } 267 | 268 | input[type='checkbox'], 269 | input[type='radio'] { 270 | *height: 13px; 271 | *width: 13px; 272 | box-sizing: border-box; 273 | padding: 0; 274 | } 275 | 276 | input[type='search'] { 277 | -moz-box-sizing: content-box; 278 | -webkit-appearance: textfield; 279 | -webkit-box-sizing: content-box; 280 | box-sizing: content-box; 281 | } 282 | 283 | input[type='search']::-webkit-search-cancel-button, 284 | input[type='search']::-webkit-search-decoration { 285 | -webkit-appearance: none; 286 | } 287 | 288 | button::-moz-focus-inner, 289 | input::-moz-focus-inner { 290 | border: 0; 291 | padding: 0; 292 | } 293 | 294 | textarea { 295 | overflow: auto; 296 | vertical-align: top; 297 | } 298 | 299 | table { 300 | border-collapse: collapse; 301 | border-spacing: 0; 302 | } 303 | 304 | html, 305 | button, 306 | input, 307 | select, 308 | textarea { 309 | color: $black; 310 | } 311 | 312 | ::-moz-selection { 313 | background-color: $secondary-light; 314 | text-shadow: none; 315 | } 316 | 317 | ::selection { 318 | background-color: $secondary-light; 319 | text-shadow: none; 320 | } 321 | 322 | fieldset { 323 | border: 0; 324 | margin: 0; 325 | padding: 0; 326 | } 327 | 328 | .chromeframe { 329 | background-color: $secondary-light; 330 | color: $black; 331 | margin: 0.2em 0; 332 | padding: 0.2em 0; 333 | } 334 | -------------------------------------------------------------------------------- /src/Flexo/stylesheets/components/_onboarding.scss: -------------------------------------------------------------------------------- 1 | $bottombar-height: 130px; 2 | $topbar-height: 80px; 3 | 4 | .onboarding { 5 | background-color: $white; 6 | display: flex; 7 | height: 100%; 8 | left: 0; 9 | opacity: 0; 10 | position: absolute; 11 | top: 0; 12 | transition: opacity 300ms $timing-func-1; 13 | width: 100%; 14 | z-index: 200000; 15 | 16 | > * { 17 | position: absolute; 18 | width: 100%; 19 | } 20 | 21 | &__topbar { 22 | align-items: center; 23 | display: flex; 24 | height: $topbar-height; 25 | padding: 0 20px; 26 | top: 0; 27 | 28 | &-back-icon, 29 | &-skip { 30 | cursor: pointer; 31 | font-size: $font-20; 32 | padding: 0 10px; 33 | transform: scale(0); 34 | transition: transform 300ms $timing-elastic-func; 35 | user-select: none; 36 | 37 | &--shown { 38 | transform: scale(1); 39 | } 40 | 41 | &:active { 42 | transform: scale(0.8); 43 | } 44 | } 45 | 46 | &-back-icon { 47 | margin-right: auto; 48 | 49 | svg { 50 | display: block; 51 | fill: $black; 52 | height: 1em; 53 | stroke-width: 0; 54 | width: 1em; 55 | } 56 | } 57 | 58 | &-skip { 59 | font-weight: $font-bold; 60 | margin-left: auto; 61 | } 62 | } 63 | 64 | &__bottombar { 65 | align-items: center; 66 | bottom: 0; 67 | display: flex; 68 | height: $bottombar-height; 69 | padding: 0 30px; 70 | 71 | &-page-indicator { 72 | left: 50%; 73 | position: absolute; 74 | top: 50%; 75 | transform: translate(-50%, -50%); 76 | 77 | &-bar { 78 | background-color: $primary-light; 79 | border-radius: 15px; 80 | display: inline-block; 81 | height: 10px; 82 | margin-right: 15px; 83 | transition: background-color 300ms $timing-func-1; 84 | width: 10px; 85 | 86 | &:last-of-type { 87 | margin-right: 0; 88 | } 89 | 90 | &--active { 91 | background-color: $primary; 92 | } 93 | } 94 | } 95 | 96 | &-next-button { 97 | background-color: $primary; 98 | height: 70px; 99 | margin-left: auto; 100 | width: 70px; 101 | 102 | .button__label { 103 | font-size: $font-24; 104 | height: calc(#{$font-24}); 105 | margin-left: 5px; 106 | 107 | svg { 108 | display: block; 109 | fill: $white; 110 | height: 1em; 111 | stroke-width: 0; 112 | width: 1em; 113 | } 114 | } 115 | 116 | &:hover { 117 | background-color: $primary; 118 | } 119 | } 120 | } 121 | 122 | &__page { 123 | align-items: center; 124 | bottom: $bottombar-height; 125 | display: flex; 126 | flex-flow: column; 127 | padding: 0 30px; 128 | top: $topbar-height; 129 | transition: transform 600ms $timing-func-1; 130 | 131 | > * { 132 | max-width: 100%; 133 | width: 665px; 134 | } 135 | 136 | &-illustration { 137 | align-items: center; 138 | display: flex; 139 | flex-grow: 1; 140 | flex-wrap: wrap; 141 | justify-content: center; 142 | 143 | svg, 144 | img { 145 | display: block; 146 | width: 100%; 147 | } 148 | } 149 | 150 | &-text { 151 | align-content: flex-end; 152 | align-items: flex-end; 153 | display: flex; 154 | flex-grow: 1; 155 | flex-wrap: wrap; 156 | justify-content: center; 157 | 158 | > * { 159 | width: 100%; 160 | } 161 | 162 | &-title { 163 | font-size: $font-32; 164 | font-weight: $font-bold; 165 | text-align: center; 166 | } 167 | 168 | &-description { 169 | font-size: $font-24; 170 | font-weight: $font-normal; 171 | margin: 15px 0 0; 172 | text-align: center; 173 | } 174 | } 175 | 176 | &--active { 177 | transform: translateX(0); 178 | } 179 | 180 | &--left { 181 | transform: translateX(-100%); 182 | } 183 | 184 | &--right { 185 | transform: translateX(100%); 186 | } 187 | } 188 | 189 | &--active { 190 | opacity: 1; 191 | } 192 | 193 | &--removed { 194 | display: none; 195 | } 196 | 197 | @include is-mobile { 198 | $bottombar-height: 70px; 199 | $topbar-height: 60px; 200 | 201 | &__topbar { 202 | height: $topbar-height; 203 | padding: 0 10px; 204 | } 205 | 206 | &__bottombar { 207 | height: $bottombar-height; 208 | padding: 0 20px; 209 | 210 | &-page-indicator { 211 | left: 20px; 212 | transform: translate(0, 0); 213 | 214 | &-bar { 215 | margin-right: 10px; 216 | } 217 | } 218 | 219 | &-next-button { 220 | height: 50px; 221 | width: 50px; 222 | 223 | .button__label { 224 | font-size: $font-20; 225 | height: calc(#{$font-20}); 226 | margin-left: 0; 227 | } 228 | } 229 | } 230 | 231 | &__page { 232 | bottom: $bottombar-height; 233 | padding: 0 20px; 234 | top: $topbar-height; 235 | 236 | &-text { 237 | flex-grow: 0; 238 | padding-bottom: 30px; 239 | 240 | &-title { 241 | font-size: $font-24; 242 | } 243 | 244 | &-description { 245 | font-size: $font-18; 246 | } 247 | } 248 | } 249 | } 250 | 251 | @include is-tab { 252 | &__page { 253 | > * { 254 | width: 80%; 255 | } 256 | 257 | &-text { 258 | flex-grow: 0; 259 | padding-bottom: 30px; 260 | 261 | &-title { 262 | font-size: $font-24; 263 | } 264 | 265 | &-description { 266 | font-size: $font-20; 267 | } 268 | } 269 | } 270 | } 271 | 272 | @include is-desktop { 273 | &__page { 274 | > * { 275 | width: 665px; 276 | } 277 | 278 | &-text { 279 | flex-grow: 1; 280 | padding-bottom: 0; 281 | 282 | &-title { 283 | font-size: $font-24; 284 | } 285 | 286 | &-description { 287 | font-size: $font-20; 288 | } 289 | } 290 | } 291 | } 292 | } 293 | -------------------------------------------------------------------------------- /src/serviceWorker.js: -------------------------------------------------------------------------------- 1 | // This optional code is used to register a service worker. 2 | // register() is not called by default. 3 | 4 | // This lets the app load faster on subsequent visits in production, and gives 5 | // it offline capabilities. However, it also means that developers (and users) 6 | // will only see deployed updates on subsequent visits to a page, after all the 7 | // existing tabs open on the page have been closed, since previously cached 8 | // resources are updated in the background. 9 | 10 | // To learn more about the benefits of this model and instructions on how to 11 | // opt-in, read https://bit.ly/CRA-PWA 12 | 13 | const isLocalhost = Boolean( 14 | window.location.hostname === 'localhost' || 15 | // [::1] is the IPv6 localhost address. 16 | window.location.hostname === '[::1]' || 17 | // 127.0.0.0/8 are considered localhost for IPv4. 18 | window.location.hostname.match( 19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ 20 | ) 21 | ); 22 | 23 | export function register(config) { 24 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { 25 | // The URL constructor is available in all browsers that support SW. 26 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href); 27 | if (publicUrl.origin !== window.location.origin) { 28 | // Our service worker won't work if PUBLIC_URL is on a different origin 29 | // from what our page is served on. This might happen if a CDN is used to 30 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374 31 | return; 32 | } 33 | 34 | window.addEventListener('load', () => { 35 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; 36 | 37 | if (isLocalhost) { 38 | // This is running on localhost. Let's check if a service worker still exists or not. 39 | checkValidServiceWorker(swUrl, config); 40 | 41 | // Add some additional logging to localhost, pointing developers to the 42 | // service worker/PWA documentation. 43 | navigator.serviceWorker.ready.then(() => { 44 | console.log( 45 | 'This web app is being served cache-first by a service ' + 46 | 'worker. To learn more, visit https://bit.ly/CRA-PWA' 47 | ); 48 | }); 49 | } else { 50 | // Is not localhost. Just register service worker 51 | registerValidSW(swUrl, config); 52 | } 53 | }); 54 | } 55 | } 56 | 57 | function registerValidSW(swUrl, config) { 58 | navigator.serviceWorker 59 | .register(swUrl) 60 | .then(registration => { 61 | registration.onupdatefound = () => { 62 | const installingWorker = registration.installing; 63 | if (installingWorker == null) { 64 | return; 65 | } 66 | installingWorker.onstatechange = () => { 67 | if (installingWorker.state === 'installed') { 68 | if (navigator.serviceWorker.controller) { 69 | // At this point, the updated precached content has been fetched, 70 | // but the previous service worker will still serve the older 71 | // content until all client tabs are closed. 72 | console.log( 73 | 'New content is available and will be used when all ' + 74 | 'tabs for this page are closed. See https://bit.ly/CRA-PWA.' 75 | ); 76 | 77 | // Execute callback 78 | if (config && config.onUpdate) { 79 | config.onUpdate(registration); 80 | } 81 | } else { 82 | // At this point, everything has been precached. 83 | // It's the perfect time to display a 84 | // "Content is cached for offline use." message. 85 | console.log('Content is cached for offline use.'); 86 | 87 | // Execute callback 88 | if (config && config.onSuccess) { 89 | config.onSuccess(registration); 90 | } 91 | } 92 | } 93 | }; 94 | }; 95 | }) 96 | .catch(error => { 97 | console.error('Error during service worker registration:', error); 98 | }); 99 | } 100 | 101 | function checkValidServiceWorker(swUrl, config) { 102 | // Check if the service worker can be found. If it can't reload the page. 103 | fetch(swUrl, { 104 | headers: { 'Service-Worker': 'script' }, 105 | }) 106 | .then(response => { 107 | // Ensure service worker exists, and that we really are getting a JS file. 108 | const contentType = response.headers.get('content-type'); 109 | if ( 110 | response.status === 404 || 111 | (contentType != null && contentType.indexOf('javascript') === -1) 112 | ) { 113 | // No service worker found. Probably a different app. Reload the page. 114 | navigator.serviceWorker.ready.then(registration => { 115 | registration.unregister().then(() => { 116 | window.location.reload(); 117 | }); 118 | }); 119 | } else { 120 | // Service worker found. Proceed as normal. 121 | registerValidSW(swUrl, config); 122 | } 123 | }) 124 | .catch(() => { 125 | console.log( 126 | 'No internet connection found. App is running in offline mode.' 127 | ); 128 | }); 129 | } 130 | 131 | export function unregister() { 132 | if ('serviceWorker' in navigator) { 133 | navigator.serviceWorker.ready 134 | .then(registration => { 135 | registration.unregister(); 136 | }) 137 | .catch(error => { 138 | console.error(error.message); 139 | }); 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /src/contextSetup.jsx: -------------------------------------------------------------------------------- 1 | import React, { createContext, useState, useEffect } from 'react'; 2 | import firebase from './dbConfig'; 3 | 4 | const { Consumer, Provider } = createContext(); 5 | const ContextProvider = (props) => { 6 | const [appState, setAppState] = useState({ 7 | showMainAxis: true, 8 | showCrossAxis: true, 9 | addPadding: true, 10 | showSidebar: false, 11 | sidebarPosition: 'right', 12 | selectedElement: { 13 | type: '', 14 | id: null, 15 | }, 16 | containerStyles: { 17 | display: 'flex', 18 | children: 1, 19 | flexDirection: 'row', 20 | flexWrap: 'nowrap', 21 | justifyContent: 'flex-start', 22 | alignItems: 'stretch', 23 | alignContent: 'stretch', 24 | }, 25 | children: [ 26 | { 27 | id: 1, 28 | childStyles: { 29 | order: '0', 30 | flexBasis: 'auto', 31 | flexGrow: '0', 32 | flexShrink: '1', 33 | alignSelf: 'auto', 34 | }, 35 | }, 36 | ], 37 | }); 38 | const [loadingState, setLoadingState] = useState({ 39 | isLoading: true, 40 | removeLoadingScreenFromDOM: false, 41 | }); 42 | const [toastState, setToastState] = useState({ 43 | isShown: false, 44 | shareID: 'No ID to share', 45 | }); 46 | 47 | useEffect(() => { 48 | const fetchView = async () => { 49 | const urlSegments = window.location.href.split('?shareID='); 50 | const urlSegmentsAfterFilteringLinkAssignedByFB = 51 | urlSegments.length > 1 52 | ? urlSegments[1].split('&flexoclid=1') 53 | : ''; 54 | const sharedViewID = urlSegmentsAfterFilteringLinkAssignedByFB[0]; 55 | const db = firebase.database(); 56 | const sharedView = sharedViewID 57 | ? await ( 58 | await db 59 | .ref(`sharedViews/${sharedViewID}`) 60 | .once('value') 61 | ).val() 62 | : null; 63 | 64 | return sharedView 65 | ? setAppState( 66 | { ...sharedView }, 67 | setLoadingState((preState) => ({ 68 | ...preState, 69 | isLoading: false, 70 | })) 71 | ) 72 | : setLoadingState((preState) => ({ 73 | ...preState, 74 | isLoading: false, 75 | })); 76 | }; 77 | 78 | fetchView(); 79 | }, []); 80 | 81 | useEffect(() => { 82 | if (!loadingState.isLoading) { 83 | setTimeout(() => { 84 | setLoadingState((preState) => ({ 85 | ...preState, 86 | removeLoadingScreenFromDOM: true, 87 | })); 88 | }, 3500); 89 | } 90 | }, [loadingState.isLoading]); 91 | 92 | const pushViewIntoDB = async () => { 93 | const db = firebase.database(); 94 | const referenceOfpushedView = await db 95 | .ref('sharedViews') 96 | .push(appState); 97 | return setToastState({ 98 | isShown: true, 99 | shareID: `${window.location.origin}/?shareID=${referenceOfpushedView.key}&flexoclid=1`, 100 | }); 101 | }; 102 | 103 | const handleMainAxisToggle = () => 104 | setAppState((preState) => ({ 105 | ...preState, 106 | showMainAxis: !preState.showMainAxis, 107 | })); 108 | 109 | const handleCrossAxisToggle = () => 110 | setAppState((preState) => ({ 111 | ...preState, 112 | showCrossAxis: !preState.showCrossAxis, 113 | })); 114 | 115 | const handlePaddingToggle = () => 116 | setAppState((preState) => ({ 117 | ...preState, 118 | addPadding: !preState.addPadding, 119 | })); 120 | 121 | const openSidebar = () => 122 | setAppState((preState) => ({ 123 | ...preState, 124 | showSidebar: true, 125 | })); 126 | 127 | const changeSidebarPosition = (position) => 128 | setAppState((preState) => ({ 129 | ...preState, 130 | sidebarPosition: position, 131 | })); 132 | 133 | const closeSidebar = () => 134 | setAppState((preState) => ({ 135 | ...preState, 136 | showSidebar: false, 137 | })); 138 | 139 | const handleSelectedElement = (elementType, id) => 140 | setAppState((preState) => ({ 141 | ...preState, 142 | selectedElement: { type: elementType, id }, 143 | })); 144 | 145 | const processPropertyValue = (styleObj, propertyName, propertyValue) => { 146 | if ( 147 | propertyValue.split('')[0] === '+' || 148 | propertyValue.split('')[0] === '-' 149 | ) { 150 | const calculatedValue = 151 | parseInt(styleObj[propertyName], 10) + 152 | parseInt(propertyValue, 10); 153 | 154 | if ( 155 | (propertyName === 'flexGrow' || 156 | propertyName === 'flexShrink') && 157 | calculatedValue < 0 158 | ) { 159 | return '0'; 160 | } 161 | 162 | if (propertyName === 'children' && calculatedValue < 1) { 163 | return '1'; 164 | } 165 | 166 | return calculatedValue.toString(); 167 | } 168 | 169 | return propertyValue; 170 | }; 171 | 172 | const createOrDestroyChildren = (numberOfChildren) => { 173 | const defaultChildStyles = { 174 | order: '0', 175 | flexBasis: 'auto', 176 | flexGrow: '0', 177 | flexShrink: '1', 178 | alignSelf: 'auto', 179 | }; 180 | const remainingChildren = [...appState.children]; 181 | const childrenNumberDifference = 182 | numberOfChildren - remainingChildren.length; 183 | 184 | if (childrenNumberDifference > 0) { 185 | for (let i = 0; i < childrenNumberDifference; i++) { 186 | remainingChildren.push({ 187 | id: remainingChildren.length + 1, 188 | childStyles: { ...defaultChildStyles }, 189 | }); 190 | } 191 | } else if (childrenNumberDifference < 0) { 192 | const splicingIndex = 193 | remainingChildren.length - Math.abs(childrenNumberDifference); 194 | remainingChildren.splice( 195 | splicingIndex, 196 | Math.abs(childrenNumberDifference) 197 | ); 198 | } 199 | 200 | return setAppState((preState) => ({ 201 | ...preState, 202 | children: [...remainingChildren], 203 | })); 204 | }; 205 | 206 | const handleCssPropertyChange = (childId, propertyName, propertyValue) => { 207 | if (childId) { 208 | const childrenAfterStyleUpdate = [...appState.children]; 209 | 210 | childrenAfterStyleUpdate[childId - 1].childStyles[ 211 | propertyName 212 | ] = processPropertyValue( 213 | childrenAfterStyleUpdate[childId - 1].childStyles, 214 | propertyName, 215 | propertyValue 216 | ); 217 | 218 | return setAppState((preState) => ({ 219 | ...preState, 220 | children: childrenAfterStyleUpdate, 221 | })); 222 | } else { 223 | const containerAfterStyleUpdate = { 224 | ...appState.containerStyles, 225 | }; 226 | 227 | containerAfterStyleUpdate[propertyName] = processPropertyValue( 228 | containerAfterStyleUpdate, 229 | propertyName, 230 | propertyValue 231 | ); 232 | 233 | if (propertyName === 'children') { 234 | createOrDestroyChildren( 235 | containerAfterStyleUpdate[propertyName] 236 | ); 237 | } 238 | 239 | return setAppState((preState) => ({ 240 | ...preState, 241 | containerStyles: containerAfterStyleUpdate, 242 | })); 243 | } 244 | }; 245 | 246 | return ( 247 | 264 | {props.children} 265 | 266 | ); 267 | }; 268 | 269 | export { ContextProvider }; 270 | export default Consumer; 271 | -------------------------------------------------------------------------------- /src/Flexo/assets/loading_art.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/Flexo/stylesheets/components/_loading-screen.scss: -------------------------------------------------------------------------------- 1 | .loading-screen { 2 | background-color: $white; 3 | display: block; 4 | height: 100%; 5 | left: 0; 6 | position: absolute; 7 | top: 0; 8 | transition: opacity 0.5s $timing-func-1 3s; 9 | width: 100%; 10 | z-index: 100000; 11 | 12 | &__art-wrapper { 13 | left: 45%; 14 | position: absolute; 15 | top: 50%; 16 | transform: translate(-50%, -50%); 17 | width: 50%; 18 | } 19 | 20 | &__art { 21 | width: 100%; 22 | 23 | .sleepy-man { 24 | animation: breething 2.5s ease infinite; 25 | } 26 | 27 | .sleep-bubble { 28 | animation: bubble 2.5s ease infinite; 29 | transform-origin: 21% 25%; 30 | } 31 | } 32 | 33 | &__spin { 34 | animation: spin 2.5s linear infinite; 35 | border-radius: 50%; 36 | border: 10px solid $secondary; 37 | height: 20vw; 38 | position: absolute; 39 | right: -10vw; 40 | top: -25%; 41 | width: 20vw; 42 | z-index: -1; 43 | 44 | &::after { 45 | animation: color-shuffle 2.5s linear infinite; 46 | background-color: $secondary; 47 | border-radius: 50%; 48 | content: ''; 49 | display: block; 50 | height: 20%; 51 | left: 3.33%; 52 | position: absolute; 53 | top: 3.33%; 54 | width: 20%; 55 | } 56 | } 57 | 58 | &__text { 59 | bottom: 0; 60 | font-size: $font-24; 61 | font-weight: $font-semi-bold; 62 | left: 25%; 63 | position: absolute; 64 | 65 | &::after { 66 | animation: width-change 2.5s linear infinite; 67 | content: '........'; 68 | display: inline-block; 69 | height: 4px; 70 | line-height: 0; 71 | margin-left: 5px; 72 | overflow: hidden; 73 | width: 0; 74 | } 75 | } 76 | 77 | &--hidden { 78 | opacity: 0; 79 | } 80 | 81 | &--removed { 82 | display: none; 83 | } 84 | 85 | @include is-mobile { 86 | .loading-screen { 87 | &__art-wrapper { 88 | left: 45%; 89 | width: 70%; 90 | } 91 | 92 | &__spin { 93 | border-width: 3px; 94 | height: 23vw; 95 | right: -11vw; 96 | top: -10%; 97 | width: 23vw; 98 | 99 | &::after { 100 | height: 30%; 101 | left: -5%; 102 | right: -5%; 103 | width: 30%; 104 | } 105 | } 106 | 107 | &__text { 108 | bottom: -5px; 109 | font-size: $font-16; 110 | 111 | &::after { 112 | height: 2px; 113 | } 114 | } 115 | } 116 | } 117 | 118 | @include is-tab { 119 | .loading-screen { 120 | &__art-wrapper { 121 | left: 45%; 122 | width: 60%; 123 | } 124 | 125 | &__spin { 126 | border-width: 6px; 127 | height: 22vw; 128 | right: -11vw; 129 | top: -20%; 130 | width: 22vw; 131 | 132 | &::after { 133 | height: 25%; 134 | left: 0; 135 | right: 0; 136 | width: 25%; 137 | } 138 | } 139 | 140 | &__text { 141 | bottom: -10px; 142 | font-size: $font-18; 143 | 144 | &::after { 145 | height: 3px; 146 | } 147 | } 148 | } 149 | } 150 | 151 | @include is-desktop { 152 | .loading-screen { 153 | &__art-wrapper { 154 | left: 45%; 155 | width: 50%; 156 | } 157 | 158 | &__spin { 159 | border-width: 10px; 160 | height: 20vw; 161 | right: -10vw; 162 | top: -25%; 163 | width: 20vw; 164 | 165 | &::after { 166 | height: 20%; 167 | left: 3.33%; 168 | right: 3.33%; 169 | width: 20%; 170 | } 171 | } 172 | 173 | &__text { 174 | bottom: 0; 175 | font-size: $font-24; 176 | 177 | &::after { 178 | height: 4px; 179 | } 180 | } 181 | } 182 | } 183 | } 184 | 185 | @keyframes spin { 186 | 0% { 187 | border-color: $secondary; 188 | transform: rotate(0deg); 189 | } 190 | 191 | 50% { 192 | border-color: $success; 193 | } 194 | 195 | 100% { 196 | border-color: $secondary; 197 | transform: rotate(360deg); 198 | } 199 | } 200 | 201 | @keyframes color-shuffle { 202 | 0% { 203 | background-color: $secondary; 204 | } 205 | 206 | 50% { 207 | background-color: $success; 208 | } 209 | 210 | 100% { 211 | background-color: $secondary; 212 | } 213 | } 214 | 215 | @keyframes breething { 216 | 0% { 217 | d: path( 218 | 'M450.256 203.13s-10.275-38.179 31.35-47.6c23.265-5.266 44.74 10.059 56.979 32.511 6.761 13.269 10.723 28.939 10.723 45.534 0 1.514-0.033 3.020-0.098 4.517l0.007-0.213c-0.615 7.207-12.644 11.276 12.423 22.372s70.469 3.27 103.171 14.222 34.042 14.183 58.045 28.103-15.118 2.101 51.498 18.779c24.644 6.172 35.513 8.289 40.382 42.163s4.176 75.131 17.637 82.472c4.826 2.57 10.568 5.056 16.518 7.097l0.861 0.257c7.499 2.826 9.807 3.677 12.607 3.94 0.245 0.029 0.53 0.045 0.818 0.045 1.414 0 2.735-0.398 3.858-1.088l-0.032 0.018s2.889-0.925 2.889 0.633c0 4.62-22.977 3.646-39.022 6.083s-19.129 7.551-25.151 3.677-1.804-8.463-2.461-19.496-1.006-12.024-4.489-30.176c-11.388-45.197-24.426-53.475-24.426-53.475s-118.717 0.084-116.981 0-36.679 0.854-54.468-8.197-23.273-28.448-25.85-5.216c-2.411 21.816-5.059 160.074-5.059 160.074 2.276 1.632 3.8 4.19 4.017 7.112l0.002 0.032c0.359 2.040 0.564 4.39 0.564 6.787 0 2.904-0.301 5.737-0.874 8.47l0.047-0.267c-0.888 4.83-3.225 4.875-4.3 4.11s0-7.173 0-7.173 0.281-5.873-0.775-4.99-0.63 5.778-2.443 11.055c-1.143 3.847-2.782 7.202-4.88 10.214l0.076-0.115c-2.015 2.986-3.383 4.092-3.254 1.839s2.663-5.403 3.769-10.834 2.7-12.032 0.654-10.895-4.108 9.862-7.412 15.759-4.912 8.032-5.802 7.861 2.24-8.549 2.24-8.549 0.911-2.808 2.248-6.566 3.774-8.074 3.11-8.502-2.936 1.726-5.778 6.784-5.143 13.658-5.552 13.453c-0.012-0.354-0.019-0.771-0.019-1.189 0-5.062 1.004-9.889 2.823-14.293l-0.091 0.249c2.626-5.97 6.992-11.015 6.724-11.557s-4.762 4.486-7.803 9.379-3.782 11.218-4.355 10.199c-0.194-1.151-0.305-2.477-0.305-3.829 0-3.799 0.875-7.393 2.435-10.592l-0.063 0.143c2.516-4.987 5.738-9.233 9.594-12.804l0.029-0.027-15.478-217.103z' 219 | ); 220 | } 221 | 222 | 50% { 223 | d: path( 224 | 'M450.256 203.13s-10.275-38.179 31.35-47.6c23.265-5.266 44.74 10.059 56.979 32.511 6.761 13.269 10.723 28.939 10.723 45.534 0 1.514-0.033 3.020-0.098 4.517l0.007-0.213c-0.615 7.207-12.644 11.276 12.423 22.372s67.146-21.091 107.452-10.506 29.761 38.919 53.775 52.842-15.118 2.101 51.498 18.779c24.644 6.172 35.513 8.289 40.382 42.163s4.176 75.131 17.637 82.472c4.826 2.57 10.568 5.056 16.518 7.097l0.861 0.257c7.499 2.826 9.807 3.677 12.607 3.94 0.245 0.029 0.53 0.045 0.818 0.045 1.414 0 2.735-0.398 3.858-1.088l-0.032 0.018s2.889-0.925 2.889 0.633c0 4.62-22.977 3.646-39.022 6.083s-19.129 7.551-25.151 3.677-1.804-8.463-2.461-19.496-1.006-12.024-4.489-30.176c-11.388-45.197-24.426-53.475-24.426-53.475s-118.717 0.084-116.981 0-36.679 0.854-54.468-8.197-23.273-28.448-25.85-5.216c-2.411 21.816-5.059 160.074-5.059 160.074 2.276 1.632 3.8 4.19 4.017 7.112l0.002 0.032c0.359 2.040 0.564 4.39 0.564 6.787 0 2.904-0.301 5.737-0.874 8.47l0.047-0.267c-0.888 4.83-3.225 4.875-4.3 4.11s0-7.173 0-7.173 0.281-5.873-0.775-4.99-0.63 5.778-2.443 11.055c-1.143 3.847-2.782 7.202-4.88 10.214l0.076-0.115c-2.015 2.986-3.383 4.092-3.254 1.839s2.663-5.403 3.769-10.834 2.7-12.032 0.654-10.895-4.108 9.862-7.412 15.759-4.912 8.032-5.802 7.861 2.24-8.549 2.24-8.549 0.911-2.808 2.248-6.566 3.774-8.074 3.11-8.502-2.936 1.726-5.778 6.784-5.143 13.658-5.552 13.453c-0.012-0.354-0.019-0.771-0.019-1.189 0-5.062 1.004-9.889 2.823-14.293l-0.091 0.249c2.626-5.97 6.992-11.015 6.724-11.557s-4.762 4.486-7.803 9.379-3.782 11.218-4.355 10.199c-0.194-1.151-0.305-2.477-0.305-3.829 0-3.799 0.875-7.393 2.435-10.592l-0.063 0.143c2.516-4.987 5.738-9.233 9.594-12.804l0.029-0.027-15.478-217.103z' 225 | ); 226 | } 227 | 228 | 100% { 229 | d: path( 230 | 'M450.256 203.13s-10.275-38.179 31.35-47.6c23.265-5.266 44.74 10.059 56.979 32.511 6.761 13.269 10.723 28.939 10.723 45.534 0 1.514-0.033 3.020-0.098 4.517l0.007-0.213c-0.615 7.207-12.644 11.276 12.423 22.372s70.469 3.27 103.171 14.222 34.042 14.183 58.045 28.103-15.118 2.101 51.498 18.779c24.644 6.172 35.513 8.289 40.382 42.163s4.176 75.131 17.637 82.472c4.826 2.57 10.568 5.056 16.518 7.097l0.861 0.257c7.499 2.826 9.807 3.677 12.607 3.94 0.245 0.029 0.53 0.045 0.818 0.045 1.414 0 2.735-0.398 3.858-1.088l-0.032 0.018s2.889-0.925 2.889 0.633c0 4.62-22.977 3.646-39.022 6.083s-19.129 7.551-25.151 3.677-1.804-8.463-2.461-19.496-1.006-12.024-4.489-30.176c-11.388-45.197-24.426-53.475-24.426-53.475s-118.717 0.084-116.981 0-36.679 0.854-54.468-8.197-23.273-28.448-25.85-5.216c-2.411 21.816-5.059 160.074-5.059 160.074 2.276 1.632 3.8 4.19 4.017 7.112l0.002 0.032c0.359 2.040 0.564 4.39 0.564 6.787 0 2.904-0.301 5.737-0.874 8.47l0.047-0.267c-0.888 4.83-3.225 4.875-4.3 4.11s0-7.173 0-7.173 0.281-5.873-0.775-4.99-0.63 5.778-2.443 11.055c-1.143 3.847-2.782 7.202-4.88 10.214l0.076-0.115c-2.015 2.986-3.383 4.092-3.254 1.839s2.663-5.403 3.769-10.834 2.7-12.032 0.654-10.895-4.108 9.862-7.412 15.759-4.912 8.032-5.802 7.861 2.24-8.549 2.24-8.549 0.911-2.808 2.248-6.566 3.774-8.074 3.11-8.502-2.936 1.726-5.778 6.784-5.143 13.658-5.552 13.453c-0.012-0.354-0.019-0.771-0.019-1.189 0-5.062 1.004-9.889 2.823-14.293l-0.091 0.249c2.626-5.97 6.992-11.015 6.724-11.557s-4.762 4.486-7.803 9.379-3.782 11.218-4.355 10.199c-0.194-1.151-0.305-2.477-0.305-3.829 0-3.799 0.875-7.393 2.435-10.592l-0.063 0.143c2.516-4.987 5.738-9.233 9.594-12.804l0.029-0.027-15.478-217.103z' 231 | ); 232 | } 233 | } 234 | 235 | @keyframes bubble { 236 | 0% { 237 | transform: scale(0); 238 | } 239 | 240 | 50% { 241 | transform: scale(1); 242 | } 243 | 244 | 100% { 245 | transform: scale(0); 246 | } 247 | } 248 | 249 | @keyframes width-change { 250 | 0% { 251 | width: 0; 252 | } 253 | 254 | 50% { 255 | width: 3ch; 256 | } 257 | 258 | 100% { 259 | width: 0; 260 | } 261 | } 262 | -------------------------------------------------------------------------------- /src/Flexo/assets/sharing_link.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /src/Flexo/assets/generating_link.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /src/Flexo/assets/building_layout.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | --------------------------------------------------------------------------------