Better caching, bundle viewer.

Former-commit-id: cacfc7fd5402fadd381d16c748cdfdbf97a63cd9
This commit is contained in:
David 2019-03-08 16:36:25 +01:00
commit a4121d01d9
27 changed files with 296 additions and 359 deletions

View file

@ -1,5 +1,6 @@
#!/usr/bin/env stack
-- stack --resolver lts-11.22 runghc --package reanimate
{-# LANGUAGE OverloadedStrings #-}
module Main (main) where
import Control.Lens

View file

@ -13,6 +13,14 @@ build-type: Simple
extra-source-files: ChangeLog.md
cabal-version: >=1.10
data-files: viewer/build/*.js
viewer/build/*.html
viewer/build/static/js/2.772a56e7.chunk.js
viewer/build/static/js/main.db22f45d.chunk.js
viewer/build/static/js/runtime~main.9eb600ee.js
viewer/build/static/css/main.6efe09fd.chunk.css
library
hs-source-dirs: src
default-language: Haskell2010
@ -28,12 +36,15 @@ library
Reanimate.Driver
Reanimate.Misc
other-modules: Reanimate.Svg.NamedColors
Reanimate.Cache
Paths_reanimate
build-depends: base >=4.10 && <4.13,
time, text, unix, filepath, process, directory,
containers, reanimate-svg >= 0.7.0.0, xml, bytestring, lens, linear, mtl, matrix,
JuicyPixels, attoparsec, parallel, diagrams, diagrams-svg,
diagrams-core, diagrams-lib, diagrams-contrib,
svg-builder, matrices, cubicbezier, palette, hinotify, websockets
svg-builder, matrices, cubicbezier, palette, hinotify, websockets,
hashable
Flag gtk-viewer
Description: Enable gtk-based viewer

81
src/Reanimate/Cache.hs Normal file
View file

@ -0,0 +1,81 @@
module Reanimate.Cache
( cacheMem
, cacheDisk
, cacheDiskSvg
, cacheDiskLines
) where
import Control.Exception
import Data.Hashable
import Data.IORef
import Data.Map (Map)
import qualified Data.Map as Map
import Data.Text (Text)
import qualified Data.Text as T
import qualified Data.Text.Encoding as T
import qualified Data.Text.IO as T
import Graphics.SvgTree (Tree (..), parseSvgFile, unparse)
import Reanimate.Monad (renderTree)
import Reanimate.Svg (unbox)
import Text.XML.Light ( Content(..), parseXML )
import System.Directory
import System.FilePath
import System.IO.Unsafe
-- Memory cache and disk cache
cacheDisk :: (T.Text -> Maybe a) -> (a -> T.Text) -> (Text -> IO a) -> (Text -> IO a)
cacheDisk parse render gen key = do
root <- getXdgDirectory XdgCache "reanimate"
createDirectoryIfMissing True root
let path = root </> show (hash key)
hit <- doesFileExist path
if hit
then do
inp <- T.readFile path
case parse inp of
Nothing -> do
let tmp = path <.> "tmp"
new <- gen key
T.writeFile tmp (render new)
renameFile tmp path
return new
Just val -> pure val
else do
let tmp = path <.> "tmp"
new <- gen key
T.writeFile tmp (render new)
renameFile tmp path
return new
cacheDiskSvg :: (Text -> IO Tree) -> (Text -> IO Tree)
cacheDiskSvg = cacheDisk parse render
where
parse txt = case parseXML txt of
[Elem t] -> Just (unparse t)
_ -> Nothing
render = T.pack . renderTree
cacheDiskLines :: (Text -> IO [Text]) -> (Text -> IO [Text])
cacheDiskLines = cacheDisk parse render
where
parse = Just . T.lines
render = T.unlines
{-# NOINLINE cache #-}
cache :: IORef (Map Text Tree)
cache = unsafePerformIO (newIORef Map.empty)
cacheMem :: (Text -> IO Tree) -> (Text -> IO Tree)
cacheMem gen key = do
store <- readIORef cache
case Map.lookup key store of
Just svg -> return svg
Nothing -> do
svg <- gen key
case svg of
-- None usually indicates that latex or another tool was misconfigured. In this case,
-- don't store the result.
None -> pure None
_ -> atomicModifyIORef cache (\store -> (Map.insert key svg store, svg))

View file

@ -11,10 +11,12 @@ import System.INotify (EventVariety (..), addWatch, withINotify)
import System.IO (BufferMode (..), hPutStrLn, hSetBuffering,
stderr, stdin)
import Reanimate.Misc (runCmdLazy, runCmd_, withTempFile)
import Reanimate.Misc (runCmdLazy, runCmd, runCmd_, withTempDir, withTempFile)
import Reanimate.Monad (Animation)
import Reanimate.Render (renderSvgs)
import Paths_reanimate
opts = defaultConnectionOptions
{ connectionCompressionOptions = PermessageDeflateCompression defaultPermessageDeflate }
@ -24,7 +26,10 @@ reanimate animation = do
hSetBuffering stdin NoBuffering
case args of
["once"] -> renderSvgs animation
_ -> runServerWith "127.0.0.1" 9161 opts $ \pending -> do
_ -> withTempDir $ \tmpDir -> do
url <- getDataFileName "viewer/build/index.html"
runCmd "xdg-open" [url]
runServerWith "127.0.0.1" 9161 opts $ \pending -> do
putStrLn "Server pending."
prog <- getProgName
lst <- listDirectory "."
@ -41,7 +46,7 @@ reanimate animation = do
putStrLn "Kill and respawn."
killThread tid
tid <- forkIO $ withTempFile ".exe" $ \tmpExecutable -> do
ret <- runCmd_ "stack" $ ["ghc", "--"] ++ ghcOptions ++ [self, "-o", tmpExecutable]
ret <- runCmd_ "stack" $ ["ghc", "--"] ++ ghcOptions tmpDir ++ [self, "-o", tmpExecutable]
case ret of
Left err ->
sendTextData conn $ T.pack $ "Error" ++ unlines (drop 3 (lines err))
@ -69,5 +74,7 @@ reanimate animation = do
loop
loop
ghcOptions :: [String]
ghcOptions = ["-rtsopts", "--make", "-threaded", "-O2"]
ghcOptions :: FilePath -> [String]
ghcOptions tmpDir =
["-rtsopts", "--make", "-threaded", "-O2"] ++
["-odir", tmpDir, "-hidir", tmpDir]

View file

@ -7,74 +7,36 @@ import qualified Data.ByteString as B
import Data.IORef
import Data.Map (Map)
import qualified Data.Map as Map
import Data.Monoid
import Reanimate.Cache
import Reanimate.Misc
import Reanimate.Svg
import System.FilePath (replaceExtension, takeFileName, (</>))
import System.IO.Unsafe (unsafePerformIO)
import Control.Lens (over, set, (%~), (&), (.~), (^.))
import Data.Text (Text)
import qualified Data.Text as T
import qualified Data.Text.IO as T
import Graphics.SvgTree (Document (..), Tree (..), defaultSvg,
elements, loadSvgFile, parseSvgFile,
xmlOfDocument)
import Text.XML.Light (elContent)
import Text.XML.Light.Output (ppcContent, ppcElement, prettyConfigPP)
-- instance ToHtml Document where
-- toHtml = toHtmlRaw
-- toHtmlRaw = toHtmlRaw . ppcElement prettyConfigPP . xmlOfDocument
latex :: T.Text -> Tree
latex tex = (unsafePerformIO . (cacheMem . cacheDiskSvg) latexToSVG)
("% plain latex\n" <> tex)
-- instance ToHtml Document where
-- toHtml = toHtmlRaw
-- toHtmlRaw doc = toHtmlRaw $ unlines $ map (ppcContent prettyConfigPP) (elContent elt)
-- where
-- elt = xmlOfDocument doc
--
-- instance ToHtml Tree where
-- toHtml = toHtmlRaw
-- toHtmlRaw tree = toHtmlRaw doc
-- where
-- doc = Document
-- { _viewBox = Nothing
-- , _width = Nothing
-- , _height = Nothing
-- , _elements = [tree]
-- , _definitions = Map.empty
-- , _description = ""
-- , _styleRules = []
-- , _documentLocation = ""
-- }
xelatex :: Text -> Tree
xelatex tex = (unsafePerformIO . (cacheMem . cacheDiskSvg) latexToSVG)
("% xelatex\n" <> tex)
{-# NOINLINE cache #-}
cache :: IORef (Map String Tree)
cache = unsafePerformIO (newIORef Map.empty)
latex :: String -> Tree
latex tex = unsafePerformIO $ do
store <- readIORef cache
case Map.lookup tex store of
Just svg -> return svg
Nothing -> do
svg <- latexToSVG tex
case svg of
None -> pure None
_ -> atomicModifyIORef cache (\store -> (Map.insert tex svg store, svg))
xelatex :: String -> Tree
xelatex tex = unsafePerformIO $ do
store <- readIORef cache
case Map.lookup tex store of
Just svg -> return svg
Nothing -> do
svg <- xelatexToSVG tex
case svg of
None -> pure None
_ -> atomicModifyIORef cache (\store -> (Map.insert tex svg store, svg))
latexAlign :: String -> Tree
latexAlign tex = latex $ unlines ["\\begin{align*}", tex, "\\end{align*}"]
latexAlign :: Text -> Tree
latexAlign tex = latex $ T.unlines ["\\begin{align*}", tex, "\\end{align*}"]
latexToSVG :: String -> IO Tree
latexToSVG :: Text -> IO Tree
latexToSVG tex = handle (\(e::SomeException) -> return (failedSvg tex)) $ do
latex <- requireExecutable "latex"
dvisvgm <- requireExecutable "dvisvgm"
@ -82,7 +44,7 @@ latexToSVG tex = handle (\(e::SomeException) -> return (failedSvg tex)) $ do
let dvi_file = tmp_dir </> replaceExtension (takeFileName tex_file) "dvi"
writeFile tex_file tex_document
appendFile tex_file tex_prologue
appendFile tex_file tex
T.appendFile tex_file tex
appendFile tex_file tex_epilogue
runCmd latex ["-interaction=batchmode", "-halt-on-error", "-output-directory="++tmp_dir, tex_file]
runCmd dvisvgm [ dvi_file
@ -95,7 +57,7 @@ latexToSVG tex = handle (\(e::SomeException) -> return (failedSvg tex)) $ do
Nothing -> error "Malformed svg"
Just svg -> return $ unbox $ replaceUses svg
xelatexToSVG :: String -> IO Tree
xelatexToSVG :: Text -> IO Tree
xelatexToSVG tex = handle (\(e::SomeException) -> return (failedSvg tex)) $ do
latex <- requireExecutable "xelatex"
dvisvgm <- requireExecutable "dvisvgm"
@ -104,7 +66,7 @@ xelatexToSVG tex = handle (\(e::SomeException) -> return (failedSvg tex)) $ do
writeFile tex_file tex_document
appendFile tex_file tex_xelatex
appendFile tex_file tex_prologue
appendFile tex_file tex
T.appendFile tex_file tex
appendFile tex_file tex_epilogue
runCmd latex ["-no-pdf", "-interaction=batchmode", "-halt-on-error", "-output-directory="++tmp_dir, tex_file]
runCmd dvisvgm [ dvi_file
@ -117,7 +79,7 @@ xelatexToSVG tex = handle (\(e::SomeException) -> return (failedSvg tex)) $ do
Nothing -> error "Malformed svg"
Just svg -> return $ unbox $ replaceUses svg
failedSvg :: String -> Tree
failedSvg :: Text -> Tree
failedSvg tex = defaultSvg
-- text_ [ font_size_ "20"
-- , fill_ "white"] (toHtml $ "bad latex: "++tex)

View file

@ -10,8 +10,7 @@ import Data.Monoid ((<>))
import Data.Text (Text, pack)
import Graphics.SvgTree (Document (..), Number (..), Text (..),
TextSpan (..), TextSpanContent (..),
Tree, Tree (..), xmlOfDocument)
import Reanimate.LaTeX
Tree, Tree (..), xmlOfDocument, xmlOfTree)
import Reanimate.Svg
import Text.XML.Light (elContent)
import Text.XML.Light.Output
@ -101,10 +100,10 @@ frameAt :: Double -> Animation -> Tree
frameAt t (Animation d (Frame f)) = mkGroup $ execState (f d (min d t)) id []
renderTree :: Tree -> String
renderTree = renderSizedTree Nothing Nothing
renderTree t = maybe "" ppElement $ xmlOfTree t
renderSizedTree :: Maybe Number -> Maybe Number -> Tree -> String
renderSizedTree w h t = ppElement $ xmlOfDocument doc
renderSvg :: Maybe Number -> Maybe Number -> Tree -> String
renderSvg w h t = ppElement $ xmlOfDocument doc
where
width = 320
height = width / (16/9)

View file

@ -25,7 +25,7 @@ renderSvgs ani = do
where
frames = [0..frameCount-1]
rate = 60
nthFrame nth = renderTree $ frameAt (recip (fromIntegral rate) * fromIntegral nth) ani
nthFrame nth = renderSvg Nothing Nothing $ frameAt (recip (fromIntegral rate) * fromIntegral nth) ani
frameCount = round (duration ani * fromIntegral rate) :: Int
nameTemplate :: String
nameTemplate = "render-%05d.svg"
@ -88,7 +88,7 @@ renderFormat format ani target = do
-- XXX: Use threads
generateFrames ani width_ rate action = withTempDir $ \tmp -> do
let frameName nth = tmp </> printf nameTemplate nth
rendered = [ renderSizedTree width height $ nthFrame n | n <- frames]
rendered = [ renderSvg width height $ nthFrame n | n <- frames]
`using` parBuffer 16 rdeepseq
forM_ (zip [0::Int ..] rendered) $ \(n, frame) -> do
writeFile (frameName n) frame

View file

@ -3,7 +3,7 @@ resolver: lts-11.22
allow-newer: false
extra-deps:
- reanimate-svg-0.8.1.0
- reanimate-svg-0.8.2.0
- diagrams-1.4@sha256:3e36369e84115b900fd9dcb570672a188339a470eb19ca62170775cd835cf8ca
- diagrams-contrib-1.4.3@sha256:bcfa6c85f8c33b8c48c3a61b7216afdebd51cd793c50da3a2dd358827d25fc76
- diagrams-core-1.4.1.1@sha256:6ef6b17785d77997c481eb085570e21b6a00cc91d086fbf49490504130ebc7d1

23
viewer/.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

@ -0,0 +1,13 @@
{
"main.css": "./static/css/main.6efe09fd.chunk.css",
"main.js": "./static/js/main.db22f45d.chunk.js",
"main.js.map": "./static/js/main.db22f45d.chunk.js.map",
"runtime~main.js": "./static/js/runtime~main.9eb600ee.js",
"runtime~main.js.map": "./static/js/runtime~main.9eb600ee.js.map",
"static/js/2.772a56e7.chunk.js": "./static/js/2.772a56e7.chunk.js",
"static/js/2.772a56e7.chunk.js.map": "./static/js/2.772a56e7.chunk.js.map",
"index.html": "./index.html",
"precache-manifest.15b8d497ab10704d87b84878b92d08cf.js": "./precache-manifest.15b8d497ab10704d87b84878b92d08cf.js",
"service-worker.js": "./service-worker.js",
"static/css/main.6efe09fd.chunk.css.map": "./static/css/main.6efe09fd.chunk.css.map"
}

BIN
viewer/build/favicon.ico Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

1
viewer/build/index.html Normal file
View file

@ -0,0 +1 @@
<!doctype html><html lang="en"><head><meta charset="utf-8"/><meta name="viewport" content="width=device-width,initial-scale=1,shrink-to-fit=no"/><meta name="theme-color" content="#000000"/><link rel="manifest" href="./manifest.json"/><title>Reanimate Playground</title><link href="./static/css/main.6efe09fd.chunk.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div><script>!function(l){function e(e){for(var r,t,n=e[0],o=e[1],u=e[2],f=0,i=[];f<n.length;f++)t=n[f],p[t]&&i.push(p[t][0]),p[t]=0;for(r in o)Object.prototype.hasOwnProperty.call(o,r)&&(l[r]=o[r]);for(s&&s(e);i.length;)i.shift()();return c.push.apply(c,u||[]),a()}function a(){for(var e,r=0;r<c.length;r++){for(var t=c[r],n=!0,o=1;o<t.length;o++){var u=t[o];0!==p[u]&&(n=!1)}n&&(c.splice(r--,1),e=f(f.s=t[0]))}return e}var t={},p={1:0},c=[];function f(e){if(t[e])return t[e].exports;var r=t[e]={i:e,l:!1,exports:{}};return l[e].call(r.exports,r,r.exports,f),r.l=!0,r.exports}f.m=l,f.c=t,f.d=function(e,r,t){f.o(e,r)||Object.defineProperty(e,r,{enumerable:!0,get:t})},f.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},f.t=function(r,e){if(1&e&&(r=f(r)),8&e)return r;if(4&e&&"object"==typeof r&&r&&r.__esModule)return r;var t=Object.create(null);if(f.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:r}),2&e&&"string"!=typeof r)for(var n in r)f.d(t,n,function(e){return r[e]}.bind(null,n));return t},f.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return f.d(r,"a",r),r},f.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},f.p="./";var r=window.webpackJsonp=window.webpackJsonp||[],n=r.push.bind(r);r.push=e,r=r.slice();for(var o=0;o<r.length;o++)e(r[o]);var s=n;a()}([])</script><script src="./static/js/2.772a56e7.chunk.js"></script><script src="./static/js/main.db22f45d.chunk.js"></script></body></html>

15
viewer/build/manifest.json Executable file
View file

@ -0,0 +1,15 @@
{
"short_name": "React App",
"name": "Create React App Sample",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}

View file

@ -0,0 +1,22 @@
self.__precacheManifest = [
{
"revision": "9eb600ee07a27cdad64f",
"url": "./static/js/runtime~main.9eb600ee.js"
},
{
"revision": "db22f45d36ec0c1e28c1",
"url": "./static/js/main.db22f45d.chunk.js"
},
{
"revision": "772a56e764e5091f7538",
"url": "./static/js/2.772a56e7.chunk.js"
},
{
"revision": "db22f45d36ec0c1e28c1",
"url": "./static/css/main.6efe09fd.chunk.css"
},
{
"revision": "8912fe90ce5c625516525c7cd80ee665",
"url": "./index.html"
}
];

View file

@ -0,0 +1,34 @@
/**
* Welcome to your Workbox-powered service worker!
*
* You'll need to register this file in your web app and you should
* disable HTTP caching for this file too.
* See https://goo.gl/nhQhGp
*
* The rest of the code is auto-generated. Please don't update this file
* directly; instead, make changes to your Workbox build configuration
* and re-run your build process.
* See https://goo.gl/2aRDsh
*/
importScripts("https://storage.googleapis.com/workbox-cdn/releases/3.6.3/workbox-sw.js");
importScripts(
"./precache-manifest.15b8d497ab10704d87b84878b92d08cf.js"
);
workbox.clientsClaim();
/**
* The workboxSW.precacheAndRoute() method efficiently caches and responds to
* requests for URLs in the manifest.
* See https://goo.gl/S9QRab
*/
self.__precacheManifest = [].concat(self.__precacheManifest || []);
workbox.precaching.suppressWarnings();
workbox.precaching.precacheAndRoute(self.__precacheManifest, {});
workbox.routing.registerNavigationRoute("./index.html", {
blacklist: [/^\/_/,/\/[^\/]+\.[^\/]+$/],
});

View file

@ -0,0 +1,2 @@
body{margin:0;padding:0;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}code{font-family:source-code-pro,Menlo,Monaco,Consolas,Courier New,monospace}.App{height:100vh;background-color:#282c34;color:#fff;display:grid;grid-template-columns:1fr;grid-template-rows:auto;grid-auto-flow:column;-webkit-align-items:center;align-items:center;overflow:hidden}.controls{text-align:center}.controls svg{padding-left:5px;padding-right:5px}.viewer{position:static}.viewer svg{max-width:100vw;max-height:100vh;margin-top:auto;margin-bottom:auto}div.messages{position:fixed;top:0;width:100%;background:#282c34}div.messages pre{margin:0}.home{color:#fff;text-align:center;float:right;margin-right:2em}
/*# sourceMappingURL=main.6efe09fd.chunk.css.map */

View file

@ -0,0 +1 @@
{"version":3,"sources":["/home/lemmih/Coding/Haskell/reanimate/viewer/src/index.css","main.6efe09fd.chunk.css","/home/lemmih/Coding/Haskell/reanimate/viewer/src/App.css"],"names":[],"mappings":"AAAA,KACE,QAAA,CACA,SAAA,CACA,mICEY,CDCZ,kCAAA,CACA,iCCCF,CDEA,KACE,uECEF,CCbA,KAGE,YAAA,CACA,wBAAA,CACA,UAAA,CACA,YAAA,CACA,yBAAA,CACA,uBAAA,CAEA,qBAAA,CACA,0BAAA,CAAA,kBAAA,CACA,eDiBF,CCZA,UACE,iBDiBF,CCfA,cACE,gBAAA,CACA,iBDiBF,CCfA,QACE,eDiBF,CCfA,YACE,eAAA,CACA,gBAAA,CACA,eAAA,CACA,kBDiBF,CCfA,aACE,cAAA,CACA,KAAA,CACA,UAAA,CACA,kBDiBF,CCfA,iBACE,QDiBF,CCfA,MACE,UAAA,CACA,iBAAA,CACA,WAAA,CACA,gBDiBF","file":"main.6efe09fd.chunk.css","sourcesContent":["body {\n margin: 0;\n padding: 0;\n font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", \"Roboto\", \"Oxygen\",\n \"Ubuntu\", \"Cantarell\", \"Fira Sans\", \"Droid Sans\", \"Helvetica Neue\",\n sans-serif;\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n}\n\ncode {\n font-family: source-code-pro, Menlo, Monaco, Consolas, \"Courier New\",\n monospace;\n}\n","body {\n margin: 0;\n padding: 0;\n font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", \"Roboto\", \"Oxygen\",\n \"Ubuntu\", \"Cantarell\", \"Fira Sans\", \"Droid Sans\", \"Helvetica Neue\",\n sans-serif;\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n}\n\ncode {\n font-family: source-code-pro, Menlo, Monaco, Consolas, \"Courier New\",\n monospace;\n}\n\n.App {\n /* text-align: center; */\n /* min-height: 100vh; */\n height: 100vh;\n background-color: #282c34;\n color: white;\n display: grid;\n grid-template-columns: 1fr;\n grid-template-rows: auto;\n /* grid-gap: 1em; */\n grid-auto-flow: column;\n -webkit-align-items: center;\n align-items: center;\n overflow: hidden;\n}\n.editor {\n /* min-width: 35em; */\n}\n.controls {\n text-align:center;\n}\n.controls svg {\n padding-left: 5px;\n padding-right: 5px;\n}\n.viewer {\n position: static;\n}\n.viewer svg {\n max-width: 100vw;\n max-height: 100vh;\n margin-top: auto;\n margin-bottom: auto;\n}\ndiv.messages {\n position: fixed;\n top: 0;\n width: 100%;\n background: #282c34;\n}\ndiv.messages pre {\n margin: 0;\n}\n.home {\n color: white;\n text-align: center;\n float:right;\n margin-right: 2em;\n}\n\n\n#editor {\n\n}\n\n",".App {\n /* text-align: center; */\n /* min-height: 100vh; */\n height: 100vh;\n background-color: #282c34;\n color: white;\n display: grid;\n grid-template-columns: 1fr;\n grid-template-rows: auto;\n /* grid-gap: 1em; */\n grid-auto-flow: column;\n align-items: center;\n overflow: hidden;\n}\n.editor {\n /* min-width: 35em; */\n}\n.controls {\n text-align:center;\n}\n.controls svg {\n padding-left: 5px;\n padding-right: 5px;\n}\n.viewer {\n position: static;\n}\n.viewer svg {\n max-width: 100vw;\n max-height: 100vh;\n margin-top: auto;\n margin-bottom: auto;\n}\ndiv.messages {\n position: fixed;\n top: 0;\n width: 100%;\n background: #282c34;\n}\ndiv.messages pre {\n margin: 0;\n}\n.home {\n color: white;\n text-align: center;\n float:right;\n margin-right: 2em;\n}\n\n\n#editor {\n\n}\n"]}

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1 @@
4078f6ee1053e716763e575de7ea78c199ad4bbd

View file

@ -0,0 +1,2 @@
(window.webpackJsonp=window.webpackJsonp||[]).push([[0],{10:function(e,n,t){e.exports=t(18)},16:function(e,n,t){},17:function(e,n,t){},18:function(e,n,t){"use strict";t.r(n);var s=t(0),a=t.n(s),o=t(4),r=t.n(o),i=(t(16),t(2)),c=t(5),l=t(6),m=t(9),g=t(7),u=t(1),d=t(8),v=(t(17),function(e){function n(e){var t;Object(c.a)(this,n),(t=Object(m.a)(this,Object(g.a)(n).call(this,e))).connect=function(){var e=new WebSocket("ws://localhost:9161");e.onopen=function(n){t.setState(function(e){return Object(i.a)({},e,{message:"Connected."})}),e.send("60")},e.onclose=function(e){t.setState(function(e){return Object(i.a)({},e,{message:"Disconnected."})}),setTimeout(t.connect,1e3)},e.onmessage=function(e){if("Success!"===e.data)console.log("Success");else if("Compiling"===e.data)t.setState({message:"Compiling..."});else if("Rendering"===e.data)t.setState({message:"Rendering..."}),t.nFrames_new=0,t.svgs_new=[];else if("Done"===e.data)t.setState({message:""}),console.log("Done"),t.nFrames=t.nFrames_new,t.svgs=t.svgs_new,t.nFrames_new=0,t.svgs_new=[],t.start=Date.now();else if(e.data.startsWith("Error"))console.log("Error"),t.setState({message:e.data.substring(5)});else{t.setState({message:"Rendering: ".concat(t.nFrames_new)}),t.nFrames_new++;var n=document.createElement("div");n.innerHTML=e.data,t.svgs_new.push(n)}},t.setState(function(n){return Object(i.a)({},n,{socket:e,message:"Connecting..."})})},t.onLoad=function(e){setTimeout(function(){e.resize()},0)},t.state={},setTimeout(t.connect,0),t.nFrames_new=0,t.svgs_new=[],t.nFrames=0,t.svgs=[],t.start=Date.now();var s=Object(u.a)(t);return requestAnimationFrame(function e(){var n=Date.now(),a=s.nFrames,o=Math.round((n-t.start)/1e3*60)%a;if(s.svgs_new.length){for(;s.svg.firstChild;)s.svg.removeChild(s.svg.firstChild);s.svg.appendChild(s.svgs_new[s.svgs_new.length-1])}else if(a){for(;s.svg.firstChild;)s.svg.removeChild(s.svg.firstChild);s.svg.appendChild(s.svgs[o])}else s.svg.innerText="";requestAnimationFrame(e)}),t}return Object(d.a)(n,e),Object(l.a)(n,[{key:"render",value:function(){var e=this,n=this.state.message;return a.a.createElement("div",{className:"App"},a.a.createElement("div",{className:"viewer"},a.a.createElement("div",{ref:function(n){return e.svg=n}}),a.a.createElement("div",{className:"messages"},a.a.createElement("pre",null,n))))}}]),n}(s.Component));Boolean("localhost"===window.location.hostname||"[::1]"===window.location.hostname||window.location.hostname.match(/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/));r.a.render(a.a.createElement(v,null),document.getElementById("root")),"serviceWorker"in navigator&&navigator.serviceWorker.ready.then(function(e){e.unregister()})}},[[10,1,2]]]);
//# sourceMappingURL=main.db22f45d.chunk.js.map

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,2 @@
!function(e){function r(r){for(var n,f,i=r[0],l=r[1],a=r[2],c=0,s=[];c<i.length;c++)f=i[c],o[f]&&s.push(o[f][0]),o[f]=0;for(n in l)Object.prototype.hasOwnProperty.call(l,n)&&(e[n]=l[n]);for(p&&p(r);s.length;)s.shift()();return u.push.apply(u,a||[]),t()}function t(){for(var e,r=0;r<u.length;r++){for(var t=u[r],n=!0,i=1;i<t.length;i++){var l=t[i];0!==o[l]&&(n=!1)}n&&(u.splice(r--,1),e=f(f.s=t[0]))}return e}var n={},o={1:0},u=[];function f(r){if(n[r])return n[r].exports;var t=n[r]={i:r,l:!1,exports:{}};return e[r].call(t.exports,t,t.exports,f),t.l=!0,t.exports}f.m=e,f.c=n,f.d=function(e,r,t){f.o(e,r)||Object.defineProperty(e,r,{enumerable:!0,get:t})},f.r=function(e){"undefined"!==typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},f.t=function(e,r){if(1&r&&(e=f(e)),8&r)return e;if(4&r&&"object"===typeof e&&e&&e.__esModule)return e;var t=Object.create(null);if(f.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:e}),2&r&&"string"!=typeof e)for(var n in e)f.d(t,n,function(r){return e[r]}.bind(null,n));return t},f.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return f.d(r,"a",r),r},f.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},f.p="./";var i=window.webpackJsonp=window.webpackJsonp||[],l=i.push.bind(i);i.push=r,i=i.slice();for(var a=0;a<i.length;a++)r(i[a]);var p=l;t()}([]);
//# sourceMappingURL=runtime~main.9eb600ee.js.map

File diff suppressed because one or more lines are too long

View file

@ -1 +1 @@
c3337c953a23ef5eb5f30ed154dd591fe5a8645e
6c7a4342c0b1d2c4a2a8b83b5ecc53ee6d1b6622

View file

@ -2,10 +2,9 @@
"name": "viewer",
"version": "0.1.0",
"private": true,
"homepage": "https://lemmih.github.io/reanimate",
"homepage": ".",
"dependencies": {
"react": "^16.8.2",
"react-ace": "^6.4.0",
"react-dom": "^16.8.2",
"react-scripts": "2.1.5"
},

View file

@ -1,11 +1,5 @@
import React, {Component} from 'react';
import './App.css';
import AceEditor from 'react-ace';
import 'brace/mode/haskell';
import 'brace/theme/github';
import 'brace/theme/monokai';
import preset from './Presets';
class App extends Component {
connect = () => {
@ -64,7 +58,6 @@ class App extends Component {
super(props);
this.state = {
program: preset[0].programs[0].code
};
setTimeout(this.connect, 0);
this.nFrames_new = 0;
@ -101,27 +94,8 @@ class App extends Component {
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;
const {message} = this.state;
return (
<div className="App">
<div className="viewer">

View file

@ -1,217 +0,0 @@
const latex_draw =
`animation :: Animation
animation =
bg \`sim\` (autoReverse $ drawText \`andThen\` fillText)
where
bg = mkAnimation 0 $ emit (mkBackground "black")
msg = "\\\\sum_{k=1}^\\\\infty {1 \\\\over k^2} = {\\\\pi^2 \\\\over 6}"
glyphs = center $ latexAlign msg
fillText = mkAnimation 1 $ do
s <- signal 0 1
emit $ scale 5 $ withFillColor "white" $ withFillOpacity s glyphs
drawText = mkAnimation 2 $ do
s <- signal 0 1
emit $ scale 5 $
withStrokeColor "white" $ withFillOpacity 0 $ withStrokeWidth (Num 0.1) $
partialSvg s glyphs`;
const bbox =
`animation :: Animation
animation = bg \`sim\`
mapA (translate (-50) 0) bbox1 \`sim\`
mapA (translate 50 0) bbox2
where
bg = mkAnimation 0 $ emit $ mkBackground "black"
bbox1 :: Animation
bbox1 = mkAnimation 5 $ do
s <- signal 0 1
emit $ mkGroup
[ mkBoundingBox $ rotate (360*s) svg
, withFillColor "white" $ rotate (360*s) svg ]
where
svg = scale 3 $ center $ latexAlign "\\\\sum_{k=1}^\\\\infty"
bbox2 :: Animation
bbox2 = autoReverse $ mkAnimation 2.5 $ do
s <- signal 0 1
emit $ mkGroup
[ mkBoundingBox $ partialSvg s heartShape
, 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.0a20.0 20.0 90.0 0 1 0.0,40.0a20.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 100 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 :: Animation
animation = autoReverse $ mkAnimation 2.5 $ do
morph <- signal 0 1
emit $ mkBackground "black"
emit $ withStrokeColor "white" $ translate (-320/2) (-180/2) $ mkGroup
[ translate 30 50 $ mkLinePath wave1
, translate 30 130 $ mkLinePath wave2
, translate 30 90 $ mkLinePath $ 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 100 $ \\idx -> (idx*width, sin (idx*pi*2*freq) * 20)
wave2 = approxFnData 100 $ \\idx -> (idx*width, sin (idx*pi*2*(freq*3)) * 20)`;
const morph_wave_circle =
`animation :: Animation
animation = autoReverse $ mkAnimation 2.5 $ do
idx <- signal 0 1
emit $ mkBackground "black"
emit $ withStrokeColor "white" $ translate (-320/2) (-180/2) $ mkGroup
[ translate 30 90 $ mkLinePath $ 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 :: Animation
animation =
bg \`sim\` labels \`sim\`
mapA (translate (-100) 0) (adjustSpeed 1.0 progressMeter) \`simLoop\`
mapA (translate 0 0) (adjustSpeed 2.0 progressMeter) \`simLoop\`
mapA (translate 100 0) (adjustSpeed 0.5 progressMeter)
where
bg = mkAnimation 0 $ emit $ mkBackground "black"
labels = mkAnimation 0 $ emit $ translate 0 70 $ withFillColor "white" $ mkGroup
[ translate (-100) 0 $ scale 2 $ center $ latex "1x"
, translate 0 0 $ scale 2 $ center $ latex "2x"
, translate 100 0 $ scale 2 $ center $ latex "0.5x"
]
progressMeter :: Animation
progressMeter = mkAnimation 3 $ do
h <- signal 0 100
emit $ center $ mkGroup
[ withStrokeColor "white" $ withStrokeWidth (Num 2) $ withFillOpacity 0 $
mkRect (Num 0, Num 0) (Num 30) (Num 100)
, withFillColor "white" $
mkRect (Num 0, Num 0) (Num 30) (Num h) ]`
const latex_basic =
`animation :: Animation
animation = autoReverse $ mkAnimation 2 $ do
s <- signal 0 1
emit $ mkGroup
[ mkBackground "black"
, 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 :: Animation
animation = mkAnimation 1 $ do
emit $ mkBackground "black"
emit $ 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"`;
const valentine =
`animation :: Animation
animation =
all_red \`before\`
( background \`sim\`
(backgroundDelay \`before\`
foldr1 sim [ pause p \`before\` fallingLove p x | (p, x) <- falling ]
) \`sim\`
(heart_ani \`before\` heart_disappear) \`sim\`
(pause 1 \`before\` repeatAnimation 10 (message ai))
)
where
falling = [(6.4, 0.09), (4.9, 0.12), (4.5, 0.88), (0.3, 0.43), (5.3, 0.93)
,(0.1, 0.80), (1.1, 0.39), (2.3, 0.21), (2.9, 0.77), (3.4, 0.46)
,(6.2, 0.88), (5.9, 0.80), (3.2, 0.14), (7.7, 0.99), (3.4, 0.35)
,(0.4, 0.51), (7.1, 0.60), (7.7, 0.65)]
ai = center $ xelatex "爱"
all_red = mkAnimation 1 $ emit $ mkBackground "red"
background = mkAnimation 2 $ do
n <- round <$> signal 0 0xFF
emit $ mkBackgroundPixel $ PixelRGBA8 0xFF n n 0xFF
backgroundDelay = pause (duration background-1)
heart_ani = repeatAnimation 10 $ mkAnimation 1 $ do
n <- oscillate $ signalSCurve 2 0.9 1.1
mapF (scale n) $ drawHeart
heart_disappear = mkAnimation 3 $ do
n <- signal 0.9 10
o <- oscillate $ signal 0 1.5
mapF (scale n) drawHeart
mapF (scale (n*4)) $ emit $ withFillOpacity o $ withFillColor "white" ai
fallingLove rand xPos = mkAnimation 2 $ do
n <- signal (-100) 90
o <- signal 0 5
emit $ withStrokeColor "black" $ withFillColor "red" $
translate ((xPos*2-1)*(320/2)) n $ scale 0.3 $ rotate (60*(o+rand+xPos)) heartShape
message txt = mkAnimation 1 $ do
o <- oscillate $ signal 0 1
n <- oscillate $ signalSCurve 2 0.9 1.1
emit $ scale n $ scale 2 $ withFillColor "white" $ withFillOpacity o txt
drawHeart = emit $ withFillColor "red" $ heartShape
heartShape =
center $ rotateAroundCenter 225 $ mkPathString
"M0.0,40.0 v-40.0 h40.0a20.0 20.0 90.0 0 1 0.0,40.0a20.0 20.0 90.0 0 1 -40.0,0.0 Z"`;
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: "Valentine", code: valentine }
// , {name: "Highlight", code: highlight }
]
},
];