mirror of
https://github.com/reanimate/reanimate.git
synced 2026-09-14 09:32:22 +00:00
Improve the web-viewer.
Former-commit-id: 2b6f3e2307214cc2de76e2bae2f3df21370c4464
This commit is contained in:
parent
bbb59d6cce
commit
cb1cbf4d1c
11 changed files with 486 additions and 47 deletions
23
web/.gitignore
vendored
Executable file
23
web/.gitignore
vendored
Executable file
|
|
@ -0,0 +1,23 @@
|
|||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
|
@ -22,7 +22,7 @@
|
|||
work correctly both with client-side routing and a non-root public URL.
|
||||
Learn how to configure a non-root public URL by running `npm run build`.
|
||||
-->
|
||||
<title>React App</title>
|
||||
<title>Reanimate Live Editor</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
|
|
|
|||
|
|
@ -7,22 +7,32 @@
|
|||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
grid-template-rows: max-content auto;
|
||||
grid-gap: 1em;
|
||||
/* grid-gap: 1em; */
|
||||
grid-auto-flow: column
|
||||
}
|
||||
|
||||
.editor {
|
||||
/* min-width: 35em; */
|
||||
}
|
||||
.controls {
|
||||
text-align:center;
|
||||
}
|
||||
.controls svg {
|
||||
padding-left: 5px;
|
||||
padding-right: 5px;
|
||||
}
|
||||
.viewer {
|
||||
position: static;
|
||||
}
|
||||
div.messages {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
transition: height 1s ease;
|
||||
height: 1em + 4px;
|
||||
background: #282c34;
|
||||
}
|
||||
div.messages pre {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
div.messages:hover {
|
||||
height: 100px;
|
||||
transition: height 1s ease;
|
||||
}
|
||||
|
||||
#editor {
|
||||
|
||||
|
|
|
|||
131
web/src/App.jsx
131
web/src/App.jsx
|
|
@ -1,45 +1,142 @@
|
|||
import React, {Component} from 'react';
|
||||
import './App.css';
|
||||
import AceEditor from 'react-ace';
|
||||
import brace from 'brace';
|
||||
import 'brace/mode/haskell';
|
||||
import 'brace/theme/github';
|
||||
import 'brace/theme/monokai';
|
||||
|
||||
import preset from './Presets';
|
||||
|
||||
class App extends Component {
|
||||
connect = () => {
|
||||
const ws = new WebSocket("ws://localhost:9160");
|
||||
|
||||
ws.onopen = event => {
|
||||
this.setState(state => ({...state, message: "Connected."}));
|
||||
ws.send(this.state.program);
|
||||
}
|
||||
ws.onclose = event => {
|
||||
this.setState(state => ({...state, message: "Disconnected."}));
|
||||
setTimeout(this.connect, 1000);
|
||||
}
|
||||
ws.onmessage = event => {
|
||||
if( event.data === "Success!" ) {
|
||||
console.log("Success");
|
||||
} else if( event.data === "Rendering" ) {
|
||||
this.setState({message: "Rendering..."});
|
||||
this.nFrames_new = 0;
|
||||
this.svgs_new = [];
|
||||
} else if( event.data === "Done" ) {
|
||||
this.setState({message: "Success!"});
|
||||
console.log("Done");
|
||||
this.nFrames = this.nFrames_new;
|
||||
this.svgs = this.svgs_new;
|
||||
this.nFrames_new = 0;
|
||||
this.svgs_new = [];
|
||||
this.start = Date.now();
|
||||
} else if( event.data.startsWith("Error") ) {
|
||||
console.log("Error");
|
||||
this.setState({message: event.data });
|
||||
} else {
|
||||
this.setState({message: `Rendering: ${this.nFrames_new}`});
|
||||
this.nFrames_new++;
|
||||
const div = document.createElement('div');
|
||||
div.innerHTML = event.data;
|
||||
this.svgs_new.push(div);
|
||||
}
|
||||
}
|
||||
this.setState(state => ({...state, socket: ws, message: "Connecting..."}));
|
||||
}
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
program: preset[0].programs[0].code
|
||||
};
|
||||
setTimeout(this.connect,0);
|
||||
this.nFrames_new = 0;
|
||||
this.svgs_new = [];
|
||||
this.nFrames = 0;
|
||||
this.svgs = [];
|
||||
this.start = Date.now();
|
||||
const self = this;
|
||||
const animate = () => {
|
||||
const now = Date.now();
|
||||
const nFrames = self.nFrames;
|
||||
const thisFrame = (Math.round((now-this.start)/1000*60))%nFrames
|
||||
// const thisFrame = 0;
|
||||
// console.log('Animation frame:', thisFrame, nFrames);
|
||||
if(nFrames) {
|
||||
// self.svg.innerHTML = self.svgs[thisFrame];
|
||||
while(self.svg.firstChild)
|
||||
self.svg.removeChild(self.svg.firstChild);
|
||||
self.svg.appendChild(self.svgs[thisFrame]);
|
||||
} else {
|
||||
self.svg.innerText = "Loading...";
|
||||
}
|
||||
requestAnimationFrame(animate);
|
||||
};
|
||||
requestAnimationFrame(animate);
|
||||
}
|
||||
onLoad = ace => {
|
||||
setTimeout(function() {
|
||||
ace.resize();
|
||||
}, 0);
|
||||
}
|
||||
onChange = text => {
|
||||
this.setState({program: text});
|
||||
if (this.timeout)
|
||||
clearTimeout(this.timeout);
|
||||
const socket = this.state.socket
|
||||
const self = this;
|
||||
|
||||
this.timeout = setTimeout(function() {
|
||||
console.log('change', text);
|
||||
self.setState(state => ({...state, message: "Compiling..."}));
|
||||
socket.send(text);
|
||||
}, 500);
|
||||
};
|
||||
selectPreset = evt => {
|
||||
this.onChange(evt.target.value);
|
||||
};
|
||||
render() {
|
||||
const {message, program} = this.state;
|
||||
return (
|
||||
<div className="App">
|
||||
<div>
|
||||
<select>
|
||||
<optgroup label="Examples">
|
||||
<option value="">Drawing LaTeX</option>
|
||||
<option value="">Colorful LaTeX</option>
|
||||
</optgroup>
|
||||
<optgroup label="Demos">
|
||||
<option value="">Bounding Boxes</option>
|
||||
</optgroup>
|
||||
<optgroup label="API">
|
||||
<option value="">LaTeX Basic</option>
|
||||
</optgroup>
|
||||
<select onChange={this.selectPreset}>
|
||||
{ preset.map( (group, i) =>
|
||||
<optgroup key={i} label={group.name}>
|
||||
{ group.programs.map( (elt, i) =>
|
||||
<option value={elt.code} key={i}>{elt.name}</option>
|
||||
)}
|
||||
</optgroup>
|
||||
)}
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<div className="editor">
|
||||
<AceEditor
|
||||
mode="haskell"
|
||||
width="100%"
|
||||
height="100%"
|
||||
theme="monokai"
|
||||
fontSize={16}
|
||||
name="UNIQUE_ID_OF_DIV"
|
||||
value={program}
|
||||
onLoad={this.onLoad}
|
||||
onChange={this.onChange}
|
||||
focus={true}
|
||||
editorProps={{
|
||||
$blockScrolling: true
|
||||
}}/>
|
||||
</div>
|
||||
<div>Controls</div>
|
||||
<div>
|
||||
Viewer
|
||||
<div className="controls">
|
||||
|
||||
</div>
|
||||
<div className="viewer">
|
||||
<div ref={node => this.svg = node}/>
|
||||
<div className="messages">
|
||||
<p>Compilation successful.</p>
|
||||
<pre>{message}</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
231
web/src/Presets.jsx
Normal file
231
web/src/Presets.jsx
Normal file
|
|
@ -0,0 +1,231 @@
|
|||
const latex_draw =
|
||||
`animation = pauseAtEnd 1 $ proc () -> do
|
||||
emit -< toHtml $ mkBackground "black"
|
||||
drawText msg \`andThen\` fillText msg -< ()
|
||||
where
|
||||
msg = "\\\\sum_{k=1}^\\\\infty {1 \\\\over k^2} = {\\\\pi^2 \\\\over 6}"
|
||||
placement = translate (320/2) (180/2) . scale 5
|
||||
fillText txt = proc () -> do
|
||||
duration 1 -< ()
|
||||
s <- signal 0 1 -< ()
|
||||
emit -< toHtml $ placement $
|
||||
withFillColor "white" $ withFillOpacity s $
|
||||
center $ latexAlign txt
|
||||
drawText txt = proc () -> do
|
||||
duration 2 -< ()
|
||||
s <- signal 0 1 -< ()
|
||||
emit -< toHtml $ placement $
|
||||
withStrokeColor "white" $ withFillOpacity 0 $ withStrokeWidth (Num 0.1) $
|
||||
partialSvg s $ center $ latexAlign txt`;
|
||||
|
||||
const bbox =
|
||||
`animation :: Ani ()
|
||||
animation = proc () -> do
|
||||
emit -< toHtml $ mkBackground "black"
|
||||
duration 5 -< ()
|
||||
annotate' bbox1 -< g_ [transform_ $ Lucid.translate (320/2-50) (180/2)]
|
||||
annotate' bbox2 -< g_ [transform_ $ Lucid.translate (320/2+50) (180/2)]
|
||||
|
||||
bbox1 :: Ani ()
|
||||
bbox1 = proc () -> do
|
||||
s <- signal 0 1 -< ()
|
||||
emit -< do
|
||||
toHtml $ mkBoundingBox $ rotate (360*s) svg
|
||||
toHtml $ withFillColor "white" $ rotate (360*s) svg
|
||||
where
|
||||
svg = scale 3 $ center $ latexAlign "\\\\sum_{k=1}^\\\\infty"
|
||||
|
||||
bbox2 :: Ani ()
|
||||
bbox2 = proc () -> do
|
||||
s <- signalOscillate 0 1 -< ()
|
||||
emit -< do
|
||||
toHtml $ mkBoundingBox $ partialSvg s heartShape
|
||||
toHtml $ withStrokeColor "white" $ withFillOpacity 0 $ partialSvg s heartShape
|
||||
|
||||
mkBoundingBox :: Tree -> Tree
|
||||
mkBoundingBox svg = withStrokeColor "red" $ withFillOpacity 0 $
|
||||
mkRect (S.Num x, S.Num y) (S.Num w) (S.Num h)
|
||||
where
|
||||
(x, y, w, h) = boundingBox svg
|
||||
|
||||
heartShape =
|
||||
center $ rotateAroundCenter 225 $ mkPathString
|
||||
"M0.0,40.0 v-40.0 h40.0\
|
||||
\a20.0 20.0 90.0 0 1 0.0,40.0\
|
||||
\a20.0 20.0 90.0 0 1 -40.0,0.0 Z"`;
|
||||
|
||||
const sinewave =
|
||||
`animation :: Ani ()
|
||||
animation = proc () -> do
|
||||
duration 10 -< ()
|
||||
emit -< toHtml $ mkBackground "black"
|
||||
idx <- signalOscillate 0 1 -< ()
|
||||
emit -< do
|
||||
defs_ $ clipPath_ [id_ "clip"] $ toHtml $
|
||||
mkRect (Num 0, Num (-height)) (Num $ idx*width) (Num 320)
|
||||
toHtml $ translate margin height $ withStrokeColor "white" $
|
||||
withClipPathRef (Ref "clip") $ mkPathText $ renderPathText $ approxFnData 1000 wave
|
||||
toHtml $ withStrokeColor "white" $
|
||||
mkLine (Num margin, Num 10) (Num margin, Num 170)
|
||||
toHtml $ withStrokeColor "white" $
|
||||
mkLine (Num margin, Num height) (Num (margin+width), Num height)
|
||||
let (circX, circY) = wave idx
|
||||
emit -< g_ [transform_ $ Lucid.translate margin height] $
|
||||
circle_ [num_ cx_ circX, num_ cy_ circY, r_ "3", fill_ "red"]
|
||||
where
|
||||
freq = 3; margin = 30; width = 260; height = 90
|
||||
wave idx = (idx*width, sin (idx*pi*2*freq) * 50)`;
|
||||
|
||||
const morph_wave =
|
||||
`animation :: Ani ()
|
||||
animation = proc () -> do
|
||||
duration 5 -< ()
|
||||
morph <- signalOscillate 0 1 -< ()
|
||||
emit -< toHtml $ mkBackground "black"
|
||||
emit -< toHtml $ withStrokeColor "white" $ mkGroup
|
||||
[ translate 30 50 $ mkPathText $ renderPathText wave1
|
||||
, translate 30 130 $ mkPathText $ renderPathText wave2
|
||||
, translate 30 90 $ mkPathText $ renderPathText $ morphPath wave1 wave2 morph
|
||||
, mkLine (Num 30, Num 10) (Num 30, Num 170)
|
||||
, mkLine (Num 30, Num 90) (Num 290, Num 90) ]
|
||||
where
|
||||
freq = 3; width = 260
|
||||
wave1 = approxFnData 1000 $ \\idx -> (idx*width, sin (idx*pi*2*freq) * 20)
|
||||
wave2 = approxFnData 1000 $ \\idx -> (idx*width, sin (idx*pi*2*(freq*3)) * 20)`;
|
||||
|
||||
const morph_wave_circle =
|
||||
`animation :: Ani ()
|
||||
animation = proc t -> do
|
||||
duration 5 -< ()
|
||||
idx <- signalOscillate 0 1 -< ()
|
||||
emit -< toHtml $ mkBackground "black"
|
||||
emit -< toHtml $ withStrokeColor "white" $ mkGroup
|
||||
[ translate 30 90 $ mkPathText $ renderPathText $ morphPath circle wave1 idx
|
||||
, mkLine (Num 30, Num 10) (Num 30, Num 170)
|
||||
, mkLine (Num 30, Num 90) (Num 290, Num 90) ]
|
||||
where
|
||||
freq = 5; width = 260; radius = 50
|
||||
wave1 = approxFnData 100 $ \\idx -> (idx*width, sin (idx*pi*2*freq) * 20)
|
||||
circle = approxFnData 100 $ \\idx ->
|
||||
(cos (idx*pi*2+pi/2)*radius + width/2, sin (idx*pi*2+pi/2)*radius)`;
|
||||
|
||||
const progressMeters =
|
||||
`animation :: Ani ()
|
||||
animation = proc () -> do
|
||||
emit -< rect_ [width_ "100%", height_ "100%", fill_ "black"]
|
||||
annotate' (adjustSpeed 1.0 progressMeter) -< g_ [transform_ $ Lucid.translate 40 20]
|
||||
annotate' (adjustSpeed 2.0 progressMeter) -< g_ [transform_ $ Lucid.translate 140 20]
|
||||
annotate' (adjustSpeed 0.5 progressMeter) -< g_ [transform_ $ Lucid.translate 240 20]
|
||||
|
||||
emit -< do
|
||||
text_ [x_ "55", y_ "150", font_size_ "20"
|
||||
, text_anchor_ "middle"
|
||||
, fill_ "white"] "1x"
|
||||
text_ [x_ "155", y_ "150", font_size_ "20"
|
||||
, text_anchor_ "middle"
|
||||
, fill_ "white"] "2x"
|
||||
text_ [x_ "255", y_ "150", font_size_ "20"
|
||||
, text_anchor_ "middle"
|
||||
, fill_ "white"] "0.5x"
|
||||
|
||||
progressMeter :: Ani ()
|
||||
progressMeter = loop $ proc () -> do
|
||||
duration 5 -< ()
|
||||
h <- signal 0 100 -< ()
|
||||
emit -< rect_ [ width_ "30", height_ "100", stroke_ "white", stroke_width_ "2", fill_opacity_ "0" ]
|
||||
emit -< rect_ [ width_ "30", num_ height_ h, stroke_ "white", fill_ "white" ]
|
||||
returnA -< ()`
|
||||
|
||||
const highlight =
|
||||
`animation :: Ani ()
|
||||
animation = proc () -> do
|
||||
emit -< rect_ [width_ "100%", height_ "100%", fill_ "black"]
|
||||
emit -< do
|
||||
path_ (commonAttrs "white" ++ [d_ $ renderPathText rect1])
|
||||
path_ (commonAttrs "white" ++ [d_ $ renderPathText rect2])
|
||||
|
||||
path_ (commonAttrs "white" ++ [d_ $ renderPathText rect3])
|
||||
path_ (commonAttrs "lightblue" ++ [d_ $ renderPathText rect4])
|
||||
path_ (commonAttrs "yellow" ++ [d_ $ renderPathText rect5])
|
||||
path_ (commonAttrs "red" ++ [d_ $ renderPathText rect6])
|
||||
|
||||
follow
|
||||
[ mkTransition highlight1 highlight2
|
||||
, mkTransition highlight2 highlight3
|
||||
, mkTransition highlight3 highlight4
|
||||
, mkTransition highlight4 highlight5
|
||||
, mkTransition highlight5 highlight6
|
||||
, mkTransition highlight6 highlight1
|
||||
] -< ()
|
||||
|
||||
where
|
||||
mkTransition from to = pauseAtEnd 1 $ proc () -> do
|
||||
duration 1 -< ()
|
||||
s <- signalSCurve 2 0 1 -< ()
|
||||
let trans = morphPath from to s
|
||||
emit -<
|
||||
path_ (highlightAttrs "green" ++ [d_ $ renderPathText trans <> "Z"])
|
||||
mkRect x y width height =
|
||||
[ (x,y), (x+width, y), (x+width, y+height), (x,y+height) ]
|
||||
rect1 = mkRect margin margin w h
|
||||
rect2 = mkRect (320-margin-w*2) margin (w*2) h
|
||||
rect3 = mkRect margin (180-margin-h) w h
|
||||
rect4 = mkRect (320/3) (180-margin-h) w h
|
||||
rect5 = mkRect (320/3*2-w) (180-margin-h) w h
|
||||
rect6 = mkRect (320-margin-w) (180-margin-h) w h
|
||||
highlight1 = mkRect (margin-b) (margin-b) (w+2*b) (h+2*b)
|
||||
highlight2 = mkRect (320-margin-w*2-b) (margin-b) (w*2+2*b) (h+2*b)
|
||||
highlight3 = mkRect (320-margin-w-b) (180-margin-h-b) (w+2*b) (h+2*b)
|
||||
highlight4 = mkRect (320/3*2-w-b) (180-margin-h-b) (320/3+2*b) (h+2*b)
|
||||
highlight5 = mkRect (320/3-b) (180-margin-h-b) (320/3+2*b) (h+2*b)
|
||||
highlight6 = mkRect (margin-b) (180-margin-h-b) (320/3+2*b) (h+2*b)
|
||||
b = 7; margin = 30; w = 30; h = 30
|
||||
commonAttrs c = [stroke_width_ "2", stroke_ c, fill_ c]
|
||||
highlightAttrs c = [stroke_width_ "2", stroke_ c, fill_opacity_ "0"]`;
|
||||
|
||||
const latex_basic =
|
||||
`animation :: Ani ()
|
||||
animation = proc () -> do
|
||||
duration 2 -< ()
|
||||
s <- signalOscillate 0 1 -< ()
|
||||
emit -< toHtml $ mkGroup
|
||||
[ mkBackground "black"
|
||||
, translate (320/2) (180/2) $ mkGroup
|
||||
[ withStrokeColor "white" $ withFillOpacity 0 $ withStrokeWidth (Num 0.1) text
|
||||
, withFillColor "white" $ withFillOpacity s text] ]
|
||||
where
|
||||
text = scale 4 $ center $ latexAlign
|
||||
"\\\\sum_{k=1}^\\\\infty {1 \\\\over k^2} = {\\\\pi^2 \\\\over 6}"`
|
||||
|
||||
const latex_color =
|
||||
`animation :: Ani ()
|
||||
animation = proc () -> do
|
||||
duration 0.1 -< ()
|
||||
emit -< toHtml $ mkBackground "black"
|
||||
emit -< toHtml $ translate (320/2) (180/2) $ withStrokeWidth (Num 0.2) $
|
||||
withStrokeColor "white" $
|
||||
withSubglyphs [0] (withFillColor "blue") $
|
||||
withSubglyphs [1] (withFillColor "yellow") $
|
||||
withSubglyphs [2] (withFillColor "green") $
|
||||
withSubglyphs [3] (withFillColor "red") $
|
||||
withSubglyphs [4] (withFillColor "darkslategrey") $
|
||||
svg
|
||||
where
|
||||
svg = scale 10 $ center $ latex "\\\\LaTeX"`
|
||||
|
||||
|
||||
export default [
|
||||
{ name: "Examples"
|
||||
, programs:
|
||||
[ {name: "LaTeX Draw", code: latex_draw }
|
||||
, {name: "LaTeX Color", code: latex_color }
|
||||
, {name: "LaTeX Basic", code: latex_basic }
|
||||
, {name: "Bounding boxes", code: bbox }
|
||||
, {name: "Sinewave", code: sinewave }
|
||||
, {name: "Morphwave", code: morph_wave }
|
||||
, {name: "Morphwave Circle", code: morph_wave_circle }
|
||||
, {name: "Progress meters", code: progressMeters }
|
||||
// , {name: "Highlight", code: highlight }
|
||||
]
|
||||
},
|
||||
];
|
||||
Loading…
Reference in a new issue