├── .gitignore ├── .idea ├── misc.xml ├── modules.xml └── vcs.xml ├── README.md ├── gitflowanimated.iml ├── package.json ├── public ├── favicon.ico ├── index.html └── manifest.json ├── src ├── app.js ├── connections.js ├── gitflow.js ├── global-styles.js ├── goey-filter.js ├── index.js └── registerServiceWorker.js └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | 12 | # misc 13 | .DS_Store 14 | .env.local 15 | .env.development.local 16 | .env.test.local 17 | .env.production.local 18 | 19 | npm-debug.log* 20 | yarn-debug.log* 21 | yarn-error.log* 22 | .idea/workspace.xml 23 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | An Experimental try in React to visualize and animate Gitflow. 2 | 3 | Try it here: [https://veerasundar.com/blog/gitflow-animated](https://veerasundar.com/blog/gitflow-animated) 4 | 5 | ![Gitflow animated](https://i.imgur.com/c2rZy5E.gif) 6 | -------------------------------------------------------------------------------- /gitflowanimated.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gitflowanimated", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "react": "^16.2.0", 7 | "react-dom": "^16.2.0", 8 | "shortid": "^2.2.8", 9 | "styled-components": "^3.1.6" 10 | }, 11 | "devDependencies": { 12 | "react-scripts": "1.1.1" 13 | }, 14 | "scripts": { 15 | "start": "react-scripts start", 16 | "build": "react-scripts build", 17 | "test": "react-scripts test --env=jsdom", 18 | "eject": "react-scripts eject" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vraa/gitflowanimated/15705a4e2cbeaeed7703b9fb4d2affc3c932311a/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | 12 | 13 | 22 | Git flows - Animated 23 | 24 | 25 | 28 |
29 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /src/app.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import styled from "styled-components"; 3 | import GitFlow from "./gitflow"; 4 | import shortid from "shortid"; 5 | 6 | const DEVELOP = 'develop'; 7 | const MASTER = 'master'; 8 | 9 | const masterID = shortid.generate(); 10 | const developID = shortid.generate(); 11 | 12 | const seedData = () => { 13 | 14 | const commits = [ 15 | { 16 | id: shortid.generate(), 17 | branch: masterID, 18 | gridIndex: 1, 19 | parents: null, 20 | }, 21 | { 22 | id: shortid.generate(), 23 | branch: developID, 24 | gridIndex: 1, 25 | parents: null 26 | } 27 | ]; 28 | return { 29 | branches: [ 30 | { 31 | name: MASTER, 32 | id: masterID, 33 | canCommit: false, 34 | color: '#E040FB', 35 | }, 36 | { 37 | name: DEVELOP, 38 | id: developID, 39 | canCommit: true, 40 | color: '#FF8A65', 41 | } 42 | ], 43 | commits 44 | } 45 | }; 46 | 47 | const AppElm = styled.main` 48 | text-align: center; 49 | padding: 10px; 50 | `; 51 | 52 | class App extends Component { 53 | 54 | state = { 55 | project: seedData() 56 | }; 57 | 58 | handleCommit = (branchID, mergeGridIndex = 0) => { 59 | let {commits} = this.state.project; 60 | const branchCommits = commits.filter(c => c.branch === branchID); 61 | const lastCommit = branchCommits[branchCommits.length - 1]; 62 | commits.push({ 63 | id: shortid.generate(), 64 | branch: branchID, 65 | gridIndex: lastCommit.gridIndex + mergeGridIndex + 1, 66 | parents: [lastCommit.id] 67 | }); 68 | this.setState({ 69 | commits 70 | }); 71 | }; 72 | 73 | handleNewFeature = () => { 74 | let {branches, commits} = this.state.project; 75 | let featureBranches = branches.filter(b => b.featureBranch); 76 | let featureBranchName = 'feature ' + ((featureBranches || []).length + 1); 77 | let developCommits = commits.filter(c => c.branch === developID); 78 | const lastDevelopCommit = developCommits[developCommits.length - 1]; 79 | let featureOffset = lastDevelopCommit.gridIndex + 1; 80 | let newBranch = { 81 | id: shortid.generate(), 82 | name: featureBranchName, 83 | featureBranch: true, 84 | canCommit: true, 85 | color: '#64B5F6' 86 | }; 87 | let newCommit = { 88 | id: shortid.generate(), 89 | branch: newBranch.id, 90 | gridIndex: featureOffset, 91 | parents: [lastDevelopCommit.id] 92 | }; 93 | commits.push(newCommit); 94 | branches.push(newBranch); 95 | this.setState({ 96 | project: { 97 | branches, 98 | commits 99 | } 100 | }); 101 | }; 102 | 103 | handleNewHotFix = () => { 104 | let {branches, commits} = this.state.project; 105 | let hotFixBranches = branches.filter(b => b.hotFixBranch); 106 | let hotFixBranchName = 'hot ' + ((hotFixBranches || []).length + 1); 107 | let masterCommits = commits.filter(c => c.branch === masterID); 108 | const lastMasterCommit = masterCommits[masterCommits.length - 1]; 109 | let hotFixOffset = lastMasterCommit.gridIndex + 1; 110 | 111 | let newBranch = { 112 | id: shortid.generate(), 113 | name: hotFixBranchName, 114 | hotFixBranch: true, 115 | canCommit: true, 116 | color: '#ff1744' 117 | }; 118 | let newCommit = { 119 | id: shortid.generate(), 120 | branch: newBranch.id, 121 | gridIndex: hotFixOffset, 122 | parents: [lastMasterCommit.id] 123 | }; 124 | commits.push(newCommit); 125 | branches.push(newBranch); 126 | this.setState({ 127 | project: { 128 | branches, 129 | commits 130 | } 131 | }); 132 | }; 133 | 134 | handleNewRelease = () => { 135 | let {branches, commits} = this.state.project; 136 | let releaseBranches = branches.filter(b => b.releaseBranch); 137 | let releaseBranchName = 'release ' + ((releaseBranches || []).length + 1); 138 | let developCommits = commits.filter(c => c.branch === developID); 139 | const lastDevelopCommit = developCommits[developCommits.length - 1]; 140 | let releaseOffset = lastDevelopCommit.gridIndex + 1; 141 | let newBranch = { 142 | id: shortid.generate(), 143 | name: releaseBranchName, 144 | releaseBranch: true, 145 | canCommit: true, 146 | color: '#B2FF59' 147 | }; 148 | let newCommit = { 149 | id: shortid.generate(), 150 | branch: newBranch.id, 151 | gridIndex: releaseOffset, 152 | parents: [lastDevelopCommit.id] 153 | }; 154 | commits.push(newCommit); 155 | branches.push(newBranch); 156 | this.setState({ 157 | project: { 158 | branches, 159 | commits 160 | } 161 | }); 162 | }; 163 | 164 | handleRelease = (sourceBranchID) => { 165 | let {branches, commits} = this.state.project; 166 | const sourceBranch = branches.find(b => b.id === sourceBranchID); 167 | const sourceCommits = commits.filter(c => c.branch === sourceBranchID); 168 | 169 | const masterCommits = commits.filter(c => c.branch === masterID); 170 | const developCommits = commits.filter(c => c.branch === developID); 171 | const lastSourceCommit = sourceCommits[sourceCommits.length - 1]; 172 | const lastMasterCommit = masterCommits[masterCommits.length - 1]; 173 | const lastDevelopCommit = developCommits[developCommits.length - 1]; 174 | 175 | const masterMergeCommit = { 176 | id: shortid.generate(), 177 | branch: masterID, 178 | gridIndex: Math.max(lastSourceCommit.gridIndex, lastMasterCommit.gridIndex) + 1, 179 | parents: [lastMasterCommit.id, lastSourceCommit.id] 180 | }; 181 | 182 | const developMergeCommit = { 183 | id: shortid.generate(), 184 | branch: developID, 185 | gridIndex: Math.max(lastSourceCommit.gridIndex, lastDevelopCommit.gridIndex) + 1, 186 | parents: [lastDevelopCommit.id, lastSourceCommit.id] 187 | }; 188 | 189 | commits.push(masterMergeCommit, developMergeCommit); 190 | sourceBranch.merged = true; 191 | 192 | this.setState({ 193 | project: { 194 | branches, 195 | commits 196 | } 197 | }); 198 | 199 | }; 200 | 201 | handleMerge = (sourceBranchID, targetBranchID = developID) => { 202 | let {branches, commits} = this.state.project; 203 | 204 | const sourceBranch = branches.find(b => b.id === sourceBranchID); 205 | const sourceCommits = commits.filter(c => c.branch === sourceBranchID); 206 | const targetCommits = commits.filter(c => c.branch === targetBranchID); 207 | 208 | const lastSourceCommit = sourceCommits[sourceCommits.length - 1]; 209 | const lastTargetCommit = targetCommits[targetCommits.length - 1]; 210 | 211 | const mergeCommit = { 212 | id: shortid.generate(), 213 | branch: targetBranchID, 214 | gridIndex: Math.max(lastSourceCommit.gridIndex, lastTargetCommit.gridIndex) + 1, 215 | parents: [lastSourceCommit.id, lastTargetCommit.id] 216 | }; 217 | commits.push(mergeCommit); 218 | 219 | sourceBranch.merged = true; 220 | 221 | this.setState({ 222 | project: { 223 | branches, 224 | commits 225 | } 226 | }); 227 | }; 228 | 229 | handleDeleteBranch = (branchID) => { 230 | let {branches, commits} = this.state.project; 231 | 232 | let commitsToDelete = commits.filter(c => c.branch === branchID); 233 | let lastCommit = commitsToDelete[commitsToDelete.length - 1]; 234 | commits = commits.map(commit => { 235 | if (commit.parents) { 236 | commit.parents = commit.parents.filter(pID => pID !== lastCommit.id); 237 | } 238 | return commit; 239 | 240 | }); 241 | branches = branches.filter(b => b.id !== branchID); 242 | commits = commits.filter(c => c.branch !== branchID); 243 | this.setState({ 244 | project: { 245 | branches, 246 | commits 247 | } 248 | }); 249 | }; 250 | 251 | render() { 252 | return ( 253 | 254 | 264 | 265 | ); 266 | } 267 | } 268 | 269 | export default App; 270 | -------------------------------------------------------------------------------- /src/connections.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from "react"; 2 | import styled, {keyframes} from "styled-components"; 3 | 4 | const ConnectionsElm = styled.svg` 5 | position: absolute; 6 | top: 0; 7 | left: 0; 8 | width: 100%; 9 | height: 100%; 10 | z-index: 10; 11 | `; 12 | 13 | const draw = keyframes` 14 | to { 15 | stroke-dashoffset: 0; 16 | } 17 | `; 18 | 19 | const AnimatedLine = styled.line` 20 | stroke-dasharray: 500; 21 | stroke-dashoffset: 500; 22 | animation: ${draw} 2s linear forwards; 23 | `; 24 | 25 | const AnimatedPath = styled.path` 26 | stroke-dasharray: 500; 27 | stroke-dashoffset: 500; 28 | animation: ${draw} 2s linear forwards; 29 | `; 30 | 31 | 32 | const LINE_COLOR = '#fff'; 33 | 34 | class Connections extends Component { 35 | 36 | render() { 37 | const {paths} = this.props; 38 | return ( 39 | 40 | { 41 | paths.map((path, idx) => { 42 | const {src, tgt} = path; 43 | let elm = null; 44 | if (src.left === tgt.left) { 45 | elm = 52 | } else { 53 | let p1, p2, c1, c2; 54 | if (src.left < tgt.left) { 55 | p1 = { 56 | x: src.left + 12.5, 57 | y: src.top + 12.5 58 | }; 59 | p2 = { 60 | x: tgt.left - 12.5, 61 | y: tgt.top + 12.5 62 | }; 63 | c1 = { 64 | x: p1.x + 45, 65 | y: p1.y 66 | }; 67 | c2 = { 68 | x: p2.x - 45, 69 | y: p2.y 70 | }; 71 | } else { 72 | p1 = { 73 | x: src.left - 12.5, 74 | y: src.top + 12.5 75 | }; 76 | p2 = { 77 | x: tgt.left + 12.5, 78 | y: tgt.top + 12.5 79 | }; 80 | c1 = { 81 | x: p1.x - 45, 82 | y: p1.y 83 | }; 84 | c2 = { 85 | x: p2.x + 45, 86 | y: p2.y 87 | }; 88 | } 89 | 90 | let pathStr = `M${p1.x},${p1.y} C${c1.x},${c1.y} ${c2.x},${c2.y} ${p2.x},${p2.y}`; 91 | elm = 98 | } 99 | 100 | return elm; 101 | }) 102 | } 103 | 104 | ) 105 | } 106 | } 107 | 108 | export default Connections; -------------------------------------------------------------------------------- /src/gitflow.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from "react"; 2 | import ReactDOM from "react-dom"; 3 | import styled from "styled-components"; 4 | import {Button, ButtonIcon, fallDownAnimation, fadeIn} from "./global-styles"; 5 | import GoeyFilter from "./goey-filter"; 6 | import Connections from "./connections"; 7 | 8 | const GitFlowElm = styled.div` 9 | margin: 0 auto; 10 | `; 11 | 12 | const GlobalActions = styled.div` 13 | display: grid; 14 | grid-template-columns: 1fr 1fr 1fr; 15 | grid-column-gap: 10px; 16 | `; 17 | 18 | const ProjectElm = styled.div` 19 | position: relative; 20 | display: grid; 21 | grid-template-columns: 1fr; 22 | grid-template-rows: 90px 1fr; 23 | margin-top: 20px; 24 | background: linear-gradient(135deg, rgba(34,52,122,1) 0%,rgba(23,35,82,1) 100%); 25 | border-radius: 5px; 26 | box-shadow: 0 4px 10px #9d9d9d; 27 | overflow: auto; 28 | `; 29 | 30 | const GridColumn = styled.div` 31 | position: relative; 32 | display: grid; 33 | grid-template-columns: ${p => `repeat(${p.count || 2}, 90px)`}; 34 | `; 35 | 36 | 37 | const BranchHeader = styled.div` 38 | max-width: 90px; 39 | padding: 5px; 40 | text-align: center; 41 | background-color: #131d45; 42 | border-right: 1px solid #1b295f; 43 | color: #f0f0f0; 44 | z-index: 1; 45 | margin-bottom: 10px; 46 | animation: ${fadeIn} .5s ease-in; 47 | `; 48 | 49 | const BranchActions = styled.div` 50 | display: grid; 51 | grid-template-columns: ${p => `repeat(${p.count || 1}, 1fr)`}; 52 | margin-top: 10px; 53 | justify-items: center; 54 | height: 24px; 55 | `; 56 | 57 | const BranchName = styled.h4` 58 | position: relative; 59 | font-size: .7rem; 60 | text-transform: uppercase; 61 | letter-spacing:1.5pt; 62 | margin-top: 10px; 63 | opacity: .6; 64 | `; 65 | 66 | const Commits = styled.ol` 67 | position: relative; 68 | min-height: 800px; 69 | height: ${p => p.height || '500px'}; 70 | filter: url('#goo'); 71 | z-index: 40; 72 | border-right: 1px solid #1b295f; 73 | transition: opacity .5s; 74 | `; 75 | 76 | const Commit = styled.li` 77 | position: absolute; 78 | display: grid; 79 | align-items: center; 80 | justify-items: center; 81 | top: ${p => (p.top * 45) + 'px'}; 82 | left: 50%; 83 | width: 25px; 84 | height: 25px; 85 | border-radius: 50%; 86 | transform: translate(-50%,-45px); 87 | background-color: ${p => p.color || '#9d9d9d'}; 88 | box-shadow: 0 0 20px #f0f0f0; 89 | border: 1px solid #fff; 90 | animation: ${fallDownAnimation} cubic-bezier(0.770, 0.000, 0.175, 1.000) 1s; 91 | animation-fill-mode: forwards; 92 | z-index: 40; 93 | transition: all .2s; 94 | &.merged { 95 | background-color: #fff; 96 | box-shadow: none; 97 | opacity: .5; 98 | } 99 | `; 100 | 101 | const Tag = styled.p` 102 | color: #fff; 103 | font-size: .7rem; 104 | letter-spacing: 1pt; 105 | `; 106 | const ConnectionsContainer = styled.div` 107 | position: absolute; 108 | top: 0; 109 | left: 0; 110 | width: 100%; 111 | height: 100%; 112 | z-index: 30; 113 | `; 114 | 115 | 116 | class GitFlow extends Component { 117 | 118 | componentWillMount() { 119 | this.commitPositions = {}; 120 | } 121 | 122 | componentDidMount() { 123 | this.connectCommits(); 124 | } 125 | 126 | componentDidUpdate() { 127 | this.connectCommits(); 128 | } 129 | 130 | cacheConnectionsContainer = (elm) => { 131 | this.connectionsContainer = elm; 132 | }; 133 | 134 | storeCommitPosition = (id, offset = 0, commitElm) => { 135 | if (commitElm) { 136 | this.commitPositions[id] = { 137 | top: commitElm.offsetTop, 138 | left: (offset * 90) + commitElm.offsetLeft 139 | } 140 | } 141 | }; 142 | 143 | connectCommits = () => { 144 | const {commits} = this.props.project; 145 | let paths = commits.map(commit => { 146 | const {parents} = commit; 147 | const tgtPosition = this.commitPositions[commit.id]; 148 | return (parents || []).map(p => { 149 | return { 150 | srcCommitID: p, 151 | tgtCommitID: commit.id, 152 | src: this.commitPositions[p], 153 | tgt: tgtPosition 154 | } 155 | }); 156 | }); 157 | paths = [].concat.apply([], paths); 158 | ReactDOM.render(, this.connectionsContainer); 159 | }; 160 | 161 | 162 | deleteBranch = (branchID) => { 163 | const {commits} = this.props.project; 164 | const commitsToDelete = commits.filter(c => c.branch === branchID).map(c => c.id); 165 | commitsToDelete.forEach(c => { 166 | delete this.commitPositions[c.id]; 167 | }); 168 | this.props.onDeleteBranch(branchID); 169 | }; 170 | 171 | renderCommitButton = (branch) => { 172 | return ( 173 | C 176 | ) 177 | }; 178 | 179 | renderDeleteButton = (branch) => { 180 | return ( 181 | 182 | 183 | 184 | ) 185 | } 186 | 187 | renderDevelopBranchHeader = (branch) => { 188 | return ( 189 | 190 | {branch.name} 191 | 194 | R 195 | {this.renderCommitButton(branch)} 196 | F 197 | 198 | 199 | ) 200 | }; 201 | 202 | renderFeatureBranchHeader = (branch) => { 203 | let actionsElm = null; 204 | if (branch.merged) { 205 | actionsElm = this.renderDeleteButton(branch); 206 | } else { 207 | actionsElm = ( 208 | 211 | M 214 | {this.renderCommitButton(branch)} 215 | 216 | ); 217 | } 218 | return ( 219 | 222 | {branch.name} 223 | {actionsElm} 224 | 225 | ) 226 | }; 227 | 228 | renderReleaseBranchHeader = (branch) => { 229 | let actionsElm = null; 230 | if (branch.merged) { 231 | actionsElm = this.renderDeleteButton(branch); 232 | } else { 233 | actionsElm = ( 236 | {this.renderCommitButton(branch)} 237 | M 240 | 241 | ); 242 | } 243 | return ( 244 | 247 | {branch.name} 248 | {actionsElm} 249 | 250 | ) 251 | }; 252 | 253 | renderMasterBranchHeader = (branch) => { 254 | return ( 255 | 256 | {branch.name} 257 | 258 | H 261 | 262 | 263 | ) 264 | }; 265 | 266 | renderBranchHeaders = (param) => { 267 | const { 268 | masterBranch, 269 | developBranch, 270 | releaseBranches, 271 | featureBranches, 272 | hotFixBranches, 273 | noOfBranches 274 | } = param; 275 | return ( 276 | 279 | { 280 | this.renderMasterBranchHeader(masterBranch) 281 | } 282 | { 283 | hotFixBranches.map(b => this.renderReleaseBranchHeader(b)) 284 | } 285 | { 286 | releaseBranches.map(b => this.renderReleaseBranchHeader(b)) 287 | } 288 | { 289 | this.renderDevelopBranchHeader(developBranch) 290 | } 291 | { 292 | featureBranches.map(b => this.renderFeatureBranchHeader(b)) 293 | } 294 | 295 | ) 296 | }; 297 | 298 | renderBranchCommits = (param) => { 299 | const { 300 | masterBranch, 301 | developBranch, 302 | releaseBranches, 303 | featureBranches, 304 | hotFixBranches, 305 | noOfBranches 306 | } = param; 307 | let branches = [masterBranch, ...hotFixBranches, ...releaseBranches, developBranch, ...featureBranches]; 308 | return ( 309 | 312 | 313 | { 314 | branches.map((branch, index) => { 315 | return this.renderBranchCommit(branch, index) 316 | }) 317 | } 318 | 319 | ) 320 | }; 321 | 322 | renderBranchCommit = (branch, branchIndex) => { 323 | const {commits} = this.props.project; 324 | const branchCommits = commits.filter(c => c.branch === branch.id); 325 | let isMasterBranch = branch.name === 'master'; 326 | return ( 327 | 333 | { 334 | branchCommits.map((commit, idx) => { 335 | return 342 | {isMasterBranch ? {'v' + idx} : null} 343 | 344 | }) 345 | } 346 | 347 | ) 348 | }; 349 | 350 | render() { 351 | 352 | const {project} = this.props; 353 | const {branches} = project; 354 | const masterBranch = branches.find(b => b.name === 'master'); 355 | const hotFixBranches = branches.filter(b => b.hotFixBranch); 356 | const developBranch = branches.find(b => b.name === 'develop'); 357 | const releaseBranches = branches.filter(b => b.releaseBranch); 358 | const featureBranches = branches.filter(b => b.featureBranch); 359 | const noOfBranches = branches.length; 360 | const param = { 361 | masterBranch, 362 | hotFixBranches, 363 | developBranch, 364 | featureBranches, 365 | releaseBranches, 366 | noOfBranches 367 | }; 368 | return ( 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | {this.renderBranchHeaders(param)} 377 | {this.renderBranchCommits(param)} 378 | 379 | 380 | 381 | ) 382 | } 383 | } 384 | 385 | export default GitFlow; 386 | -------------------------------------------------------------------------------- /src/global-styles.js: -------------------------------------------------------------------------------- 1 | import styled, {injectGlobal, keyframes} from 'styled-components'; 2 | 3 | injectGlobal` 4 | html, body, div, span, applet, object, iframe, 5 | h1, h2, h3, h4, h5, h6, p, blockquote, pre, 6 | a, abbr, acronym, address, big, cite, code, 7 | del, dfn, em, img, ins, kbd, q, s, samp, 8 | small, strike, strong, sub, sup, tt, var, 9 | b, u, i, center, 10 | dl, dt, dd, ol, ul, li, 11 | fieldset, form, label, legend, 12 | table, caption, tbody, tfoot, thead, tr, th, td, 13 | article, aside, canvas, details, embed, 14 | figure, figcaption, footer, header, hgroup, 15 | menu, nav, output, ruby, section, summary, 16 | time, mark, audio, video { 17 | margin: 0; 18 | padding: 0; 19 | border: 0; 20 | font-size: 100%; 21 | font: inherit; 22 | vertical-align: baseline; 23 | } 24 | /* HTML5 display-role reset for older browsers */ 25 | article, aside, details, figcaption, figure, 26 | footer, header, hgroup, menu, nav, section { 27 | display: block; 28 | } 29 | body { 30 | line-height: 1; 31 | } 32 | ol, ul { 33 | list-style: none; 34 | } 35 | blockquote, q { 36 | quotes: none; 37 | } 38 | blockquote:before, blockquote:after, 39 | q:before, q:after { 40 | content: ''; 41 | content: none; 42 | } 43 | table { 44 | border-collapse: collapse; 45 | border-spacing: 0; 46 | } 47 | 48 | html { 49 | box-sizing: border-box; 50 | } 51 | *, *:before, *:after { 52 | box-sizing: inherit; 53 | } 54 | 55 | 56 | #root { 57 | background-color: #fff; 58 | color: #3d3d3d; 59 | font-family: 'Open Sans', sans-serif; 60 | font-size: 1em; 61 | } 62 | `; 63 | 64 | export const FlowName = styled.h2` 65 | font-weight: bold; 66 | font-size: 1.2rem; 67 | `; 68 | 69 | export const FlowActions = styled.div` 70 | display: grid; 71 | grid-template-columns: 1fr 1fr; 72 | grid-column-gap: 10px; 73 | margin-top: 10px; 74 | `; 75 | 76 | export const Button = styled.button` 77 | border: none; 78 | background: transparent; 79 | border: 1px solid #2196F3; 80 | color: #2196F3; 81 | border-radius: 20px; 82 | padding: 10px; 83 | cursor: pointer; 84 | transition: all .2s; 85 | &:hover { 86 | background-color: #E3F2FD; 87 | } 88 | `; 89 | 90 | export const ButtonIcon = styled.button` 91 | display: grid; 92 | align-items: center; 93 | justify-items: center; 94 | width: 24px; 95 | height: 24px; 96 | background: transparent; 97 | background: transparent; 98 | border: 1px solid #2196F3; 99 | color: #2196F3; 100 | border-radius: 50%; 101 | cursor: pointer; 102 | transition: all .2s; 103 | &:hover { 104 | background-color: #2196F3; 105 | color: #fff; 106 | transform: scale(1.1); 107 | } 108 | `; 109 | 110 | export const fallDownAnimation = keyframes` 111 | to { 112 | transform: translate(-50%, 0); 113 | } 114 | `; 115 | 116 | export const fadeIn = keyframes` 117 | from { 118 | opacity: 0; 119 | } 120 | to { 121 | opacity: 1; 122 | } 123 | `; -------------------------------------------------------------------------------- /src/goey-filter.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from "react"; 2 | 3 | class GoeyFilter extends Component { 4 | render() { 5 | return ( 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | ) 16 | } 17 | } 18 | 19 | export default GoeyFilter; 20 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import "./global-styles"; 4 | import App from './app'; 5 | import registerServiceWorker from './registerServiceWorker'; 6 | 7 | ReactDOM.render(, document.getElementById('root')); 8 | -------------------------------------------------------------------------------- /src/registerServiceWorker.js: -------------------------------------------------------------------------------- 1 | // In production, we register a service worker to serve assets from local cache. 2 | 3 | // This lets the app load faster on subsequent visits in production, and gives 4 | // it offline capabilities. However, it also means that developers (and users) 5 | // will only see deployed updates on the "N+1" visit to a page, since previously 6 | // cached resources are updated in the background. 7 | 8 | // To learn more about the benefits of this model, read https://goo.gl/KwvDNy. 9 | // This link also includes instructions on opting out of this behavior. 10 | 11 | const isLocalhost = Boolean( 12 | window.location.hostname === 'localhost' || 13 | // [::1] is the IPv6 localhost address. 14 | window.location.hostname === '[::1]' || 15 | // 127.0.0.1/8 is considered localhost for IPv4. 16 | window.location.hostname.match( 17 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ 18 | ) 19 | ); 20 | 21 | export default function register() { 22 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { 23 | // The URL constructor is available in all browsers that support SW. 24 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location); 25 | if (publicUrl.origin !== window.location.origin) { 26 | // Our service worker won't work if PUBLIC_URL is on a different origin 27 | // from what our page is served on. This might happen if a CDN is used to 28 | // serve assets; see https://github.com/facebookincubator/create-react-app/issues/2374 29 | return; 30 | } 31 | 32 | window.addEventListener('load', () => { 33 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; 34 | 35 | if (isLocalhost) { 36 | // This is running on localhost. Lets check if a service worker still exists or not. 37 | checkValidServiceWorker(swUrl); 38 | 39 | // Add some additional logging to localhost, pointing developers to the 40 | // service worker/PWA documentation. 41 | navigator.serviceWorker.ready.then(() => { 42 | console.log( 43 | 'This web app is being served cache-first by a service ' + 44 | 'worker. To learn more, visit https://goo.gl/SC7cgQ' 45 | ); 46 | }); 47 | } else { 48 | // Is not local host. Just register service worker 49 | registerValidSW(swUrl); 50 | } 51 | }); 52 | } 53 | } 54 | 55 | function registerValidSW(swUrl) { 56 | navigator.serviceWorker 57 | .register(swUrl) 58 | .then(registration => { 59 | registration.onupdatefound = () => { 60 | const installingWorker = registration.installing; 61 | installingWorker.onstatechange = () => { 62 | if (installingWorker.state === 'installed') { 63 | if (navigator.serviceWorker.controller) { 64 | // At this point, the old content will have been purged and 65 | // the fresh content will have been added to the cache. 66 | // It's the perfect time to display a "New content is 67 | // available; please refresh." message in your web app. 68 | console.log('New content is available; please refresh.'); 69 | } else { 70 | // At this point, everything has been precached. 71 | // It's the perfect time to display a 72 | // "Content is cached for offline use." message. 73 | console.log('Content is cached for offline use.'); 74 | } 75 | } 76 | }; 77 | }; 78 | }) 79 | .catch(error => { 80 | console.error('Error during service worker registration:', error); 81 | }); 82 | } 83 | 84 | function checkValidServiceWorker(swUrl) { 85 | // Check if the service worker can be found. If it can't reload the page. 86 | fetch(swUrl) 87 | .then(response => { 88 | // Ensure service worker exists, and that we really are getting a JS file. 89 | if ( 90 | response.status === 404 || 91 | response.headers.get('content-type').indexOf('javascript') === -1 92 | ) { 93 | // No service worker found. Probably a different app. Reload the page. 94 | navigator.serviceWorker.ready.then(registration => { 95 | registration.unregister().then(() => { 96 | window.location.reload(); 97 | }); 98 | }); 99 | } else { 100 | // Service worker found. Proceed as normal. 101 | registerValidSW(swUrl); 102 | } 103 | }) 104 | .catch(() => { 105 | console.log( 106 | 'No internet connection found. App is running in offline mode.' 107 | ); 108 | }); 109 | } 110 | 111 | export function unregister() { 112 | if ('serviceWorker' in navigator) { 113 | navigator.serviceWorker.ready.then(registration => { 114 | registration.unregister(); 115 | }); 116 | } 117 | } 118 | --------------------------------------------------------------------------------