Improve the web-viewer.

Former-commit-id: 2b6f3e2307214cc2de76e2bae2f3df21370c4464
This commit is contained in:
David 2019-02-27 10:06:38 +01:00
commit cb1cbf4d1c
11 changed files with 486 additions and 47 deletions

23
web/.gitignore vendored Executable file
View 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*

View file

@ -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>

View file

@ -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 {

View file

@ -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
View 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 }
]
},
];