finished gif creator
This commit is contained in:
parent
0559d6fac8
commit
1ba149b3e8
19
package-lock.json
generated
19
package-lock.json
generated
@ -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",
|
||||||
|
|||||||
@ -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": {
|
||||||
|
|||||||
@ -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,10 +25,22 @@ 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);
|
||||||
@ -31,15 +48,15 @@ class GIFGenerator extends React.Component {
|
|||||||
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">
|
||||||
|
<div className="popupHeader">
|
||||||
|
GIF Generator
|
||||||
|
</div>
|
||||||
|
<div className="popupSection">
|
||||||
<label htmlFor="gifDuration" id="gifLengthLabel">Duration In Seconds: </label>
|
<label htmlFor="gifDuration" id="gifLengthLabel">Duration In Seconds: </label>
|
||||||
<input htmlFor="gifDuration" type="number" id="gifLengthInput"
|
<input htmlFor="gifDuration" type="number" id="gifLengthInput"
|
||||||
value={this.state.duration} onChange={(e) => this.setState({duration: e.target.value})}/>
|
value={this.state.duration} onChange={(e) => this.setState({duration: e.target.value})}/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="popupSection">
|
||||||
<label htmlFor="gifBackground" id="gifBackgroundLabel">Background Color: </label>
|
<label htmlFor="gifBackground" id="gifBackgroundLabel">Background Color: </label>
|
||||||
<input htmlFor="gifBackground" id="gifBackgroundInput" type='color'
|
<input htmlFor="gifBackground" id="gifBackgroundInput" type='color'
|
||||||
value={this.state.background}
|
value={this.state.background}
|
||||||
onChange={(e) => this.setState({background: e.target.value})}/>
|
onChange={(e) => this.setState({background: e.target.value})}/>
|
||||||
|
</div>
|
||||||
|
<div className="popupSection">
|
||||||
<label htmlFor="gifLoop" id="gifLoopLabel">Loop: </label>
|
<label htmlFor="gifLoop" id="gifLoopLabel">Loop: </label>
|
||||||
<input htmlFor="gifLoop" id="gifLoopInput" type="checkbox" checked={this.state.loop}
|
<input htmlFor="gifLoop" id="gifLoopInput" type="checkbox" checked={this.state.loop}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
this.setState({loop: e.target.checked})}/>
|
this.setState({loop: e.target.checked})}/>
|
||||||
<button onClick ={this.generateGIF} >Generate GIF</button>
|
</div>
|
||||||
|
<button className="popupButton" onClick={this.generateGIF} >Generate GIF 📸</button>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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>
|
||||||
|
{close => (
|
||||||
|
<div className="modal">
|
||||||
|
<button className="close" onClick={() => {close()}} >×</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>
|
<h3 id="durationDescription">Duration (seconds): </h3>
|
||||||
<input id="animationDuration" type="number" onChange={this.changeAnimationDuration} value={this.state.animationDuration}/>
|
<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>);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,6 +5,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.Dropdown {
|
.Dropdown {
|
||||||
|
cursor: pointer;
|
||||||
color: #eeeeee;
|
color: #eeeeee;
|
||||||
font-size: 15pt;
|
font-size: 15pt;
|
||||||
display: block;
|
display: block;
|
||||||
|
|||||||
@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
@ -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"
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user