mirror of
https://github.com/reanimate/reanimate.git
synced 2026-09-14 09:32:22 +00:00
Improve the web-viewer.
This commit is contained in:
parent
7d050fb7d6
commit
3ac39cb2bc
11 changed files with 486 additions and 47 deletions
|
|
@ -28,17 +28,32 @@ library
|
||||||
build-depends: base >=4.10 && <4.13,
|
build-depends: base >=4.10 && <4.13,
|
||||||
lucid-svg, time, text, unix, lucid, filepath, process, directory,
|
lucid-svg, time, text, unix, lucid, filepath, process, directory,
|
||||||
containers, svg-tree >= 0.6.2.3, xml, bytestring, lens, linear, mtl, matrix,
|
containers, svg-tree >= 0.6.2.3, xml, bytestring, lens, linear, mtl, matrix,
|
||||||
JuicyPixels, attoparsec
|
JuicyPixels, attoparsec, parallel
|
||||||
|
|
||||||
Flag gtk-viewer
|
Flag gtk-viewer
|
||||||
Description: Enable gtk-based viewer
|
Description: Enable gtk-based viewer
|
||||||
Default: True
|
Default: False
|
||||||
|
|
||||||
|
Flag server
|
||||||
|
Description: Enable rendering server
|
||||||
|
Default: False
|
||||||
|
|
||||||
|
executable reanimate-server
|
||||||
|
if flag(server)
|
||||||
|
buildable: True
|
||||||
|
else
|
||||||
|
buildable: False
|
||||||
|
hs-source-dirs: server, src
|
||||||
|
main-is: Main.hs
|
||||||
|
other-modules: Reanimate.Misc
|
||||||
|
build-depends: base >=4.10 && <4.13, text, websockets, process, filepath, directory,
|
||||||
|
cache, clock, stm, containers
|
||||||
|
|
||||||
executable reanimate-viewer
|
executable reanimate-viewer
|
||||||
if flag(gtk-viewer)
|
if flag(gtk-viewer)
|
||||||
buildable: True
|
buildable: True
|
||||||
else
|
else
|
||||||
buildable: False
|
buildable: False
|
||||||
main-is: SvgViewer.hs
|
main-is: SvgViewer.hs
|
||||||
-- other-extensions:
|
-- other-extensions:
|
||||||
other-modules: Reanimate.Arrow
|
other-modules: Reanimate.Arrow
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,15 @@ frameAt n (Animation d fn) = svg $ fn d (n `mod'` d) ()
|
||||||
with (svg11_ content) [width_ "320" , height_ "180", viewBox_ "0 0 320 180"]
|
with (svg11_ content) [width_ "320" , height_ "180", viewBox_ "0 0 320 180"]
|
||||||
-- with (svg11_ content) [width_ "1280" , height_ "720", viewBox_ "0 0 320 180"]
|
-- with (svg11_ content) [width_ "1280" , height_ "720", viewBox_ "0 0 320 180"]
|
||||||
|
|
||||||
|
unboundedFrameAt :: Double -> Ani () -> Svg ()
|
||||||
|
unboundedFrameAt n (Animation d fn) = svg $ fn d (n `mod'` d) ()
|
||||||
|
where
|
||||||
|
svg :: Svg () -> Svg ()
|
||||||
|
svg content = do
|
||||||
|
doctype_
|
||||||
|
with (svg11_ content) [viewBox_ "0 0 320 180"]
|
||||||
|
-- with (svg11_ content) [width_ "1280" , height_ "720", viewBox_ "0 0 320 180"]
|
||||||
|
|
||||||
emit :: Animation (Svg ()) ()
|
emit :: Animation (Svg ()) ()
|
||||||
emit = Animation 0 (\d t svg -> svg)
|
emit = Animation 0 (\d t svg -> svg)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ sinewave = proc () -> do
|
||||||
idx <- signalOscillate 0 1 -< ()
|
idx <- signalOscillate 0 1 -< ()
|
||||||
emit -< do
|
emit -< do
|
||||||
defs_ $ clipPath_ [id_ "clip"] $ toHtml $
|
defs_ $ clipPath_ [id_ "clip"] $ toHtml $
|
||||||
mkRect (Num 0, Num (-height)) (Num $ idx*width) (Percent 100)
|
mkRect (Num 0, Num (-height)) (Num $ idx*width) (Num 320)
|
||||||
toHtml $ translate margin height $ withStrokeColor "white" $
|
toHtml $ translate margin height $ withStrokeColor "white" $
|
||||||
withClipPathRef (Ref "clip") $ mkPathText $ renderPathText $ approxFnData 1000 wave
|
withClipPathRef (Ref "clip") $ mkPathText $ renderPathText $ approxFnData 1000 wave
|
||||||
toHtml $ withStrokeColor "white" $
|
toHtml $ withStrokeColor "white" $
|
||||||
|
|
@ -54,9 +54,9 @@ morph_wave :: Ani ()
|
||||||
morph_wave = proc () -> do
|
morph_wave = proc () -> do
|
||||||
duration 5 -< ()
|
duration 5 -< ()
|
||||||
morph <- signalOscillate 0 1 -< ()
|
morph <- signalOscillate 0 1 -< ()
|
||||||
|
emit -< toHtml $ mkBackground "black"
|
||||||
emit -< toHtml $ withStrokeColor "white" $ mkGroup
|
emit -< toHtml $ withStrokeColor "white" $ mkGroup
|
||||||
[ mkBackground "black"
|
[ translate 30 50 $ mkPathText $ renderPathText wave1
|
||||||
, translate 30 50 $ mkPathText $ renderPathText wave1
|
|
||||||
, translate 30 130 $ mkPathText $ renderPathText wave2
|
, translate 30 130 $ mkPathText $ renderPathText wave2
|
||||||
, translate 30 90 $ mkPathText $ renderPathText $ morphPath wave1 wave2 morph
|
, translate 30 90 $ mkPathText $ renderPathText $ morphPath wave1 wave2 morph
|
||||||
, mkLine (Num 30, Num 10) (Num 30, Num 170)
|
, mkLine (Num 30, Num 10) (Num 30, Num 170)
|
||||||
|
|
@ -368,7 +368,7 @@ bezier = adjustSpeed 0.4 $ proc () -> do
|
||||||
let new' = map (\(a,b) -> between a b s) (zip old new)
|
let new' = map (\(a,b) -> between a b s) (zip old new)
|
||||||
emit -< forM_ (zip new' (tail new')) $ \(a,b) -> do
|
emit -< forM_ (zip new' (tail new')) $ \(a,b) -> do
|
||||||
renderPath $
|
renderPath $
|
||||||
approxFnData 1000 $ \idx ->
|
approxFnData 100 $ \idx ->
|
||||||
between a b idx
|
between a b idx
|
||||||
emit -< mapM_ secondaryCircleAt new'
|
emit -< mapM_ secondaryCircleAt new'
|
||||||
emit -< primaryCircleAt (head new')
|
emit -< primaryCircleAt (head new')
|
||||||
|
|
@ -377,11 +377,11 @@ bezier = adjustSpeed 0.4 $ proc () -> do
|
||||||
s <- signalOscillate 0 1 -< ()
|
s <- signalOscillate 0 1 -< ()
|
||||||
emit -< primaryCircleAt =<< orderN' (map const lst) s <* mapM_ secondaryCircleAt lst
|
emit -< primaryCircleAt =<< orderN' (map const lst) s <* mapM_ secondaryCircleAt lst
|
||||||
orderN' [a] s = do
|
orderN' [a] s = do
|
||||||
renderPath $ take (round $ 1000*s) $ approxFnData 1000 $ \idx -> a idx
|
renderPath $ take (round $ 100*s) $ approxFnData 100 $ \idx -> a idx
|
||||||
return (a s)
|
return (a s)
|
||||||
orderN' lst s = do
|
orderN' lst s = do
|
||||||
forM_ (zip lst (tail lst)) $ \(a,b) -> renderPath $
|
forM_ (zip lst (tail lst)) $ \(a,b) -> renderPath $
|
||||||
approxFnData 1000 $ \idx ->
|
approxFnData 100 $ \idx ->
|
||||||
between (a s) (b s) idx
|
between (a s) (b s) idx
|
||||||
let middlePoints = map (\(a,b) -> \idx -> between (a idx) (b idx) idx) (zip lst (tail lst))
|
let middlePoints = map (\(a,b) -> \idx -> between (a idx) (b idx) idx) (zip lst (tail lst))
|
||||||
orderN' middlePoints s <* mapM_ secondaryCircleAt (map ($s) middlePoints)
|
orderN' middlePoints s <* mapM_ secondaryCircleAt (map ($s) middlePoints)
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,24 @@
|
||||||
module Reanimate.Misc
|
module Reanimate.Misc
|
||||||
( requireExecutable
|
( requireExecutable
|
||||||
, runCmd
|
, runCmd
|
||||||
|
, runCmd_
|
||||||
|
, runCmdLazy
|
||||||
, withTempDir
|
, withTempDir
|
||||||
, withTempFile
|
, withTempFile
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import Control.Exception (evaluate, finally, throwIO)
|
import Control.Exception (evaluate, finally, throwIO)
|
||||||
|
import qualified Data.Text as T
|
||||||
|
import qualified Data.Text.IO as T
|
||||||
import System.Directory (createDirectory, findExecutable,
|
import System.Directory (createDirectory, findExecutable,
|
||||||
getTemporaryDirectory,
|
getTemporaryDirectory,
|
||||||
removeDirectoryRecursive, removeFile)
|
removeDirectoryRecursive, removeFile)
|
||||||
import System.Exit (ExitCode (..))
|
import System.Exit (ExitCode (..))
|
||||||
import System.FilePath ((<.>), (</>))
|
import System.FilePath ((<.>), (</>))
|
||||||
import System.IO (hClose, openTempFile)
|
import System.IO (hClose, openTempFile, hGetContents, hIsEOF)
|
||||||
import System.Process (readProcessWithExitCode, showCommandForUser)
|
import System.Process (readProcessWithExitCode,
|
||||||
|
runInteractiveProcess, showCommandForUser,
|
||||||
|
waitForProcess)
|
||||||
|
|
||||||
requireExecutable :: String -> IO FilePath
|
requireExecutable :: String -> IO FilePath
|
||||||
requireExecutable exec = do
|
requireExecutable exec = do
|
||||||
|
|
@ -24,16 +29,42 @@ requireExecutable exec = do
|
||||||
|
|
||||||
runCmd :: FilePath -> [String] -> IO ()
|
runCmd :: FilePath -> [String] -> IO ()
|
||||||
runCmd exec args = do
|
runCmd exec args = do
|
||||||
|
_ <- runCmd_ exec args
|
||||||
|
return ()
|
||||||
|
|
||||||
|
runCmd_ :: FilePath -> [String] -> IO (Either String String)
|
||||||
|
runCmd_ exec args = do
|
||||||
(ret, stdout, stderr) <- readProcessWithExitCode exec args ""
|
(ret, stdout, stderr) <- readProcessWithExitCode exec args ""
|
||||||
evaluate (length stdout + length stderr)
|
evaluate (length stdout + length stderr)
|
||||||
case ret of
|
case ret of
|
||||||
ExitSuccess -> return ()
|
ExitSuccess -> return (Right stdout)
|
||||||
ExitFailure err -> do
|
ExitFailure err -> do
|
||||||
putStrLn $
|
return $ Left $
|
||||||
"Failed to run: " ++ showCommandForUser exec args ++ "\n" ++
|
"Failed to run: " ++ showCommandForUser exec args ++ "\n" ++
|
||||||
"Error code: " ++ show err ++ "\n" ++
|
"Error code: " ++ show err ++ "\n" ++
|
||||||
"stderr: " ++ show stderr
|
"stderr: " ++ stderr
|
||||||
throwIO (ExitFailure err)
|
|
||||||
|
runCmdLazy :: FilePath -> [String] -> IO (IO (Either String T.Text))
|
||||||
|
runCmdLazy exec args = do
|
||||||
|
(inp, out, err, pid) <- runInteractiveProcess exec args Nothing Nothing
|
||||||
|
hClose inp
|
||||||
|
return $ do
|
||||||
|
eof <- hIsEOF out
|
||||||
|
if eof
|
||||||
|
then do
|
||||||
|
stderr <- hGetContents err
|
||||||
|
evaluate (length stderr)
|
||||||
|
ret <- waitForProcess pid
|
||||||
|
case ret of
|
||||||
|
ExitSuccess -> return (Left "")
|
||||||
|
ExitFailure err -> do
|
||||||
|
return $ Left $
|
||||||
|
"Failed to run: " ++ showCommandForUser exec args ++ "\n" ++
|
||||||
|
"Error code: " ++ show err ++ "\n" ++
|
||||||
|
"stderr: " ++ stderr
|
||||||
|
else do
|
||||||
|
line <- T.hGetLine out
|
||||||
|
return (Right line)
|
||||||
|
|
||||||
withTempDir :: (FilePath -> IO a) -> IO a
|
withTempDir :: (FilePath -> IO a) -> IO a
|
||||||
withTempDir action = do
|
withTempDir action = do
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,31 @@
|
||||||
module Reanimate.Render
|
module Reanimate.Render
|
||||||
( render
|
( render
|
||||||
|
, renderSvgs
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import Control.Monad (forM_)
|
import Control.Monad (forM_)
|
||||||
import Lucid.Svg (renderToFile)
|
import Lucid.Svg (renderToFile, renderBS)
|
||||||
import Reanimate.Arrow (Ani, animationDuration, frameAt)
|
import Reanimate.Arrow (Ani, animationDuration, frameAt, unboundedFrameAt)
|
||||||
import Reanimate.Examples
|
import Reanimate.Examples
|
||||||
import Reanimate.Misc
|
import Reanimate.Misc
|
||||||
import System.FilePath (takeExtension, (</>))
|
import System.Directory (renameFile)
|
||||||
|
import System.FilePath (takeExtension, takeFileName, (</>))
|
||||||
import Text.Printf (printf)
|
import Text.Printf (printf)
|
||||||
|
import qualified Data.ByteString.Lazy.Char8 as BS
|
||||||
|
import Control.Parallel.Strategies
|
||||||
|
|
||||||
|
renderSvgs :: Ani () -> FilePath -> IO ()
|
||||||
|
renderSvgs ani tmpDir = do
|
||||||
|
let frameName nth = tmpDir </> printf nameTemplate nth
|
||||||
|
renderedFrames = map (BS.concat . BS.lines . renderBS . nthFrame) frames
|
||||||
|
mapM_ BS.putStrLn (renderedFrames `using` parBuffer 16 rdeepseq)
|
||||||
|
where
|
||||||
|
frames = [0..frameCount-1]
|
||||||
|
rate = 60
|
||||||
|
nthFrame nth = unboundedFrameAt (recip (fromIntegral rate) * fromIntegral nth) ani
|
||||||
|
frameCount = round (animationDuration ani * fromIntegral rate) :: Int
|
||||||
|
nameTemplate :: String
|
||||||
|
nameTemplate = "render-%05d.svg"
|
||||||
|
|
||||||
|
|
||||||
data Format = RenderMp4 | RenderGif | RenderWebm
|
data Format = RenderMp4 | RenderGif | RenderWebm
|
||||||
|
|
|
||||||
|
|
@ -425,6 +425,12 @@ mkRect corner width height = RectangleTree $ defaultSvg
|
||||||
& rectWidth .~ width
|
& rectWidth .~ width
|
||||||
& rectHeight .~ height
|
& rectHeight .~ height
|
||||||
|
|
||||||
|
mkBoundingRect :: Tree -> Double -> Tree
|
||||||
|
mkBoundingRect src margin =
|
||||||
|
mkRect (Num $ x-margin, Num $ y-margin) (Num $ w+margin*2) (Num $ h+margin*2)
|
||||||
|
where
|
||||||
|
(x, y, w, h) = boundingBox src
|
||||||
|
|
||||||
mkLine :: Point -> Point -> Tree
|
mkLine :: Point -> Point -> Tree
|
||||||
mkLine point1 point2 = LineTree $ defaultSvg
|
mkLine point1 point2 = LineTree $ defaultSvg
|
||||||
& linePoint1 .~ point1
|
& linePoint1 .~ point1
|
||||||
|
|
|
||||||
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.
|
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`.
|
Learn how to configure a non-root public URL by running `npm run build`.
|
||||||
-->
|
-->
|
||||||
<title>React App</title>
|
<title>Reanimate Live Editor</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||||
|
|
|
||||||
|
|
@ -7,22 +7,32 @@
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr 1fr;
|
grid-template-columns: 1fr 1fr;
|
||||||
grid-template-rows: max-content auto;
|
grid-template-rows: max-content auto;
|
||||||
grid-gap: 1em;
|
/* grid-gap: 1em; */
|
||||||
grid-auto-flow: column
|
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 {
|
div.messages {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
bottom: 0;
|
top: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
transition: height 1s ease;
|
background: #282c34;
|
||||||
height: 1em + 4px;
|
}
|
||||||
|
div.messages pre {
|
||||||
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.messages:hover {
|
|
||||||
height: 100px;
|
|
||||||
transition: height 1s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
#editor {
|
#editor {
|
||||||
|
|
||||||
|
|
|
||||||
131
web/src/App.jsx
131
web/src/App.jsx
|
|
@ -1,45 +1,142 @@
|
||||||
import React, {Component} from 'react';
|
import React, {Component} from 'react';
|
||||||
import './App.css';
|
import './App.css';
|
||||||
import AceEditor from 'react-ace';
|
import AceEditor from 'react-ace';
|
||||||
import brace from 'brace';
|
|
||||||
import 'brace/mode/haskell';
|
import 'brace/mode/haskell';
|
||||||
import 'brace/theme/github';
|
import 'brace/theme/github';
|
||||||
import 'brace/theme/monokai';
|
import 'brace/theme/monokai';
|
||||||
|
|
||||||
|
import preset from './Presets';
|
||||||
|
|
||||||
class App extends Component {
|
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() {
|
render() {
|
||||||
|
const {message, program} = this.state;
|
||||||
return (
|
return (
|
||||||
<div className="App">
|
<div className="App">
|
||||||
<div>
|
<div>
|
||||||
<select>
|
<select onChange={this.selectPreset}>
|
||||||
<optgroup label="Examples">
|
{ preset.map( (group, i) =>
|
||||||
<option value="">Drawing LaTeX</option>
|
<optgroup key={i} label={group.name}>
|
||||||
<option value="">Colorful LaTeX</option>
|
{ group.programs.map( (elt, i) =>
|
||||||
</optgroup>
|
<option value={elt.code} key={i}>{elt.name}</option>
|
||||||
<optgroup label="Demos">
|
)}
|
||||||
<option value="">Bounding Boxes</option>
|
</optgroup>
|
||||||
</optgroup>
|
)}
|
||||||
<optgroup label="API">
|
|
||||||
<option value="">LaTeX Basic</option>
|
|
||||||
</optgroup>
|
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div className="editor">
|
||||||
<AceEditor
|
<AceEditor
|
||||||
mode="haskell"
|
mode="haskell"
|
||||||
width="100%"
|
width="100%"
|
||||||
height="100%"
|
height="100%"
|
||||||
theme="monokai"
|
theme="monokai"
|
||||||
|
fontSize={16}
|
||||||
name="UNIQUE_ID_OF_DIV"
|
name="UNIQUE_ID_OF_DIV"
|
||||||
|
value={program}
|
||||||
|
onLoad={this.onLoad}
|
||||||
|
onChange={this.onChange}
|
||||||
|
focus={true}
|
||||||
editorProps={{
|
editorProps={{
|
||||||
$blockScrolling: true
|
$blockScrolling: true
|
||||||
}}/>
|
}}/>
|
||||||
</div>
|
</div>
|
||||||
<div>Controls</div>
|
<div className="controls">
|
||||||
<div>
|
|
||||||
Viewer
|
</div>
|
||||||
|
<div className="viewer">
|
||||||
|
<div ref={node => this.svg = node}/>
|
||||||
<div className="messages">
|
<div className="messages">
|
||||||
<p>Compilation successful.</p>
|
<pre>{message}</pre>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</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