finished gif creator

This commit is contained in:
JuliusHerrmann 2021-12-14 02:26:20 +01:00
parent 0559d6fac8
commit 1ba149b3e8
10 changed files with 178 additions and 29 deletions

19
package-lock.json generated
View File

@ -18,6 +18,7 @@
"react-dom": "^17.0.2", "react-dom": "^17.0.2",
"react-nvd3": "^0.5.7", "react-nvd3": "^0.5.7",
"react-scripts": "4.0.3", "react-scripts": "4.0.3",
"reactjs-popup": "^2.0.5",
"web-vitals": "^1.0.1" "web-vitals": "^1.0.1"
} }
}, },
@ -15714,6 +15715,18 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/reactjs-popup": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/reactjs-popup/-/reactjs-popup-2.0.5.tgz",
"integrity": "sha512-b5hv9a6aGsHEHXFAgPO5s1Jw1eSkopueyUVxQewGdLgqk2eW0IVXZrPRpHR629YcgIpC2oxtX8OOZ8a7bQJbxA==",
"engines": {
"node": ">=10"
},
"peerDependencies": {
"react": ">=16",
"react-dom": ">=16"
}
},
"node_modules/read-pkg": { "node_modules/read-pkg": {
"version": "5.2.0", "version": "5.2.0",
"resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz",
@ -33056,6 +33069,12 @@
} }
} }
}, },
"reactjs-popup": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/reactjs-popup/-/reactjs-popup-2.0.5.tgz",
"integrity": "sha512-b5hv9a6aGsHEHXFAgPO5s1Jw1eSkopueyUVxQewGdLgqk2eW0IVXZrPRpHR629YcgIpC2oxtX8OOZ8a7bQJbxA==",
"requires": {}
},
"read-pkg": { "read-pkg": {
"version": "5.2.0", "version": "5.2.0",
"resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz",

View File

@ -13,6 +13,7 @@
"react-dom": "^17.0.2", "react-dom": "^17.0.2",
"react-nvd3": "^0.5.7", "react-nvd3": "^0.5.7",
"react-scripts": "4.0.3", "react-scripts": "4.0.3",
"reactjs-popup": "^2.0.5",
"web-vitals": "^1.0.1" "web-vitals": "^1.0.1"
}, },
"scripts": { "scripts": {

View File

@ -11,8 +11,13 @@ class GIFGenerator extends React.Component {
this.animationId = 0; this.animationId = 0;
} }
generateGIF = () => { generateGIF = () => {
//clean input //clean input
if(!Math.abs(Number(this.state.duration)) > 0) {
console.log("Only numbers are allowed in duration!");
return;
}
this.setState({duration: Math.abs(this.state.duration)}); this.setState({duration: Math.abs(this.state.duration)});
var gif = new GIF( { var gif = new GIF( {
workers: 2, workers: 2,
@ -20,26 +25,38 @@ class GIFGenerator extends React.Component {
repeat: this.state.loop? 0 : -1, repeat: this.state.loop? 0 : -1,
background: this.state.background background: this.state.background
}); });
//set download trigger
gif.on('finished', function(blob) { gif.on('finished', function(blob) {
window.open(URL.createObjectURL(blob)); const link = document.createElement('a');
// create a blobURI pointing to our Blob
link.href = URL.createObjectURL(blob);
link.download = "simulationGIF";
// some browser needs the anchor to be in the doc
document.body.append(link);
link.click();
link.remove();
// in case the Blob uses a lot of memory
setTimeout(() => URL.revokeObjectURL(link.href), 7000);
}); });
let stepBehtmlFore = this.state.step;
let stepBefore = this.state.step;
//clear playing animation //clear playing animation
clearInterval(this.animationId); clearInterval(this.animationId);
this.props.setState({step: 0}, () => { this.props.setState({step: 0}, () => {
this.stepTime = 1; this.stepTime = 1;
//this.animationId = setInterval(this.visualizeOneStep, this.stepTime); //this.animationId = setInterval(this.visualizeOneStep, this.stepTime);
this.animationId = setInterval(this.grabImageAndNextStep.bind(null, gif, stepBehtmlFore), this.stepTime); this.animationId = setInterval(this.grabImageAndNextStep.bind(null, gif, stepBefore), this.stepTime);
this.props.setState({playing: true}); this.props.setState({playing: true});
}); });
} }
grabImageAndNextStep = (gif, stepBehtmlFore) => { grabImageAndNextStep = (gif, stepBefore) => {
//check if we are at the end //check if we are at the end
if (this.props.state.step > this.props.animationLength) { if (this.props.state.step > this.props.animationLength) {
this.props.setState({step: stepBehtmlFore}); this.props.setState({step: stepBefore});
this.props.visualizeOneStep(false); this.props.visualizeOneStep(false);
clearInterval(this.animationId); clearInterval(this.animationId);
gif.render(); gif.render();
@ -57,18 +74,28 @@ class GIFGenerator extends React.Component {
render() { render() {
return ( return (
<div id="generateGIFPopup"> <div id="generateGIFPopup">
<label htmlFor="gifDuration" id="gifLengthLabel">Duration In Seconds: </label> <div className="popupHeader">
<input htmlFor="gifDuration" type="number" id="gifLengthInput" GIF Generator
value={this.state.duration} onChange={(e) => this.setState({duration: e.target.value})}/> </div>
<label htmlFor="gifBackground" id="gifBackgroundLabel">Background Color: </label> <div className="popupSection">
<input htmlFor="gifBackground" id="gifBackgroundInput" type='color' <label htmlFor="gifDuration" id="gifLengthLabel">Duration In Seconds: </label>
value={this.state.background} <input htmlFor="gifDuration" type="number" id="gifLengthInput"
onChange={(e) => this.setState({background: e.target.value})}/> value={this.state.duration} onChange={(e) => this.setState({duration: e.target.value})}/>
<label htmlFor="gifLoop" id="gifLoopLabel">Loop: </label> </div>
<input htmlFor="gifLoop" id="gifLoopInput" type="checkbox" checked={this.state.loop}
onChange={(e) => <div className="popupSection">
this.setState({loop: e.target.checked})}/> <label htmlFor="gifBackground" id="gifBackgroundLabel">Background Color: </label>
<button onClick ={this.generateGIF} >Generate GIF</button> <input htmlFor="gifBackground" id="gifBackgroundInput" type='color'
value={this.state.background}
onChange={(e) => this.setState({background: e.target.value})}/>
</div>
<div className="popupSection">
<label htmlFor="gifLoop" id="gifLoopLabel">Loop: </label>
<input htmlFor="gifLoop" id="gifLoopInput" type="checkbox" checked={this.state.loop}
onChange={(e) =>
this.setState({loop: e.target.checked})}/>
</div>
<button className="popupButton" onClick={this.generateGIF} >Generate GIF 📸</button>
</div> </div>
); );
} }

View File

@ -1,5 +1,6 @@
import React from 'react'; import React from 'react';
import CytoscapeComponent from 'react-cytoscapejs'; import CytoscapeComponent from 'react-cytoscapejs';
import Popup from 'reactjs-popup';
import '../../css/Graph.css'; import '../../css/Graph.css';
import Slider from '../Slider'; import Slider from '../Slider';
import GIFGenerator from './GIFGenerator'; import GIFGenerator from './GIFGenerator';
@ -201,14 +202,23 @@ class Graph extends React.Component {
//we want this to be a "tabbed" approach //we want this to be a "tabbed" approach
return (<div id="graphDiv"> return (<div id="graphDiv">
<button id="runSimulationButton" onClick={this.visualizeSimulation}>{playPauseString}</button> <button id="runSimulationButton" onClick={this.visualizeSimulation}>{playPauseString}</button>
<div> <Popup trigger={<button id="gifGeneratorButton">Generate GIF 📸</button>} modal>
<h3 id="durationDescription">Duration (seconds): </h3> {close => (
<input id="animationDuration" type="number" onChange={this.changeAnimationDuration} value={this.state.animationDuration}/> <div className="modal">
<button className="close" onClick={() => {close()}} >&times;</button>
<GIFGenerator setState={this.setState.bind(this)} state={this.state} visualizeOneStep={this.visualizeOneStep} animationLength={this.props.animationLength}/>
</div>
)}
</Popup>
<div id="durationWrapper">
<h3 id="durationDescription">Duration (seconds): </h3>
<input id="animationDuration" type="number" onChange={this.changeAnimationDuration} value={this.state.animationDuration}/>
</div> </div>
<Slider description="Step" min="0" max={this.props.animationLength} currentValue={this.state.step} handleChange={this.visualizeSpecificStep}/> <Slider description="Step" min="0" max={this.props.animationLength} currentValue={this.state.step} handleChange={this.visualizeSpecificStep}/>
<CytoscapeComponent id="cy" userZoomingEnabled={false} userPanningEnabled={false} <CytoscapeComponent id="cy" userZoomingEnabled={false} userPanningEnabled={false}
cy={(cy) => { this.cy = cy }} elements={this.props.graphData}/> cy={(cy) => { this.cy = cy }} elements={this.props.graphData}/>
<GIFGenerator setState={this.setState.bind(this)} state={this.state} visualizeOneStep={this.visualizeOneStep} animationLength={this.props.animationLength}/>
</div>); </div>);
} }
} }

View File

@ -5,6 +5,7 @@
} }
.Dropdown { .Dropdown {
cursor: pointer;
color: #eeeeee; color: #eeeeee;
font-size: 15pt; font-size: 15pt;
display: block; display: block;

View File

@ -1,6 +1,69 @@
#generateGIFPopup { .popup-overlay {
position: absolute; background: rgba(0, 0, 0, 0.5);
top: 0; }
left: 0; .popup-content {
border: solid black; background-color: #2d4059;
/*background-color: #222831;*/
padding: 1em;
border-radius: 30px;
filter: opacity(1);
max-width: 85%;
}
.modal {
font-size: 13pt;
font-family: 'Roboto', sans-serif;
color: #eeeeee; text-align: left;
}
.modal > .close {
color: white;
cursor: pointer;
position: absolute;
display: block;
padding: 2px 5px;
line-height: 20px;
right: -5px;
top: -5px;
font-size: 24px;
background: #ff5722;
border-radius: 18px;
border: 1px solid #ff5722;
}
.popupHeader {
font-size: 20pt;
}
.popupSection {
display: block;
margin-top: 10px;
}
.popupButton {
cursor: pointer;
margin: auto;
font-size: 16pt;
display: block;
margin-top: 10px;
border: none;
outline: none;
color: #eeeeee;
background-color: #0a8e4e;
border-radius: 10px;
padding: 5px 10px;
margin-top: 10px;
}
#gifLengthInput {
display: inline-block;
width: 1.55em;
/*transform: translateY(-3px);*/
margin-left: 2%;
background-color: #222831;
color: #ff5722;
border-color: #ff5722;
border-style: none;
border-radius: 3px;
font-size: 12pt;
outline: solid;
}
.gifLengthInput:focus {
outline-width: 0;
outline: solid;
} }

View File

@ -1,4 +1,5 @@
#recalculate { #recalculate {
cursor: pointer;
margin-right: 20px; margin-right: 20px;
margin-bottom: 20px; margin-bottom: 20px;
border: none; border: none;
@ -11,6 +12,7 @@
} }
#switchView { #switchView {
cursor: pointer;
margin-right: 20px; margin-right: 20px;
margin-bottom: 20px; margin-bottom: 20px;
border: none; border: none;
@ -25,7 +27,9 @@
} }
#runSimulationButton { #runSimulationButton {
cursor: pointer;
margin-right: 20px; margin-right: 20px;
margin-bottom: 20px;
border: none; border: none;
outline: none; outline: none;
font-size: 25pt; font-size: 25pt;
@ -37,6 +41,23 @@
min-width: 295px; min-width: 295px;
} }
#gifGeneratorButton {
cursor: pointer;
border: none;
outline: none;
font-size: 25pt;
color: #eeeeee;
background-color: #0A8E4E;
border-radius: 10px;
padding: 5px 10px;
text-align: left;
min-width: 260px;
}
#durationWrapper {
margin-top: -10px;
}
#durationDescription { #durationDescription {
display: inline-block; display: inline-block;
margin-right: 20px; margin-right: 20px;

View File

@ -3,6 +3,7 @@
} }
.ColorPicker { .ColorPicker {
cursor: pointer;
display: inline-block; display: inline-block;
margin-left: 150px; margin-left: 150px;
margin-top: 5px; margin-top: 5px;

View File

@ -41,6 +41,7 @@ input[type="range"]::-webkit-slider-thumb {
} }
input[type="range"]::-moz-range-thumb { input[type="range"]::-moz-range-thumb {
cursor: pointer;
height: 14px; height: 14px;
width: 14px; width: 14px;
background: #ff5722; background: #ff5722;

View File

@ -9345,7 +9345,7 @@
"strip-ansi" "6.0.0" "strip-ansi" "6.0.0"
"text-table" "0.2.0" "text-table" "0.2.0"
"react-dom@*", "react-dom@^17.0.2", "react-dom@>=0.14.0", "react-dom@>=15.0.0": "react-dom@*", "react-dom@^17.0.2", "react-dom@>=0.14.0", "react-dom@>=15.0.0", "react-dom@>=16":
"integrity" "sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA==" "integrity" "sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA=="
"resolved" "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz" "resolved" "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz"
"version" "17.0.2" "version" "17.0.2"
@ -9448,7 +9448,7 @@
optionalDependencies: optionalDependencies:
"fsevents" "^2.1.3" "fsevents" "^2.1.3"
"react@*", "react@^17.0.2", "react@>= 16", "react@>=0.14.0", "react@>=15.0.0", "react@17.0.2": "react@*", "react@^17.0.2", "react@>= 16", "react@>=0.14.0", "react@>=15.0.0", "react@>=16", "react@17.0.2":
"integrity" "sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==" "integrity" "sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA=="
"resolved" "https://registry.npmjs.org/react/-/react-17.0.2.tgz" "resolved" "https://registry.npmjs.org/react/-/react-17.0.2.tgz"
"version" "17.0.2" "version" "17.0.2"
@ -9456,6 +9456,11 @@
"loose-envify" "^1.1.0" "loose-envify" "^1.1.0"
"object-assign" "^4.1.1" "object-assign" "^4.1.1"
"reactjs-popup@^2.0.5":
"integrity" "sha512-b5hv9a6aGsHEHXFAgPO5s1Jw1eSkopueyUVxQewGdLgqk2eW0IVXZrPRpHR629YcgIpC2oxtX8OOZ8a7bQJbxA=="
"resolved" "https://registry.npmjs.org/reactjs-popup/-/reactjs-popup-2.0.5.tgz"
"version" "2.0.5"
"read-pkg-up@^7.0.1": "read-pkg-up@^7.0.1":
"integrity" "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==" "integrity" "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg=="
"resolved" "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz" "resolved" "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz"