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 #!/usr/bin/env stack
-- stack --resolver lts-11.22 runghc --package reanimate -- stack --resolver lts-11.22 runghc --package reanimate
{-# LANGUAGE OverloadedStrings #-}
module Main (main) where module Main (main) where
import Control.Lens import Control.Lens

View file

@ -13,6 +13,14 @@ build-type: Simple
extra-source-files: ChangeLog.md extra-source-files: ChangeLog.md
cabal-version: >=1.10 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 library
hs-source-dirs: src hs-source-dirs: src
default-language: Haskell2010 default-language: Haskell2010
@ -28,12 +36,15 @@ library
Reanimate.Driver Reanimate.Driver
Reanimate.Misc Reanimate.Misc
other-modules: Reanimate.Svg.NamedColors other-modules: Reanimate.Svg.NamedColors
Reanimate.Cache
Paths_reanimate
build-depends: base >=4.10 && <4.13, build-depends: base >=4.10 && <4.13,
time, text, unix, filepath, process, directory, time, text, unix, filepath, process, directory,
containers, reanimate-svg >= 0.7.0.0, xml, bytestring, lens, linear, mtl, matrix, containers, reanimate-svg >= 0.7.0.0, xml, bytestring, lens, linear, mtl, matrix,
JuicyPixels, attoparsec, parallel, diagrams, diagrams-svg, JuicyPixels, attoparsec, parallel, diagrams, diagrams-svg,
diagrams-core, diagrams-lib, diagrams-contrib, diagrams-core, diagrams-lib, diagrams-contrib,
svg-builder, matrices, cubicbezier, palette, hinotify, websockets svg-builder, matrices, cubicbezier, palette, hinotify, websockets,
hashable
Flag gtk-viewer Flag gtk-viewer
Description: Enable gtk-based 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, import System.IO (BufferMode (..), hPutStrLn, hSetBuffering,
stderr, stdin) stderr, stdin)
import Reanimate.Misc (runCmdLazy, runCmd_, withTempFile) import Reanimate.Misc (runCmdLazy, runCmd, runCmd_, withTempDir, withTempFile)
import Reanimate.Monad (Animation) import Reanimate.Monad (Animation)
import Reanimate.Render (renderSvgs) import Reanimate.Render (renderSvgs)
import Paths_reanimate
opts = defaultConnectionOptions opts = defaultConnectionOptions
{ connectionCompressionOptions = PermessageDeflateCompression defaultPermessageDeflate } { connectionCompressionOptions = PermessageDeflateCompression defaultPermessageDeflate }
@ -24,50 +26,55 @@ reanimate animation = do
hSetBuffering stdin NoBuffering hSetBuffering stdin NoBuffering
case args of case args of
["once"] -> renderSvgs animation ["once"] -> renderSvgs animation
_ -> runServerWith "127.0.0.1" 9161 opts $ \pending -> do _ -> withTempDir $ \tmpDir -> do
putStrLn "Server pending." url <- getDataFileName "viewer/build/index.html"
prog <- getProgName runCmd "xdg-open" [url]
lst <- listDirectory "." runServerWith "127.0.0.1" 9161 opts $ \pending -> do
mbSelf <- findFile ("." : lst) prog putStrLn "Server pending."
blocker <- newEmptyMVar :: IO (MVar ()) prog <- getProgName
case mbSelf of lst <- listDirectory "."
Nothing -> do mbSelf <- findFile ("." : lst) prog
hPutStrLn stderr "Failed to find own source code." blocker <- newEmptyMVar :: IO (MVar ())
Just self -> withINotify $ \notify -> do case mbSelf of
conn <- acceptRequest pending Nothing -> do
slave <- newEmptyMVar hPutStrLn stderr "Failed to find own source code."
let handler = modifyMVar_ slave $ \tid -> do Just self -> withINotify $ \notify -> do
sendTextData conn (T.pack "Compiling") conn <- acceptRequest pending
putStrLn "Kill and respawn." slave <- newEmptyMVar
killThread tid let handler = modifyMVar_ slave $ \tid -> do
tid <- forkIO $ withTempFile ".exe" $ \tmpExecutable -> do sendTextData conn (T.pack "Compiling")
ret <- runCmd_ "stack" $ ["ghc", "--"] ++ ghcOptions ++ [self, "-o", tmpExecutable] putStrLn "Kill and respawn."
case ret of killThread tid
Left err -> tid <- forkIO $ withTempFile ".exe" $ \tmpExecutable -> do
sendTextData conn $ T.pack $ "Error" ++ unlines (drop 3 (lines err)) ret <- runCmd_ "stack" $ ["ghc", "--"] ++ ghcOptions tmpDir ++ [self, "-o", tmpExecutable]
Right{} -> do case ret of
getFrame <- runCmdLazy tmpExecutable ["once", "+RTS", "-N", "-M200M", "-RTS"] Left err ->
flip fix [] $ \loop acc -> do sendTextData conn $ T.pack $ "Error" ++ unlines (drop 3 (lines err))
frame <- getFrame Right{} -> do
case frame of getFrame <- runCmdLazy tmpExecutable ["once", "+RTS", "-N", "-M200M", "-RTS"]
Left "" -> do flip fix [] $ \loop acc -> do
sendTextData conn (T.pack "Done") frame <- getFrame
-- insertCache msg (reverse acc) case frame of
Left err -> do Left "" -> do
-- _ <- getChanContents queue sendTextData conn (T.pack "Done")
sendTextData conn $ T.pack $ "Error" ++ err -- insertCache msg (reverse acc)
Right frame -> do Left err -> do
sendTextData conn frame -- _ <- getChanContents queue
loop (frame : acc) sendTextData conn $ T.pack $ "Error" ++ err
return tid Right frame -> do
putStrLn "Found self. Listening." sendTextData conn frame
addWatch notify [Modify] self (const handler) loop (frame : acc)
putMVar slave =<< forkIO (return ()) return tid
let loop = do putStrLn "Found self. Listening."
fps <- receiveData conn :: IO T.Text addWatch notify [Modify] self (const handler)
handler putMVar slave =<< forkIO (return ())
loop let loop = do
loop fps <- receiveData conn :: IO T.Text
handler
loop
loop
ghcOptions :: [String] ghcOptions :: FilePath -> [String]
ghcOptions = ["-rtsopts", "--make", "-threaded", "-O2"] 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.IORef
import Data.Map (Map) import Data.Map (Map)
import qualified Data.Map as Map import qualified Data.Map as Map
import Data.Monoid
import Reanimate.Cache
import Reanimate.Misc import Reanimate.Misc
import Reanimate.Svg import Reanimate.Svg
import System.FilePath (replaceExtension, takeFileName, (</>)) import System.FilePath (replaceExtension, takeFileName, (</>))
import System.IO.Unsafe (unsafePerformIO) import System.IO.Unsafe (unsafePerformIO)
import Control.Lens (over, set, (%~), (&), (.~), (^.)) 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, import Graphics.SvgTree (Document (..), Tree (..), defaultSvg,
elements, loadSvgFile, parseSvgFile, elements, loadSvgFile, parseSvgFile,
xmlOfDocument) xmlOfDocument)
import Text.XML.Light (elContent) import Text.XML.Light (elContent)
import Text.XML.Light.Output (ppcContent, ppcElement, prettyConfigPP) import Text.XML.Light.Output (ppcContent, ppcElement, prettyConfigPP)
-- instance ToHtml Document where latex :: T.Text -> Tree
-- toHtml = toHtmlRaw latex tex = (unsafePerformIO . (cacheMem . cacheDiskSvg) latexToSVG)
-- toHtmlRaw = toHtmlRaw . ppcElement prettyConfigPP . xmlOfDocument ("% plain latex\n" <> tex)
-- instance ToHtml Document where xelatex :: Text -> Tree
-- toHtml = toHtmlRaw xelatex tex = (unsafePerformIO . (cacheMem . cacheDiskSvg) latexToSVG)
-- toHtmlRaw doc = toHtmlRaw $ unlines $ map (ppcContent prettyConfigPP) (elContent elt) ("% xelatex\n" <> tex)
-- 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 = ""
-- }
{-# NOINLINE cache #-} latexAlign :: Text -> Tree
cache :: IORef (Map String Tree) latexAlign tex = latex $ T.unlines ["\\begin{align*}", tex, "\\end{align*}"]
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*}"]
latexToSVG :: String -> IO Tree latexToSVG :: Text -> IO Tree
latexToSVG tex = handle (\(e::SomeException) -> return (failedSvg tex)) $ do latexToSVG tex = handle (\(e::SomeException) -> return (failedSvg tex)) $ do
latex <- requireExecutable "latex" latex <- requireExecutable "latex"
dvisvgm <- requireExecutable "dvisvgm" 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" let dvi_file = tmp_dir </> replaceExtension (takeFileName tex_file) "dvi"
writeFile tex_file tex_document writeFile tex_file tex_document
appendFile tex_file tex_prologue appendFile tex_file tex_prologue
appendFile tex_file tex T.appendFile tex_file tex
appendFile tex_file tex_epilogue appendFile tex_file tex_epilogue
runCmd latex ["-interaction=batchmode", "-halt-on-error", "-output-directory="++tmp_dir, tex_file] runCmd latex ["-interaction=batchmode", "-halt-on-error", "-output-directory="++tmp_dir, tex_file]
runCmd dvisvgm [ dvi_file runCmd dvisvgm [ dvi_file
@ -95,7 +57,7 @@ latexToSVG tex = handle (\(e::SomeException) -> return (failedSvg tex)) $ do
Nothing -> error "Malformed svg" Nothing -> error "Malformed svg"
Just svg -> return $ unbox $ replaceUses svg Just svg -> return $ unbox $ replaceUses svg
xelatexToSVG :: String -> IO Tree xelatexToSVG :: Text -> IO Tree
xelatexToSVG tex = handle (\(e::SomeException) -> return (failedSvg tex)) $ do xelatexToSVG tex = handle (\(e::SomeException) -> return (failedSvg tex)) $ do
latex <- requireExecutable "xelatex" latex <- requireExecutable "xelatex"
dvisvgm <- requireExecutable "dvisvgm" dvisvgm <- requireExecutable "dvisvgm"
@ -104,7 +66,7 @@ xelatexToSVG tex = handle (\(e::SomeException) -> return (failedSvg tex)) $ do
writeFile tex_file tex_document writeFile tex_file tex_document
appendFile tex_file tex_xelatex appendFile tex_file tex_xelatex
appendFile tex_file tex_prologue appendFile tex_file tex_prologue
appendFile tex_file tex T.appendFile tex_file tex
appendFile tex_file tex_epilogue appendFile tex_file tex_epilogue
runCmd latex ["-no-pdf", "-interaction=batchmode", "-halt-on-error", "-output-directory="++tmp_dir, tex_file] runCmd latex ["-no-pdf", "-interaction=batchmode", "-halt-on-error", "-output-directory="++tmp_dir, tex_file]
runCmd dvisvgm [ dvi_file runCmd dvisvgm [ dvi_file
@ -117,7 +79,7 @@ xelatexToSVG tex = handle (\(e::SomeException) -> return (failedSvg tex)) $ do
Nothing -> error "Malformed svg" Nothing -> error "Malformed svg"
Just svg -> return $ unbox $ replaceUses svg Just svg -> return $ unbox $ replaceUses svg
failedSvg :: String -> Tree failedSvg :: Text -> Tree
failedSvg tex = defaultSvg failedSvg tex = defaultSvg
-- text_ [ font_size_ "20" -- text_ [ font_size_ "20"
-- , fill_ "white"] (toHtml $ "bad latex: "++tex) -- , fill_ "white"] (toHtml $ "bad latex: "++tex)

View file

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

View file

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

View file

@ -3,7 +3,7 @@ resolver: lts-11.22
allow-newer: false allow-newer: false
extra-deps: extra-deps:
- reanimate-svg-0.8.1.0 - reanimate-svg-0.8.2.0
- diagrams-1.4@sha256:3e36369e84115b900fd9dcb570672a188339a470eb19ca62170775cd835cf8ca - diagrams-1.4@sha256:3e36369e84115b900fd9dcb570672a188339a470eb19ca62170775cd835cf8ca
- diagrams-contrib-1.4.3@sha256:bcfa6c85f8c33b8c48c3a61b7216afdebd51cd793c50da3a2dd358827d25fc76 - diagrams-contrib-1.4.3@sha256:bcfa6c85f8c33b8c48c3a61b7216afdebd51cd793c50da3a2dd358827d25fc76
- diagrams-core-1.4.1.1@sha256:6ef6b17785d77997c481eb085570e21b6a00cc91d086fbf49490504130ebc7d1 - 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", "name": "viewer",
"version": "0.1.0", "version": "0.1.0",
"private": true, "private": true,
"homepage": "https://lemmih.github.io/reanimate", "homepage": ".",
"dependencies": { "dependencies": {
"react": "^16.8.2", "react": "^16.8.2",
"react-ace": "^6.4.0",
"react-dom": "^16.8.2", "react-dom": "^16.8.2",
"react-scripts": "2.1.5" "react-scripts": "2.1.5"
}, },

View file

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