linden (empty) → 0
raw patch · 32 files changed
+2897/−0 lines, 32 filesdep +SHAdep +aesondep +basesetup-changedbinary-added
Dependencies added: SHA, aeson, base, bifunctors, bytestring, containers, deepseq, delay, exceptions, filepath, hashable, hedis, http-types, lens, linden, monad-supply, mtl, random-fu, rosezipper, text, time, unordered-containers, uuid, wai, wai-app-static, wai-cors, wai-extra, warp, zlib
Files
- LICENSE +30/−0
- Linden.hs +52/−0
- Setup.hs +2/−0
- art/add-light.png binary
- art/add-light.svg +257/−0
- art/lamp-45.png binary
- art/light.png binary
- art/loading.png binary
- art/remove.png binary
- art/remove.svg +86/−0
- assets/branch.png binary
- assets/f235a44f2c511cdd.json +1/−0
- client/build/index.html +10/−0
- client/build/linden.js +25/−0
- client/package.json +44/−0
- client/src/GardenClient.js +251/−0
- client/src/ImgCache.js +62/−0
- client/src/Linden.js +794/−0
- client/src/index.js +34/−0
- client/src/index.less +166/−0
- client/src/randomWind.js +19/−0
- client/src/walkTree.js +12/−0
- client/webpack.config.js +35/−0
- extract_grips.py +142/−0
- linden.cabal +87/−0
- src/Linden/RuleDSL.hs +261/−0
- src/Linden/Server.hs +152/−0
- src/Linden/Store/Memory.hs +37/−0
- src/Linden/Store/Redis.hs +75/−0
- src/Linden/TH.hs +8/−0
- src/Linden/Types.hs +255/−0
- tiff/branch.tif binary
+ LICENSE view
@@ -0,0 +1,30 @@+Copyright (c) 2016, davean++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++ * Redistributions of source code must retain the above copyright+ notice, this list of conditions and the following disclaimer.++ * Redistributions in binary form must reproduce the above+ copyright notice, this list of conditions and the following+ disclaimer in the documentation and/or other materials provided+ with the distribution.++ * Neither the name of davean nor the names of other+ contributors may be used to endorse or promote products derived+ from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ Linden.hs view
@@ -0,0 +1,52 @@+{-# LANGUAGE OverloadedStrings #-}+module Main where++import Data.Text (Text)+import Data.Tree+import Control.Monad+import Control.Monad.Trans+import Control.Monad.Reader+import Data.Random.Distribution.Normal+import Data.Hashable (hash)++import qualified Network.Wai.Handler.Warp as Warp++import Linden.Types+import Linden.RuleDSL+import Linden.Server+import Linden.Store.Memory++import Paths_linden++rule :: ReaderT FilePath IO Rule+rule = do+ basicBranch <- readBranchSym [hash ("basic"::Text)] "branch"+ return . compileRules . noDup $ do+ b <- curBranch+ --cs <- offspring <$> ask+ let s = bScale b+ angSum <- angleSum+ ls <- lightCones+ doNothing 100 -- Doing nothing is always an option+ when (not . null $ ls) $ do+ when (s==1) $ do+ addBranch 50 basicBranch 0 tscl 1+ when (s==tscl) $ do+ addBranch 50 basicBranch 0 tscl 1+ a <- lift . lift $ normal 20 10+ when (angSum < 180) $ addBranch 1 basicBranch a tscl 1+ when (angSum > -180) $ addBranch 1 basicBranch (-a) tscl 1+ where+ tscl = 1++axiom :: AxiomSource+axiom u t =+ return . LS u t 2 [Light 1 (0, 300) (-90) 90 0.7] .+ Just . PrettyJSTree $ Node (Branch 0 (Just "branch.png") (2, 0) (2, 19) 0 1 True True (map hash ["basic"::Text]) Nothing 0.5) []++main :: IO ()+main = do+ bp <- getDataDir+ ms <- memStore+ rule' <- return <$> runReaderT rule bp+ Warp.runEnv 8080 $ lindenApp bp ms "87fhn34wdfd2" 5.0 axiom rule' (return "")
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ art/add-light.png view
binary file changed (absent → 560 bytes)
+ art/add-light.svg view
@@ -0,0 +1,257 @@+<?xml version="1.0" encoding="UTF-8" standalone="no"?>+<!-- Created with Inkscape (http://www.inkscape.org/) -->++<svg+ xmlns:dc="http://purl.org/dc/elements/1.1/"+ xmlns:cc="http://creativecommons.org/ns#"+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"+ xmlns:svg="http://www.w3.org/2000/svg"+ xmlns="http://www.w3.org/2000/svg"+ xmlns:xlink="http://www.w3.org/1999/xlink"+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"+ width="40"+ height="40"+ viewBox="0 0 39.999999 39.999999"+ id="svg2"+ version="1.1"+ inkscape:version="0.91 r13725"+ sodipodi:docname="add-light.svg"+ inkscape:export-filename="/home/chromakode/dev/xkcd/2016/art/add-light.png"+ inkscape:export-xdpi="180"+ inkscape:export-ydpi="180">+ <defs+ id="defs4" />+ <sodipodi:namedview+ id="base"+ pagecolor="#ffffff"+ bordercolor="#666666"+ borderopacity="1.0"+ inkscape:pageopacity="0"+ inkscape:pageshadow="2"+ inkscape:zoom="22.627417"+ inkscape:cx="20.870016"+ inkscape:cy="22.55294"+ inkscape:document-units="px"+ inkscape:current-layer="layer1"+ showgrid="true"+ units="px"+ borderlayer="true"+ inkscape:window-width="2560"+ inkscape:window-height="1440"+ inkscape:window-x="0"+ inkscape:window-y="0"+ inkscape:window-maximized="0">+ <inkscape:grid+ type="xygrid"+ id="grid4324" />+ </sodipodi:namedview>+ <metadata+ id="metadata7">+ <rdf:RDF>+ <cc:Work+ rdf:about="">+ <dc:format>image/svg+xml</dc:format>+ <dc:type+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />+ <dc:title></dc:title>+ </cc:Work>+ </rdf:RDF>+ </metadata>+ <g+ inkscape:label="Layer 1"+ inkscape:groupmode="layer"+ id="layer1"+ transform="translate(0,-1012.3622)">+ <rect+ style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"+ id="rect4203"+ width="12.931219"+ height="3.4205158"+ x="3.017005"+ y="1041.5504" />+ <rect+ style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"+ id="rect4203-3"+ width="12.931219"+ height="3.4205158"+ x="1036.7952"+ y="-11.192872"+ transform="matrix(0,1,-1,0,0,0)" />+ <image+ y="820.42047"+ x="530.83942"+ id="image4321"+ xlink:href="data:image/png;base64,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"+ preserveAspectRatio="none"+ height="61.498058"+ width="35.114033"+ transform="matrix(0.85827999,0.51318171,-0.51318171,0.85827999,0,0)" />+ </g>+</svg>
+ art/lamp-45.png view
binary file changed (absent → 9447 bytes)
+ art/light.png view
binary file changed (absent → 1804 bytes)
+ art/loading.png view
binary file changed (absent → 1479 bytes)
+ art/remove.png view
binary file changed (absent → 365 bytes)
+ art/remove.svg view
@@ -0,0 +1,86 @@+<?xml version="1.0" encoding="UTF-8" standalone="no"?>+<!-- Created with Inkscape (http://www.inkscape.org/) -->++<svg+ xmlns:dc="http://purl.org/dc/elements/1.1/"+ xmlns:cc="http://creativecommons.org/ns#"+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"+ xmlns:svg="http://www.w3.org/2000/svg"+ xmlns="http://www.w3.org/2000/svg"+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"+ width="40"+ height="40"+ viewBox="0 0 39.999999 39.999999"+ id="svg2"+ version="1.1"+ inkscape:version="0.91 r13725"+ sodipodi:docname="remove.svg"+ inkscape:export-filename="/home/chromakode/dev/xkcd/2016/art/remove.png"+ inkscape:export-xdpi="180"+ inkscape:export-ydpi="180">+ <defs+ id="defs4" />+ <sodipodi:namedview+ id="base"+ pagecolor="#ffffff"+ bordercolor="#666666"+ borderopacity="1.0"+ inkscape:pageopacity="0"+ inkscape:pageshadow="2"+ inkscape:zoom="22.627417"+ inkscape:cx="11.854404"+ inkscape:cy="18.159802"+ inkscape:document-units="px"+ inkscape:current-layer="layer1"+ showgrid="true"+ units="px"+ borderlayer="true"+ inkscape:window-width="2560"+ inkscape:window-height="1440"+ inkscape:window-x="0"+ inkscape:window-y="0"+ inkscape:window-maximized="0">+ <inkscape:grid+ type="xygrid"+ id="grid4324" />+ </sodipodi:namedview>+ <metadata+ id="metadata7">+ <rdf:RDF>+ <cc:Work+ rdf:about="">+ <dc:format>image/svg+xml</dc:format>+ <dc:type+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />+ <dc:title></dc:title>+ </cc:Work>+ </rdf:RDF>+ </metadata>+ <g+ inkscape:label="Layer 1"+ inkscape:groupmode="layer"+ id="layer1"+ transform="translate(0,-1012.3622)">+ <g+ id="g4908"+ transform="matrix(0.8958018,0.8958018,-0.8958018,0.8958018,940.87405,84.126685)"+ style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-opacity:1">+ <rect+ y="1041.5504"+ x="3.017005"+ height="3.4205158"+ width="24.510092"+ id="rect4203"+ style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" />+ <rect+ transform="matrix(0,1,-1,0,0,0)"+ y="-16.982309"+ x="1031.0056"+ height="3.4205158"+ width="24.510092"+ id="rect4203-1"+ style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" />+ </g>+ </g>+</svg>
+ assets/branch.png view
binary file changed (absent → 33 bytes)
+ assets/f235a44f2c511cdd.json view
@@ -0,0 +1,1 @@+{"attach": [], "root": {"y": 1, "x": 13}}
+ client/build/index.html view
@@ -0,0 +1,10 @@+<!doctype html>+<html>+<head>+<meta name="viewport" content="width=device-width, initial-scale=1">+</head>+<body>+<div id="comic"></div>+<script src="linden.js" data-endpoint="/" data-image-endpoint="/art/" data-title="title text"></script>+</body>+</html>
+ client/build/linden.js view
@@ -0,0 +1,25 @@+/*! linden by chromako.de */+!function(t){function e(i){if(n[i])return n[i].exports;var o=n[i]={exports:{},id:i,loaded:!1};return t[i].call(o.exports,o,o.exports,e),o.loaded=!0,o.exports}var n={};return e.m=t,e.c=n,e.p="",e(0)}([function(t,e,n){n(55),n(53),n(52),n(51),n(54),n(50),n(109),n(103);var i=n(45),o=document.getElementsByTagName("script"),r=o[o.length-1];window.garden=new i({name:"garden",el:document.getElementById("comic"),width:740,height:462,title:r.getAttribute("data-title"),endpoint:r.getAttribute("data-endpoint"),imgPrefix:r.getAttribute("data-image-endpoint"),spread:{reload:60},debug:"?debug"===location.search}).run(),window.addEventListener("keydown",function(t){8===t.which&&t.preventDefault()},!1)},function(t,e,n){var i=n(40)("wks"),o=n(29),r=n(2).Symbol,s="function"==typeof r;t.exports=function(t){return i[t]||(i[t]=s&&r[t]||(s?r:o)("Symbol."+t))}},function(t,e){var n=t.exports="undefined"!=typeof window&&window.Math==Math?window:"undefined"!=typeof self&&self.Math==Math?self:Function("return this")();"number"==typeof __g&&(__g=n)},function(t,e){var n=t.exports={version:"2.2.1"};"number"==typeof __e&&(__e=n)},function(t,e,n){var i=n(10);t.exports=function(t){if(!i(t))throw TypeError(t+" is not an object!");return t}},function(t,e,n){var i=n(2),o=n(3),r=n(9),s=n(13),a=n(6),c="prototype",u=function(t,e,n){var h,l,d,A,f=t&u.F,p=t&u.G,g=t&u.S,v=t&u.P,m=t&u.B,y=p?i:g?i[e]||(i[e]={}):(i[e]||{})[c],b=p?o:o[e]||(o[e]={}),w=b[c]||(b[c]={});p&&(n=e);for(h in n)l=!f&&y&&void 0!==y[h],d=(l?y:n)[h],A=m&&l?a(d,i):v&&"function"==typeof d?a(Function.call,d):d,y&&s(y,h,d,t&u.U),b[h]!=d&&r(b,h,A),v&&w[h]!=d&&(w[h]=d)};i.core=o,u.F=1,u.G=2,u.S=4,u.P=8,u.B=16,u.W=32,u.U=64,u.R=128,t.exports=u},function(t,e,n){var i=n(17);t.exports=function(t,e,n){if(i(t),void 0===e)return t;switch(n){case 1:return function(n){return t.call(e,n)};case 2:return function(n,i){return t.call(e,n,i)};case 3:return function(n,i,o){return t.call(e,n,i,o)}}return function(){return t.apply(e,arguments)}}},function(t,e,n){t.exports=!n(14)(function(){return 7!=Object.defineProperty({},"a",{get:function(){return 7}}).a})},function(t,e){var n={}.hasOwnProperty;t.exports=function(t,e){return n.call(t,e)}},function(t,e,n){var i=n(15),o=n(23);t.exports=n(7)?function(t,e,n){return i.f(t,e,o(1,n))}:function(t,e,n){return t[e]=n,t}},function(t,e){t.exports=function(t){return"object"==typeof t?null!==t:"function"==typeof t}},function(t,e){var n={}.toString;t.exports=function(t){return n.call(t).slice(8,-1)}},function(t,e){t.exports={}},function(t,e,n){var i=n(2),o=n(9),r=n(8),s=n(29)("src"),a="toString",c=Function[a],u=(""+c).split(a);n(3).inspectSource=function(t){return c.call(t)},(t.exports=function(t,e,n,a){var c="function"==typeof n;c&&(r(n,"name")||o(n,"name",e)),t[e]!==n&&(c&&(r(n,s)||o(n,s,t[e]?""+t[e]:u.join(String(e)))),t===i?t[e]=n:a?t[e]?t[e]=n:o(t,e,n):(delete t[e],o(t,e,n)))})(Function.prototype,a,function(){return"function"==typeof this&&this[s]||c.call(this)})},function(t,e){t.exports=function(t){try{return!!t()}catch(e){return!0}}},function(t,e,n){var i=n(4),o=n(35),r=n(43),s=Object.defineProperty;e.f=n(7)?Object.defineProperty:function(t,e,n){if(i(t),e=r(e,!0),i(n),o)try{return s(t,e,n)}catch(a){}if("get"in n||"set"in n)throw TypeError("Accessors not supported!");return"value"in n&&(t[e]=n.value),t}},function(t,e,n){var i=n(22),o=n(20);t.exports=function(t){return i(o(t))}},function(t,e){t.exports=function(t){if("function"!=typeof t)throw TypeError(t+" is not a function!");return t}},function(t,e,n){var i=n(6),o=n(22),r=n(28),s=n(27),a=n(58);t.exports=function(t,e){var n=1==t,c=2==t,u=3==t,h=4==t,l=6==t,d=5==t||l,A=e||a;return function(e,a,f){for(var p,g,v=r(e),m=o(v),y=i(a,f,3),b=s(m.length),w=0,x=n?A(e,b):c?A(e,0):void 0;b>w;w++)if((d||w in m)&&(p=m[w],g=y(p,w,v),t))if(n)x[w]=g;else if(g)switch(t){case 3:return!0;case 5:return p;case 6:return w;case 2:x.push(p)}else if(h)return!1;return l?-1:u||h?h:x}}},function(t,e,n){var i=n(11),o=n(1)("toStringTag"),r="Arguments"==i(function(){return arguments}()),s=function(t,e){try{return t[e]}catch(n){}};t.exports=function(t){var e,n,a;return void 0===t?"Undefined":null===t?"Null":"string"==typeof(n=s(e=Object(t),o))?n:r?i(e):"Object"==(a=i(e))&&"function"==typeof e.callee?"Arguments":a}},function(t,e){t.exports=function(t){if(void 0==t)throw TypeError("Can't call method on "+t);return t}},function(t,e,n){var i=n(10),o=n(2).document,r=i(o)&&i(o.createElement);t.exports=function(t){return r?o.createElement(t):{}}},function(t,e,n){var i=n(11);t.exports=Object("z").propertyIsEnumerable(0)?Object:function(t){return"String"==i(t)?t.split(""):Object(t)}},function(t,e){t.exports=function(t,e){return{enumerable:!(1&t),configurable:!(2&t),writable:!(4&t),value:e}}},function(t,e,n){var i=n(15).f,o=n(8),r=n(1)("toStringTag");t.exports=function(t,e,n){t&&!o(t=n?t:t.prototype,r)&&i(t,r,{configurable:!0,value:e})}},function(t,e,n){var i=n(40)("keys"),o=n(29);t.exports=function(t){return i[t]||(i[t]=o(t))}},function(t,e){var n=Math.ceil,i=Math.floor;t.exports=function(t){return isNaN(t=+t)?0:(t>0?i:n)(t)}},function(t,e,n){var i=n(26),o=Math.min;t.exports=function(t){return t>0?o(i(t),9007199254740991):0}},function(t,e,n){var i=n(20);t.exports=function(t){return Object(i(t))}},function(t,e){var n=0,i=Math.random();t.exports=function(t){return"Symbol(".concat(void 0===t?"":t,")_",(++n+i).toString(36))}},function(t,e){t.exports=function n(t,e){function i(){for(var i=0;i<t.children.length;i++)n(t.children[i],e)}t&&e(t.node,i)}},function(t,e){function n(t,e,n){return n>e?e>t?e:t>n?n:t:n>t?n:t>e?e:t}t.exports=n},function(t,e,n){var i=n(1)("unscopables"),o=Array.prototype;void 0==o[i]&&n(9)(o,i,{}),t.exports=function(t){o[i][t]=!0}},function(t,e){t.exports="constructor,hasOwnProperty,isPrototypeOf,propertyIsEnumerable,toLocaleString,toString,valueOf".split(",")},function(t,e,n){t.exports=n(2).document&&document.documentElement},function(t,e,n){t.exports=!n(7)&&!n(14)(function(){return 7!=Object.defineProperty(n(21)("div"),"a",{get:function(){return 7}}).a})},function(t,e,n){"use strict";var i=n(37),o=n(5),r=n(13),s=n(9),a=n(8),c=n(12),u=n(64),h=n(24),l=n(73),d=n(1)("iterator"),A=!([].keys&&"next"in[].keys()),f="@@iterator",p="keys",g="values",v=function(){return this};t.exports=function(t,e,n,m,y,b,w){u(n,e,m);var x,E,C,D=function(t){if(!A&&t in O)return O[t];switch(t){case p:return function(){return new n(this,t)};case g:return function(){return new n(this,t)}}return function(){return new n(this,t)}},M=e+" Iterator",S=y==g,R=!1,O=t.prototype,k=O[d]||O[f]||y&&O[y],N=k||D(y),T=y?S?D("entries"):N:void 0,L="Array"==e?O.entries||k:k;if(L&&(C=l(L.call(new t)),C!==Object.prototype&&(h(C,M,!0),i||a(C,d)||s(C,d,v))),S&&k&&k.name!==g&&(R=!0,N=function(){return k.call(this)}),i&&!w||!A&&!R&&O[d]||s(O,d,N),c[e]=N,c[M]=v,y)if(x={values:S?N:D(g),keys:b?N:D(p),entries:T},w)for(E in x)E in O||r(O,E,x[E]);else o(o.P+o.F*(A||R),e,x);return x}},function(t,e){t.exports=!1},function(t,e,n){var i=n(74),o=n(33);t.exports=Object.keys||function(t){return i(t,o)}},function(t,e){e.f={}.propertyIsEnumerable},function(t,e,n){var i=n(2),o="__core-js_shared__",r=i[o]||(i[o]={});t.exports=function(t){return r[t]||(r[t]={})}},function(t,e,n){var i=n(14);t.exports=function(t,e){return!!t&&i(function(){e?t.call(null,function(){},1):t.call(null)})}},function(t,e,n){var i,o,r,s=n(6),a=n(60),c=n(34),u=n(21),h=n(2),l=h.process,d=h.setImmediate,A=h.clearImmediate,f=h.MessageChannel,p=0,g={},v="onreadystatechange",m=function(){var t=+this;if(g.hasOwnProperty(t)){var e=g[t];delete g[t],e()}},y=function(t){m.call(t.data)};d&&A||(d=function(t){for(var e=[],n=1;arguments.length>n;)e.push(arguments[n++]);return g[++p]=function(){a("function"==typeof t?t:Function(t),e)},i(p),p},A=function(t){delete g[t]},"process"==n(11)(l)?i=function(t){l.nextTick(s(m,t,1))}:f?(o=new f,r=o.port2,o.port1.onmessage=y,i=s(r.postMessage,r,1)):h.addEventListener&&"function"==typeof postMessage&&!h.importScripts?(i=function(t){h.postMessage(t+"","*")},h.addEventListener("message",y,!1)):i=v in u("script")?function(t){c.appendChild(u("script"))[v]=function(){c.removeChild(this),m.call(t)}}:function(t){setTimeout(s(m,t,1),0)}),t.exports={set:d,clear:A}},function(t,e,n){var i=n(10);t.exports=function(t,e){if(!i(t))return t;var n,o;if(e&&"function"==typeof(n=t.toString)&&!i(o=n.call(t)))return o;if("function"==typeof(n=t.valueOf)&&!i(o=n.call(t)))return o;if(!e&&"function"==typeof(n=t.toString)&&!i(o=n.call(t)))return o;throw TypeError("Can't convert object to primitive value")}},function(t,e,n){function i(t){return t===p||t===g}function o(t){return y.test(t)}var r=n(97),s=0,a=1,c=2,u=3,h=4,l=5,d=6,A=7,f=8,p=9,g=10,v=11,m=12;t.exports=function(t,e){function n(t){return"function"==typeof t?t:"string"==typeof t?t:t&&"object"==typeof t?t:y("",t)}t=r(t),e||(e={});var y=e.concat||function(t,e){return String(t)+String(e)};return function(e){function r(t){var e=[];b===A&&(b=h);for(var n=0;n<t.length;n++){var o=t.charAt(n);b===a&&"<"===o?(w.length&&e.push([a,w]),w="",b=c):">"!==o||i(b)?b===a?w+=o:b===c&&/\s/.test(o)?(e.push([c,w]),w="",b=h):b===c?w+=o:b===h&&/[\w-]/.test(o)?(b=l,w=o):b===h&&/\s/.test(o)?(w.length&&e.push([l,w]),e.push([m])):b===l&&/\s/.test(o)?(e.push([l,w]),w="",b=d):b===l&&"="===o?(e.push([l,w],[v]),w="",b=A):b===l?w+=o:b!==d&&b!==h||"="!==o?b!==d&&b!==h||/\s/.test(o)?b===A&&'"'===o?b=g:b===A&&"'"===o?b=p:b===g&&'"'===o?(e.push([f,w],[m]),w="",b=h):b===p&&"'"===o?(e.push([f,w],[m]),w="",b=h):b!==A||/\s/.test(o)?b===f&&/\s/.test(o)?(e.push([m],[f,w]),w="",b=h):b!==f&&b!==p&&b!==g||(w+=o):(b=f,n--):(e.push([m]),/[\w-]/.test(o)?(w+=o,b=l):b=h):(e.push([v]),b=A):(b===c?e.push([c,w]):b===l?e.push([l,w]):b===f&&w.length&&e.push([f,w]),e.push([u]),w="",b=a)}return b===a&&w.length?(e.push([a,w]),w=""):b===f&&w.length?(e.push([f,w]),w=""):b===g&&w.length?(e.push([f,w]),w=""):b===p&&w.length?(e.push([f,w]),w=""):b===l&&(e.push([l,w]),w=""),e}for(var b=a,w="",x=arguments.length,E=[],C=0;C<e.length;C++)if(x-1>C){var D=arguments[C+1],M=r(e[C]),S=b;S===g&&(S=f),S===p&&(S=f),S===A&&(S=f),S===h&&(S=l),M.push([s,S,D]),E.push.apply(E,M)}else E.push.apply(E,r(e[C]));for(var R=[null,{},[]],O=[[R,-1]],C=0;C<E.length;C++){var k=O[O.length-1][0],M=E[C],N=M[0];if(N===c&&/^\//.test(M[1])){var T=O[O.length-1][1];O.length>1&&(O.pop(),O[O.length-1][0][2][T]=t(k[0],k[1],k[2].length?k[2]:void 0))}else if(N===c){var L=[M[1],{},[]];k[2].push(L),O.push([L,k[2].length-1])}else if(N===l||N===s&&M[1]===l){for(var I,U="";C<E.length;C++)if(E[C][0]===l)U=y(U,E[C][1]);else{if(E[C][0]!==s||E[C][1]!==l)break;if("object"!=typeof E[C][2]||U)U=y(U,E[C][2]);else for(I in E[C][2])E[C][2].hasOwnProperty(I)&&!k[1][I]&&(k[1][I]=E[C][2][I])}E[C][0]===v&&C++;for(var B=C;C<E.length;C++)if(E[C][0]===f||E[C][0]===l)k[1][U]?k[1][U]=y(k[1][U],E[C][1]):k[1][U]=n(E[C][1]);else{if(E[C][0]!==s||E[C][1]!==f&&E[C][1]!==l){!U.length||k[1][U]||C!==B||E[C][0]!==u&&E[C][0]!==m||(k[1][U]=U.toLowerCase());break}k[1][U]?k[1][U]=y(k[1][U],E[C][2]):k[1][U]=n(E[C][2])}}else if(N===l)k[1][M[1]]=!0;else if(N===s&&M[1]===l)k[1][M[2]]=!0;else if(N===u){if(o(k[0])&&O.length){var T=O[O.length-1][1];O.pop(),O[O.length-1][0][2][T]=t(k[0],k[1],k[2].length?k[2]:void 0)}}else if(N===s&&M[1]===a)void 0===M[2]||null===M[2]?M[2]="":M[2]||(M[2]=y("",M[2])),Array.isArray(M[2][0])?k[2].push.apply(k[2],M[2]):k[2].push(M[2]);else if(N===a)k[2].push(M[1]);else if(N!==v&&N!==m)throw new Error("unhandled: "+N)}if(R[2].length>1&&/^\s*$/.test(R[2][0])&&R[2].shift(),R[2].length>2||2===R[2].length&&/\S/.test(R[2][1]))throw new Error("multiple root elements must be wrapped in an enclosing tag");return Array.isArray(R[2][0])&&"string"==typeof R[2][0][0]&&Array.isArray(R[2][0][2])&&(R[2][0]=t(R[2][0][0],R[2][0][1],R[2][0][2])),R[2][0]}};var y=(Object.prototype.hasOwnProperty,RegExp("^("+["area","base","basefont","bgsound","br","col","command","embed","frame","hr","img","input","isindex","keygen","link","meta","param","source","track","wbr","animate","animateTransform","circle","cursor","desc","ellipse","feBlend","feColorMatrix","feComponentTransfer","feComposite","feConvolveMatrix","feDiffuseLighting","feDisplacementMap","feDistantLight","feFlood","feFuncA","feFuncB","feFuncG","feFuncR","feGaussianBlur","feImage","feMerge","feMergeNode","feMorphology","feOffset","fePointLight","feSpecularLighting","feSpotLight","feTile","feTurbulence","font-face-format","font-face-name","font-face-uri","glyph","glyphRef","hkern","image","line","missing-glyph","mpath","path","polygon","polyline","rect","set","stop","tref","use","view","vkern"].join("|")+")(?:[.#][a-zA-Z0-9-_:-]+)*$"))},function(t,e,n){n(99);var i=n(47),o=n(30),r=n(48),s=function(t){this.opts=t,this.data=null,this.gardenId=null,this.gardenKey=null};s.prototype={record:function(t,e){(new Image).src="http://xkcd.com/events.gif?re="+t+"&ev="+e},log:function(){this.opts.debug&&console.log.apply(console,arguments)},_url:function(){var t=this.opts.endpoint+"run";return this.gardenId&&(t+="/"+this.gardenId),t},_connect:function(){var t=this._url();this.source=new EventSource(t),this.log("connecting to event source:",t),this.source.addEventListener("open",this.onConnect.bind(this),!1),this.source.addEventListener("error",this.onConnectionError.bind(this),!1),this.source.addEventListener("linden-key",this.onLindenKey.bind(this),!1),this.source.addEventListener("linden",this.onLindenUpdate.bind(this),!1),this.source.addEventListener("reload",this.onReload.bind(this),!1)},run:function(){try{this.opts.endpoint&&(location.hash&&(this.gardenId=location.hash.substr(1),this.gardenKey=localStorage["key-"+this.gardenId]),this._connect()),this.linden=new i({el:this.opts.el,width:this.opts.width,height:this.opts.height,title:this.opts.title,urlPrefix:this.opts.imgPrefix,loading:!0,readOnly:!this.gardenKey,debug:this.opts.debug,onLightChanging:this.handleLightChanging.bind(this),onSetLight:this.handleSetLight.bind(this),onRemoveLight:this.handleRemoveLight.bind(this),onPruneNode:this.handlePruneNode.bind(this)}),this.opts.data&&this.linden.update(this.opts.data),r(this.linden)}catch(t){throw this.record(this.opts.name,"js_error"),t}return this},onConnect:function(){this.record(this.opts.name,"connect_start"),this.dirtyLights={},this.inFlightRequests=0,this.linden.renderUI()},onConnectionError:function(t){this.log("connection error",t),this.record(this.opts.name,"connect_error"),this.linden.opts.loading=!0,this.linden.resetTree(),this.linden.renderUI(),this.source.url!==this._url()&&(this.source.close(),this._connect())},onLindenKey:function(t){this.log("linden key from EventSource",t);var e=JSON.parse(t.data);this.gardenId=location.hash=e.garden,this.gardenKey=localStorage["key-"+e.garden]=e.key,this.linden.opts.readOnly=!1,this.linden.renderUI()},_acceptUpdate:function(){if(this.inFlightRequests>0)return this.log("rejecting update, inflight:",this.inFlightRequests),!1;for(var t in this.dirtyLights)if(this.dirtyLights.hasOwnProperty(t)&&this.dirtyLights[t]===!0)return this.log("rejecting update, dirty light:",t),!1;return!0},_update:function(t){this._acceptUpdate()&&(this.data=t,this.linden.update(t))},onLindenUpdate:function(t){this.log("linden from EventSource",t),this._update(JSON.parse(t.data))},onReload:function(){var t=Math.round(Math.random()*this.opts.spread.reload);this.log("reloading in",t+5,"seconds"),setTimeout(function(){this.record(this.opts.name,"reloading"),setTimeout(function(){location.reload()},5e3)},1e3*t)},request:function(t,e){var n=function(){this.inFlightRequests--,this.log("inflight--",this.inFlightRequests)}.bind(this),i={method:"POST"};Object.assign(i,e),i.body=JSON.stringify(e.body),this.log("request",t,i);var o=fetch(t,i);return o.then(n,n),this.inFlightRequests++,this.log("inflight++",this.inFlightRequests),o},handleLightChanging:function(t){this.dirtyLights[t]=!0},handleSetLight:function(t,e){this.log("set light:",t,e),this.request(this.opts.endpoint+"edit",{body:Object.assign({tag:"EditLight",garden:this.data.garden,editKey:this.gardenKey,light:t},e)}).then(function(t){return t.json()}).then(function(t){this._update(Object.assign({},this.data,{lights:t}))}.bind(this)),delete this.dirtyLights[t]},_sendRemove:function(t){this.request(this.opts.endpoint+"edit",{body:{tag:"Del",garden:this.data.garden,editKey:this.gardenKey,node:t}})},handleRemoveLight:function(t){this.log("remove light:",t);var e=this.data.lights.filter(function(e){return e.id!==t});this._update(Object.assign({},this.data,{lights:e})),this._sendRemove(t)},handlePruneNode:function(t){this.log("prune node:",t);var e={children:[]},n=e;o(this.data.tree,function(e,i){var o=n;n={node:e,children:[]},e.id!==t&&o.children.push(n),i(),n=o}),this._update(Object.assign({},this.data,{tree:e.children[0]})),this._sendRemove(t)}},t.exports=s},function(t,e){function n(){this.imgs={},this.callbacks={}}n.prototype={get:function(t,e){var n=this.imgs[t];void 0===n?(this.imgs[t]=!0,this.callbacks[t]=[e],n=new Image,n.onload=this._onDone.bind(this,t,n,n),n.onerror=this._onDone.bind(this,t,n,!1),n.crossOrigin="anonymous",n.src=t):n===!0?this.callbacks[t].push(e):e&&e(t,n)},_onDone:function(t,e,n){this.imgs[t]=n;for(var i=0;i<this.callbacks[t].length;i++){var o=this.callbacks[t][i];o&&o(t,n)}delete this.callbacks[t]},getAll:function(t,e){function n(t,n){o[t]=n,i--,0===i&&e(o)}if(!t.length)return void e([]);for(var i=t.length,o={},r=0;r<t.length;r++)this.get(t[r],n)}},t.exports=n},function(t,e,n){function i(t,e){return Object.freeze(Object.defineProperties(t,{raw:{value:Object.freeze(e)}}))}function o(t){return Math.sin(t*O)}function r(t){return Math.cos(t*O)}function s(t,e){return(t%e+e)%e}function a(t,e,n,i,a,c,u){var h,l,d,A,f=s(t,360);90>=f?(h=i,l=u,d=c,A=a):180>=f?(h=c,l=a,d=i,A=u):270>=f?(h=c,l=u,d=i,A=a):(h=i,l=u,d=c,A=a),h-=e,l-=n,d-=e,A-=n;var p={x1:e+h*r(f)-l*o(f),y1:n+h*o(f)+l*r(f),x2:e+d*r(f)-A*o(f),y2:n+d*o(f)+A*r(f)};if(p.x2<p.x1){var g=p.x2;p.x2=p.x1,p.x1=g}if(p.y2<p.y1){var v=p.y2;p.y2=p.y1,p.y1=v}return p}function c(t,e,n,i){return"rgba("+100*t+"%, "+100*e+"%, "+100*n+"%, "+i+")"}function u(t){return.32>t?[1,.5+.5*t/.32,.5]:.64>t?[1,1,.5+.5*(t-.32)/.32]:[.75+.25*(1-t)/.36,.75+.25*(1-t)/.36,1]}function h(t,e,n){var i={x2:0,y2:0,scale:1,angle:0};M(t,function(t,s){var a=e[t.id],c=t.cache;c||(c=t.cache={}),c.scale=i.scale*t.scale,c.angle=i.angle+a.angle;var u=t.attach[0]-t.root[0],h=t.attach[1]-t.root[1];c.x1=i.x2,c.y1=i.y2,c.x2=c.x1+c.scale*(u*r(c.angle)+h*o(c.angle)),c.y2=c.y1+c.scale*(-u*o(c.angle)+h*r(c.angle));var l=i;i=c,n(t,s),i=l})}function l(t){var e=t.cache,n=e.x1-t.root[0],i=e.y1-t.root[1];return a(-e.angle,n+t.root[0],i+t.root[1],n,i,n+e.scale*e.width,i+e.scale*e.height)}function d(t,e,n,i){function s(t,n){return e[t.id][n]}var a=i.width,d=i.height,A=i.debug,f=i.urlPrefix,p="",g=i.imgCache,v=i.el.getContext("2d"),m=Number(i.el.getAttribute("data-density"));m>1&&(p="2x-");var y=[];M(t,function(t,e){t.img&&y.push(f+p+t.img),e()}),g.getAll(y,function(g){function y(t){for(var e=0;e<n.length;e++){var i=n[e];x.save(),x.beginPath(),x.moveTo(i.pos[0],i.pos[1]);var s=i.pos[0]+E*r(i.pointAngle-i.beamAngle/2),a=i.pos[1]+E*o(i.pointAngle-i.beamAngle/2);x.lineTo(s,a),x.arc(i.pos[0],i.pos[1],E,(i.pointAngle-i.beamAngle/2)*O,(i.pointAngle+i.beamAngle/2)*O),x.lineTo(i.pos[0],i.pos[1]),t(i),x.restore()}}v.save(),v.scale(m,-m),v.translate(0,-d),v.clearRect(0,0,a,d);var b;h(t,e,function(t,n){var i=t.cache;if(v.save(),v.rotate(s(t,"angle")*-O),v.scale(t.scale,t.scale),e._selected&&(t.id===e._selected.id&&(v.globalAlpha=.5),t.id===e._selected.circled&&(b=t)),t.img){var o=f+p+t.img,r=g[o];r&&(v.save(),v.scale(1/m,-1/m),v.translate(0,-r.height),v.drawImage(r,-t.root[0]*m,t.root[1]*m),v.restore(),i.width=r.width/m,i.height=r.height/m)}v.translate(t.attach[0]-t.root[0],t.attach[1]-t.root[1]),n(),v.restore()});var w=document.createElement("canvas");w.width=i.el.width,w.height=i.el.height;var x=w.getContext("2d");x.scale(m,m);var E=Math.sqrt(Math.pow(a,2)+Math.pow(d,2));if(y(function(t){var e=x.createRadialGradient(t.pos[0],t.pos[1],0,t.pos[0],t.pos[1],E),n=u(t.temp);e.addColorStop(0,c(n[0],n[1],n[2],1)),e.addColorStop(1,c(n[0],n[1],n[2],0)),x.fillStyle=e,x.globalCompositeOperation="lighten",x.fill()}),y(function(t){x.globalAlpha=.4,x.strokeStyle="black",x.stroke()}),v.save(),v.scale(1/m,1/m),v.globalCompositeOperation="source-atop",v.globalAlpha=.65,v.drawImage(w,0,0),v.globalAlpha=.25,v.globalCompositeOperation="destination-over",v.drawImage(w,0,0),v.restore(),b){b.cache;v.save(),v.beginPath();var C=l(b),D=C.x1+(C.x2-C.x1)/2,S=C.y1+(C.y2-C.y1)/2,k=Math.sqrt(Math.pow(C.x2-C.x1,2)+Math.pow(C.y2-C.y1,2));v.arc(D,S,k/2+6,0,R),v.strokeStyle="red",v.lineWidth=2,v.globalAlpha=.9,v.stroke(),v.restore()}A&&M(t,function(t,e){v.save();var n=t.cache;if(v.beginPath(),v.globalAlpha=.75,v.moveTo(n.x1,n.y1),v.lineTo(n.x2,n.y2),v.strokeStyle=t.proxyFor?"lightgreen":"magenta",v.lineWidth=1,v.stroke(),e(),v.restore(),n.width){var i=l(t);v.beginPath(),v.rect(i.x1,i.y1,i.x2-i.x1,i.y2-i.y1),v.strokeStyle="red",v.stroke()}}),v.restore(),i.onFinish&&i.onFinish()})}function A(t){this.el=t.el,this.opts=t,this.imgCache=new D,this._queued=!1,this._rendering=!1,this._afterNextRender=null,this.resetTree(),this.renderUI()}var f=i(["<div\n className=","\n style=","\n onMouseDown=","\n />"],["<div\n className=","\n style=","\n onMouseDown=","\n />"]),p=i(["\n <div>\n <div style=",'>\n <Handle\n className="rotate"\n disabled=',"\n onDrag=","\n onRelease=","\n ></Handle>\n </div>\n <div style=",'>\n <Handle\n className="color"\n style=',"\n disabled=","\n onDrag=","\n onRelease=","\n ></Handle>\n </div>\n </div>\n "],["\n <div>\n <div style=",'>\n <Handle\n className="rotate"\n disabled=',"\n onDrag=","\n onRelease=","\n ></Handle>\n </div>\n <div style=",'>\n <Handle\n className="color"\n style=',"\n disabled=","\n onDrag=","\n onRelease=","\n ></Handle>\n </div>\n </div>\n "]),g=i(["\n <div\n className=","\n style=","\n data-light-id=",'\n >\n <div>\n <Handle\n className="position"\n disabled=',"\n onGrab=","\n onDrag=","\n onRelease=","\n ></Handle>\n </div>\n ","\n </div>\n "],["\n <div\n className=","\n style=","\n data-light-id=",'\n >\n <div>\n <Handle\n className="position"\n disabled=',"\n onGrab=","\n onDrag=","\n onRelease=","\n ></Handle>\n </div>\n ","\n </div>\n "]),v=i(['\n <div className="tools">\n <button\n className="add-light"\n disabled=','\n title="Create new light"\n onClick=','\n >+</button>\n <button\n className="remove"\n disabled=','\n title="Remove selected (delete key)"\n onClick=',"\n >x</button>\n </div>\n "],['\n <div className="tools">\n <button\n className="add-light"\n disabled=','\n title="Create new light"\n onClick=','\n >+</button>\n <button\n className="remove"\n disabled=','\n title="Remove selected (delete key)"\n onClick=',"\n >x</button>\n </div>\n "]),m=i(["\n <div\n onMouseDown=","\n onKeyDown=","\n style=","\n className=","\n title=",'\n tabIndex="1"\n >\n ',"\n ",'\n <div className="content">\n <canvas\n data-density=',"\n width=","\n height=","\n style=","\n onMouseDown=","\n ></canvas>\n ","\n </div>\n </div>\n "],["\n <div\n onMouseDown=","\n onKeyDown=","\n style=","\n className=","\n title=",'\n tabIndex="1"\n >\n ',"\n ",'\n <div className="content">\n <canvas\n data-density=',"\n width=","\n height=","\n style=","\n onMouseDown=","\n ></canvas>\n ","\n </div>\n </div>\n "]),y=i(['<div><div className="spinner"></div><div className="spinner-text">loading</div>'],['<div><div className="spinner"></div><div className="spinner-text">loading</div>']),b=i(["<Light\n id=","\n disabled=","\n selected=","\n x=","\n y=","\n temp=","\n pointAngle=","\n beamAngle=","\n onInteract=","\n onMove=","\n onRotate=","\n onChangeColor=","\n onSave=","\n />"],["<Light\n id=","\n disabled=","\n selected=","\n x=","\n y=","\n temp=","\n pointAngle=","\n beamAngle=","\n onInteract=","\n onMove=","\n onRotate=","\n onChangeColor=","\n onSave=","\n />"]),w=n(94),x=n(49),E=n(31),C=n(100),D=n(46),M=n(30),S=Math.PI/2,R=2*Math.PI,O=S/90;w.component("Handle",{componentDidMount:function(){this._handleDragMove=this.handleDragMove.bind(this),this._handleDragUp=this.handleDragUp.bind(this),this.getDOMNode().firstChild.addEventListener("touchstart",this.handleDragDown.bind(this))},_stopListening:function(){window.removeEventListener("mousemove",this._handleDragMove,!1),window.removeEventListener("mouseup",this._handleDragUp,!1),window.removeEventListener("touchmove",this._handleDragMove,!1),window.removeEventListener("touchend",this._handleDragUp,!1)},componentWillUnmount:function(){this._stopListening()},_normalizeEvent:function(t){t.touches&&t.touches[0]&&(t.clientX=t.touches[0].clientX,t.clientY=t.touches[0].clientY)},handleDragDown:function(t){this.props.disabled||(document.documentElement.style.cursor="move",window.addEventListener("mousemove",this._handleDragMove,!1),window.addEventListener("mouseup",this._handleDragUp,!1),window.addEventListener("touchmove",this._handleDragMove,!1),window.addEventListener("touchend",this._handleDragUp,!1),this.props.onGrab&&(this._normalizeEvent(t),this.props.onGrab(t)),t.preventDefault())},handleDragUp:function(t){document.documentElement.style.cursor=null,this._stopListening(),this.props.onRelease&&(this._normalizeEvent(t),this.props.onRelease(t)),t.preventDefault()},handleDragMove:function(t){this.props.onDrag&&(this._normalizeEvent(t),this.props.onDrag(t)),t.preventDefault()},render:function(){return w(f,x("handle",this.props.className),this.props.style||"",this.handleDragDown.bind(this))}}),w.component("Light",{handleRotate:function(t){var e=this.getDOMNode().firstChild.getBoundingClientRect(),n=t.clientX-e.left,i=t.clientY-e.top,o=s(-Math.atan2(i,n)/O,360),r=Math.sqrt(Math.pow(n,2)+Math.pow(i,2));this.props.onRotate(o,E(0,95-3*(r-42),95))},handleRotateColor:function(t){var e=this.getDOMNode().firstChild.getBoundingClientRect(),n=t.clientX-e.left,i=t.clientY-e.top,o=E(90,s(-Math.atan2(i,n)/O-this.props.pointAngle,360),270)-90;this.props.onChangeColor(o/180)},render:function(){var t=C({left:this.props.x+"px",top:this.props.y+"px",transform:"rotate("+(-this.props.pointAngle-90)+"deg)"}),e="";if(this.props.selected){var n=42+E(0,(95-this.props.beamAngle)/3,95),i=C({position:"absolute",top:n+"px"}),s=180*this.props.temp,a=u(this.props.temp),h=C({position:"absolute",left:39*r(-s)+"px",top:39*o(-s)+"px"}),l=C({backgroundColor:c(a[0],a[1],a[2],1)});e=w(p,i,this.props.disabled,this.handleRotate.bind(this),this.props.onSave,h,l,this.props.disabled,this.handleRotateColor.bind(this),this.props.onSave)}return w(g,x("light",this.props.selected&&"selected"),t,this.props.id,this.props.disabled,this.props.onInteract,this.props.onMove,this.props.onSave,e)}}),A.prototype={STIFFNESS:.25,FRICTION:.1,MAX_LIGHTS:3,FPS:15,resetTree:function(){this.l=null,this.lights=[],this.state={_selected:null}},renderUI:function(){var t="";this.opts.readOnly||this.opts.loading||(t=w(v,this.lights.length===this.MAX_LIGHTS,this.handleCreateLight.bind(this),!this.state._selected,this.handleRemoveSelection.bind(this)));var e=C({width:this.opts.width+"px",height:this.opts.height+"px"}),n=window.devicePixelRatio>1?2:1,i=C({transformOrigin:"0 0",transform:"scale("+1/n+", "+1/n+")"}),o=x("frame",this.opts.loading&&"loading");w.render(w(m,this.handleMouseDown.bind(this),this.handleKeyDown.bind(this),e,o,this.opts.title,t,this.opts.loading?w(y):"",n,this.opts.width*n,this.opts.height*n,i,this.handleCanvasMouseDown.bind(this),this.lights.map(function(t){var e=Boolean(this.state._selected&&"light"===this.state._selected.kind&&this.state._selected.id===t.id);return w(b,t.id,this.opts.readOnly,e,t.pos[0],this.opts.height-t.pos[1],t.temp,t.pointAngle,t.beamAngle,this.handleLightInteract.bind(this,t.id),this.handleLightMove.bind(this,t.id),this.handleLightRotate.bind(this,t.id),this.handleLightChangeColor.bind(this,t.id),this.saveLight.bind(this,t.id))}.bind(this))),this.el)},update:function(t){this.l=t.tree;var e=0,n={};M(this.l,function(t,i){e++;var o=t.img||"proxy";n[o]=n[o]||{count:0},n[o].count++;var r=this.state[t.id];r||(r=this.state[t.id]={angle:t.angle,av:0}),r.node=t,i()}.bind(this)),this.lights=t.lights||[];var i=Date.now();this._afterNextRender=function(){if(this.opts.loading=!1,this.renderUI(),this._afterNextRender=null,this.opts.debug){var o=Date.now()-i;console.debug("updated. rendered",e,"nodes in",o,"ms","| supply:",t.supply),console.table(n)}}.bind(this),this.renderCanvas()},addWind:function(t,e,n){M(this.l,function(i,r){if(!i.rigid&&i.cache){var s=this.state[i.id],a=o(t-i.cache.angle),c=1-n+Math.random()*n;s.av+=i.waviness*c*(1/Math.sqrt(i.cache.scale))*a*e}r()}.bind(this)),this.queueStep()},queueStep:function(){this._queued||(setTimeout(this.step.bind(this),1e3/this.FPS),this._queued=Date.now())},step:function(){this.opts.beforeStep&&this.opts.beforeStep(),M(this.l,function(t,e){var n=this.state[t.id];n.av+=(1-t.waviness)*t.scale*this.STIFFNESS*(t.angle-n.angle),n.av+=-n.av*this.FRICTION,n.angle+=n.av,e()}.bind(this)),this.renderCanvas(),this._queued=!1,this.queueStep()},renderCanvas:function(){!this._rendering&&this.l&&(this._rendering=!0,d(this.l,this.state,this.lights,{width:this.opts.width,height:this.opts.height,el:this.el.querySelector("canvas"),urlPrefix:this.opts.urlPrefix,imgCache:this.imgCache,debug:this.opts.debug,onFinish:function(){this._afterNextRender&&this._afterNextRender(),this._rendering=!1}.bind(this)}))},handleMouseDown:function(){this.el.querySelector(".frame").focus()},_deselect:function(){this.state._selected=null,this.renderCanvas(),this.renderUI()},handleCanvasMouseDown:function(t){if(!this.opts.readOnly){this._deselect();var e=this.el.getBoundingClientRect(),n=t.clientX-e.left,i=this.opts.height-(t.clientY-e.top),o=null,r=Number.MAX_VALUE;if(M(this.l,function(t,e){var s=t.cache;if(s&&s.width&&!t.immutable){var a=l(t);if(a.x1<=n&&a.x2>=n&&a.y1<=i&&a.y2>=i){var c=(a.x2-a.x1)*(a.y2-a.y1);r>c&&(o=t,r=c)}}e()}),o){var s=o.proxyFor||o.id;this.state._selected={kind:"node",id:s,circled:o.id}}this.renderCanvas(),this.renderUI()}},handleKeyDown:function(t){46!==t.which&&8!==t.which||(this.handleRemoveSelection(),t.preventDefault())},handleLightInteract:function(t){this.opts.readOnly||(this.state._selected={kind:"light",id:t},this.renderCanvas(),this.renderUI())},handleLightMove:function(t,e){+var n=this.el.getBoundingClientRect(),i=E(0,e.clientX-n.left,this.opts.width),o=E(0,this.opts.height-(e.clientY-n.top),this.opts.height);this.setLight(t,{pos:[i,o]})},handleLightRotate:function(t,e,n){this.setLight(t,{pointAngle:e,beamAngle:n})},handleLightChangeColor:function(t,e){this.setLight(t,{temp:e})},setLight:function(t,e){this.opts.onLightChanging(t);var n=this.lights.findIndex(function(e){return e.id===t});-1!==n&&(Object.assign(this.lights[n],e),this.renderCanvas(),this.renderUI())},saveLight:function(t){var e=this.lights.findIndex(function(e){return e.id===t});-1!==e&&this.opts.onSetLight(t,this.lights[e])},handleCreateLight:function(){var t=this.opts.width*Math.random(),e=(this.opts.height+this.opts.height*Math.random())/2,n=this.opts.width/2,i=0;this.opts.onSetLight(null,{pos:[t,e],pointAngle:Math.atan2(i-e,n-t)/O,beamAngle:45+45*Math.random(),temp:.5})},handleRemoveSelection:function(){this.state._selected&&("node"===this.state._selected.kind?this.opts.onPruneNode(this.state._selected.id):"light"===this.state._selected.kind&&this.opts.onRemoveLight(this.state._selected.id),this._deselect())}},t.exports=A},function(t,e,n){var i=n(31);t.exports=function(t){function e(){var e=2*(Math.random()-.5);Math.random()>.9&&(n+=Math.random()-.5),o=i(60,o+2*(Math.random()-.5),120),n*=.995,t.addWind(o,.025*e+.25*n,.5)}var n=0,o=90;t.opts.beforeStep=e,e()}},function(t,e,n){var i,o;/*!+ Copyright (c) 2016 Jed Watson.+ Licensed under the MIT License (MIT), see+ http://jedwatson.github.io/classnames+ */+!function(){"use strict";function n(){for(var t=[],e=0;e<arguments.length;e++){var i=arguments[e];if(i){var o=typeof i;if("string"===o||"number"===o)t.push(i);else if(Array.isArray(i))t.push(n.apply(null,i));else if("object"===o)for(var s in i)r.call(i,s)&&i[s]&&t.push(s)}}return t.join(" ")}var r={}.hasOwnProperty;"undefined"!=typeof t&&t.exports?t.exports=n:(i=[],o=function(){return n}.apply(e,i),!(void 0!==o&&(t.exports=o)))}()},function(t,e,n){n(88),n(90),n(91),n(89),t.exports=n(3).Promise},function(t,e,n){n(82),t.exports=n(3).Array.filter},function(t,e,n){n(83),t.exports=n(3).Array.findIndex},function(t,e,n){n(85),t.exports=n(3).Array.map},function(t,e,n){n(86),t.exports=n(3).Date.now},function(t,e,n){n(87),t.exports=n(3).Object.assign},function(t,e){t.exports=function(t,e,n,i){if(!(t instanceof e)||void 0!==i&&i in t)throw TypeError(n+": incorrect invocation!");return t}},function(t,e,n){var i=n(16),o=n(27),r=n(80);t.exports=function(t){return function(e,n,s){var a,c=i(e),u=o(c.length),h=r(s,u);if(t&&n!=n){for(;u>h;)if(a=c[h++],a!=a)return!0}else for(;u>h;h++)if((t||h in c)&&c[h]===n)return t||h;return!t&&-1}}},function(t,e,n){var i=n(10),o=n(62),r=n(1)("species");t.exports=function(t,e){var n;return o(t)&&(n=t.constructor,"function"!=typeof n||n!==Array&&!o(n.prototype)||(n=void 0),i(n)&&(n=n[r],null===n&&(n=void 0))),new(void 0===n?Array:n)(e)}},function(t,e,n){var i=n(6),o=n(63),r=n(61),s=n(4),a=n(27),c=n(81);t.exports=function(t,e,n,u,h){var l,d,A,f=h?function(){return t}:c(t),p=i(n,u,e?2:1),g=0;if("function"!=typeof f)throw TypeError(t+" is not iterable!");if(r(f))for(l=a(t.length);l>g;g++)e?p(s(d=t[g])[0],d[1]):p(t[g]);else for(A=f.call(t);!(d=A.next()).done;)o(A,p,d.value,e)}},function(t,e){t.exports=function(t,e,n){var i=void 0===n;switch(e.length){case 0:return i?t():t.call(n);case 1:return i?t(e[0]):t.call(n,e[0]);case 2:return i?t(e[0],e[1]):t.call(n,e[0],e[1]);case 3:return i?t(e[0],e[1],e[2]):t.call(n,e[0],e[1],e[2]);case 4:return i?t(e[0],e[1],e[2],e[3]):t.call(n,e[0],e[1],e[2],e[3])}return t.apply(n,e)}},function(t,e,n){var i=n(12),o=n(1)("iterator"),r=Array.prototype;t.exports=function(t){return void 0!==t&&(i.Array===t||r[o]===t)}},function(t,e,n){var i=n(11);t.exports=Array.isArray||function(t){return"Array"==i(t)}},function(t,e,n){var i=n(4);t.exports=function(t,e,n,o){try{return o?e(i(n)[0],n[1]):e(n)}catch(r){var s=t["return"];throw void 0!==s&&i(s.call(t)),r}}},function(t,e,n){"use strict";var i=n(69),o=n(23),r=n(24),s={};n(9)(s,n(1)("iterator"),function(){return this}),t.exports=function(t,e,n){t.prototype=i(s,{next:o(1,n)}),r(t,e+" Iterator")}},function(t,e,n){var i=n(1)("iterator"),o=!1;try{var r=[7][i]();r["return"]=function(){o=!0},Array.from(r,function(){throw 2})}catch(s){}t.exports=function(t,e){if(!e&&!o)return!1;var n=!1;try{var r=[7],s=r[i]();s.next=function(){n=!0},r[i]=function(){return s},t(r)}catch(a){}return n}},function(t,e){t.exports=function(t,e){return{value:e,done:!!t}}},function(t,e,n){var i,o,r,s=n(2),a=n(42).set,c=s.MutationObserver||s.WebKitMutationObserver,u=s.process,h=s.Promise,l="process"==n(11)(u),d=function(){var t,e;for(l&&(t=u.domain)&&t.exit();i;)e=i.fn,e(),i=i.next;o=void 0,t&&t.enter()};if(l)r=function(){u.nextTick(d)};else if(c){var A=!0,f=document.createTextNode("");new c(d).observe(f,{characterData:!0}),r=function(){f.data=A=!A}}else r=h&&h.resolve?function(){h.resolve().then(d)}:function(){a.call(s,d)};t.exports=function(t){var e={fn:t,next:void 0};o&&(o.next=e),i||(i=e,r()),o=e}},function(t,e,n){"use strict";var i=n(38),o=n(72),r=n(39),s=n(28),a=n(22),c=Object.assign;t.exports=!c||n(14)(function(){var t={},e={},n=Symbol(),i="abcdefghijklmnopqrst";return t[n]=7,i.split("").forEach(function(t){e[t]=t}),7!=c({},t)[n]||Object.keys(c({},e)).join("")!=i})?function(t,e){for(var n=s(t),c=arguments.length,u=1,h=o.f,l=r.f;c>u;)for(var d,A=a(arguments[u++]),f=h?i(A).concat(h(A)):i(A),p=f.length,g=0;p>g;)l.call(A,d=f[g++])&&(n[d]=A[d]);return n}:c},function(t,e,n){var i=n(4),o=n(70),r=n(33),s=n(25)("IE_PROTO"),a=function(){},c="prototype",u=function(){var t,e=n(21)("iframe"),i=r.length,o=">";for(e.style.display="none",n(34).appendChild(e),e.src="javascript:",t=e.contentWindow.document,t.open(),t.write("<script>document.F=Object</script"+o),t.close(),u=t.F;i--;)delete u[c][r[i]];return u()};t.exports=Object.create||function(t,e){var n;return null!==t?(a[c]=i(t),n=new a,a[c]=null,n[s]=t):n=u(),void 0===e?n:o(n,e)}},function(t,e,n){var i=n(15),o=n(4),r=n(38);t.exports=n(7)?Object.defineProperties:function(t,e){o(t);for(var n,s=r(e),a=s.length,c=0;a>c;)i.f(t,n=s[c++],e[n]);return t}},function(t,e,n){var i=n(39),o=n(23),r=n(16),s=n(43),a=n(8),c=n(35),u=Object.getOwnPropertyDescriptor;e.f=n(7)?u:function(t,e){if(t=r(t),e=s(e,!0),c)try{return u(t,e)}catch(n){}return a(t,e)?o(!i.f.call(t,e),t[e]):void 0}},function(t,e){e.f=Object.getOwnPropertySymbols},function(t,e,n){var i=n(8),o=n(28),r=n(25)("IE_PROTO"),s=Object.prototype;t.exports=Object.getPrototypeOf||function(t){return t=o(t),i(t,r)?t[r]:"function"==typeof t.constructor&&t instanceof t.constructor?t.constructor.prototype:t instanceof Object?s:null}},function(t,e,n){var i=n(8),o=n(16),r=n(57)(!1),s=n(25)("IE_PROTO");t.exports=function(t,e){var n,a=o(t),c=0,u=[];for(n in a)n!=s&&i(a,n)&&u.push(n);for(;e.length>c;)i(a,n=e[c++])&&(~r(u,n)||u.push(n));return u}},function(t,e,n){var i=n(13);t.exports=function(t,e,n){for(var o in e)i(t,o,e[o],n);return t}},function(t,e,n){var i=n(10),o=n(4),r=function(t,e){if(o(t),!i(e)&&null!==e)throw TypeError(e+": can't set as prototype!")};t.exports={set:Object.setPrototypeOf||("__proto__"in{}?function(t,e,i){try{i=n(6)(Function.call,n(71).f(Object.prototype,"__proto__").set,2),i(t,[]),e=!(t instanceof Array)}catch(o){e=!0}return function(t,n){return r(t,n),e?t.__proto__=n:i(t,n),t}}({},!1):void 0),check:r}},function(t,e,n){"use strict";var i=n(2),o=n(15),r=n(7),s=n(1)("species");t.exports=function(t){var e=i[t];r&&e&&!e[s]&&o.f(e,s,{configurable:!0,get:function(){return this}})}},function(t,e,n){var i=n(4),o=n(17),r=n(1)("species");t.exports=function(t,e){var n,s=i(t).constructor;return void 0===s||void 0==(n=i(s)[r])?e:o(n)}},function(t,e,n){var i=n(26),o=n(20);t.exports=function(t){return function(e,n){var r,s,a=String(o(e)),c=i(n),u=a.length;return 0>c||c>=u?t?"":void 0:(r=a.charCodeAt(c),55296>r||r>56319||c+1===u||(s=a.charCodeAt(c+1))<56320||s>57343?t?a.charAt(c):r:t?a.slice(c,c+2):(r-55296<<10)+(s-56320)+65536)}}},function(t,e,n){var i=n(26),o=Math.max,r=Math.min;t.exports=function(t,e){return t=i(t),0>t?o(t+e,0):r(t,e)}},function(t,e,n){var i=n(19),o=n(1)("iterator"),r=n(12);t.exports=n(3).getIteratorMethod=function(t){return void 0!=t?t[o]||t["@@iterator"]||r[i(t)]:void 0}},function(t,e,n){"use strict";var i=n(5),o=n(18)(2);i(i.P+i.F*!n(41)([].filter,!0),"Array",{filter:function(t){return o(this,t,arguments[1])}})},function(t,e,n){"use strict";var i=n(5),o=n(18)(6),r="findIndex",s=!0;r in[]&&Array(1)[r](function(){s=!1}),i(i.P+i.F*s,"Array",{findIndex:function(t){return o(this,t,arguments.length>1?arguments[1]:void 0)}}),n(32)(r)},function(t,e,n){"use strict";var i=n(32),o=n(66),r=n(12),s=n(16);t.exports=n(36)(Array,"Array",function(t,e){this._t=s(t),this._i=0,this._k=e},function(){var t=this._t,e=this._k,n=this._i++;return!t||n>=t.length?(this._t=void 0,o(1)):"keys"==e?o(0,n):"values"==e?o(0,t[n]):o(0,[n,t[n]])},"values"),r.Arguments=r.Array,i("keys"),i("values"),i("entries")},function(t,e,n){"use strict";var i=n(5),o=n(18)(1);i(i.P+i.F*!n(41)([].map,!0),"Array",{map:function(t){return o(this,t,arguments[1])}})},function(t,e,n){var i=n(5);i(i.S,"Date",{now:function(){return(new Date).getTime()}})},function(t,e,n){var i=n(5);i(i.S+i.F,"Object",{assign:n(68)})},function(t,e,n){"use strict";var i=n(19),o={};o[n(1)("toStringTag")]="z",o+""!="[object z]"&&n(13)(Object.prototype,"toString",function(){return"[object "+i(this)+"]"},!0)},function(t,e,n){"use strict";var i,o,r,s=n(37),a=n(2),c=n(6),u=n(19),h=n(5),l=n(10),d=(n(4),n(17)),A=n(56),f=n(59),p=(n(76).set,n(78)),g=n(42).set,v=n(67),m="Promise",y=a.TypeError,b=a.process,w=a[m],b=a.process,x="process"==u(b),E=function(){},C=!!function(){try{var t=w.resolve(1),e=(t.constructor={})[n(1)("species")]=function(t){t(E,E)};return(x||"function"==typeof PromiseRejectionEvent)&&t.then(E)instanceof e}catch(i){}}(),D=function(t,e){return t===e||t===w&&e===r},M=function(t){var e;return l(t)&&"function"==typeof(e=t.then)?e:!1},S=function(t){return D(w,t)?new R(t):new o(t)},R=o=function(t){var e,n;this.promise=new t(function(t,i){if(void 0!==e||void 0!==n)throw y("Bad Promise constructor");e=t,n=i}),this.resolve=d(e),this.reject=d(n)},O=function(t){try{t()}catch(e){return{error:e}}},k=function(t,e){if(!t._n){t._n=!0;var n=t._c;v(function(){for(var i=t._v,o=1==t._s,r=0,s=function(e){var n,r,s=o?e.ok:e.fail,a=e.resolve,c=e.reject,u=e.domain;try{s?(o||(2==t._h&&L(t),t._h=1),s===!0?n=i:(u&&u.enter(),n=s(i),u&&u.exit()),n===e.promise?c(y("Promise-chain cycle")):(r=M(n))?r.call(n,a,c):a(n)):c(i)}catch(h){c(h)}};n.length>r;)s(n[r++]);t._c=[],t._n=!1,e&&!t._h&&N(t)})}},N=function(t){g.call(a,function(){var e,n,i,o=t._v;if(T(t)&&(e=O(function(){x?b.emit("unhandledRejection",o,t):(n=a.onunhandledrejection)?n({promise:t,reason:o}):(i=a.console)&&i.error&&i.error("Unhandled promise rejection",o)}),t._h=x||T(t)?2:1),t._a=void 0,e)throw e.error})},T=function(t){if(1==t._h)return!1;for(var e,n=t._a||t._c,i=0;n.length>i;)if(e=n[i++],e.fail||!T(e.promise))return!1;return!0},L=function(t){g.call(a,function(){var e;x?b.emit("rejectionHandled",t):(e=a.onrejectionhandled)&&e({promise:t,reason:t._v})})},I=function(t){var e=this;e._d||(e._d=!0,e=e._w||e,e._v=t,e._s=2,e._a||(e._a=e._c.slice()),k(e,!0))},U=function(t){var e,n=this;if(!n._d){n._d=!0,n=n._w||n;try{if(n===t)throw y("Promise can't be resolved itself");(e=M(t))?v(function(){var i={_w:n,_d:!1};try{e.call(t,c(U,i,1),c(I,i,1))}catch(o){I.call(i,o)}}):(n._v=t,n._s=1,k(n,!1))}catch(i){I.call({_w:n,_d:!1},i)}}};C||(w=function(t){A(this,w,m,"_h"),d(t),i.call(this);try{t(c(U,this,1),c(I,this,1))}catch(e){I.call(this,e)}},i=function(t){this._c=[],this._a=void 0,this._s=0,this._d=!1,this._v=void 0,this._h=0,this._n=!1},i.prototype=n(75)(w.prototype,{then:function(t,e){var n=S(p(this,w));return n.ok="function"==typeof t?t:!0,n.fail="function"==typeof e&&e,n.domain=x?b.domain:void 0,this._c.push(n),this._a&&this._a.push(n),this._s&&k(this,!1),n.promise},"catch":function(t){return this.then(void 0,t)}}),R=function(){var t=new i;this.promise=t,this.resolve=c(U,t,1),this.reject=c(I,t,1)}),h(h.G+h.W+h.F*!C,{Promise:w}),n(24)(w,m),n(77)(m),r=n(3)[m],h(h.S+h.F*!C,m,{reject:function(t){var e=S(this),n=e.reject;return n(t),e.promise}}),h(h.S+h.F*(s||!C),m,{resolve:function(t){if(t instanceof w&&D(t.constructor,this))return t;var e=S(this),n=e.resolve;return n(t),e.promise}}),h(h.S+h.F*!(C&&n(65)(function(t){w.all(t)["catch"](E)})),m,{all:function(t){var e=this,n=S(e),i=n.resolve,o=n.reject,r=O(function(){var n=[],r=0,s=1;f(t,!1,function(t){var a=r++,c=!1;n.push(void 0),s++,e.resolve(t).then(function(t){c||(c=!0,n[a]=t,--s||i(n))},o)}),--s||i(n)});return r&&o(r.error),n.promise},race:function(t){var e=this,n=S(e),i=n.reject,o=O(function(){f(t,!1,function(t){e.resolve(t).then(n.resolve,i)})});return o&&i(o.error),n.promise}})},function(t,e,n){"use strict";var i=n(79)(!0);n(36)(String,"String",function(t){this._t=String(t),this._i=0},function(){var t,e=this._t,n=this._i;return n>=e.length?{value:void 0,done:!0}:(t=i(e,n),this._i+=t.length,{value:t,done:!1})})},function(t,e,n){for(var i=n(84),o=n(13),r=n(2),s=n(9),a=n(12),c=n(1),u=c("iterator"),h=c("toStringTag"),l=a.Array,d=["NodeList","DOMTokenList","MediaList","StyleSheetList","CSSRuleList"],A=0;5>A;A++){var f,p=d[A],g=r[p],v=g&&g.prototype;if(v){v[u]||s(v,u,l),v[h]||s(v,h,p),a[p]=l;for(f in i)v[f]||o(v,f,i[f],!0)}}},function(t,e,n){e=t.exports=n(93)(),e.push([t.id,"#comic{position:relative;display:inline-block;outline:2px solid #000;overflow:hidden;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}#comic .frame{background:#fff;-webkit-transition:background-color .5s ease;transition:background-color .5s ease}#comic .frame:focus{outline:none}#comic .frame .content{opacity:1;-webkit-transition:opacity .5s ease;transition:opacity .5s ease}#comic .frame.loading{background:#fff}#comic .frame.loading .content{opacity:0}#comic .spinner{position:absolute;left:50%;top:45%;width:50px;height:52px;margin-left:-30px;margin-top:-26px;background-image:url("+n(107)+");background-size:contain;-webkit-animation-name:rotate;animation-name:rotate;-webkit-animation-iteration-count:infinite;animation-iteration-count:infinite;-webkit-animation-timing-function:linear;animation-timing-function:linear;-webkit-animation-duration:2s;animation-duration:2s;-webkit-transform-origin:56% 50%;transform-origin:56% 50%}@-webkit-keyframes rotate{0%{-webkit-transform:rotateY(0);transform:rotateY(0)}50%{-webkit-transform:rotateY(1turn);transform:rotateY(1turn)}to{-webkit-transform:rotateY(0);transform:rotateY(0)}}@keyframes rotate{0%{-webkit-transform:rotateY(0);transform:rotateY(0)}50%{-webkit-transform:rotateY(1turn);transform:rotateY(1turn)}to{-webkit-transform:rotateY(0);transform:rotateY(0)}}#comic .spinner-text{font-size:0;position:absolute;left:50%;top:53%;width:91px;height:17px;margin-left:-45.5px;background-image:url("+n(106)+");background-repeat:no-repeat}#comic .light{position:absolute;z-index:200}#comic .light .handle{position:absolute;-webkit-transform:translate(-50%,-50%);transform:translate(-50%,-50%)}#comic .light .handle.position{width:40px;height:44px;background-image:url("+n(105)+");background-size:contain}#comic .light .handle.rotate{width:0;height:0;background:transparent;border-left:10px solid transparent;border-right:10px solid transparent;border-top:10px solid red}#comic .light .handle.color{width:8px;height:8px;background:#000;border:2px solid #000;border-radius:100%}#comic .light.selected:before{content:'';display:block;position:absolute;left:-40px;right:-40px;top:-40px;bottom:-40px;border:2px solid red;border-radius:100%;opacity:.75}#comic .tools{position:absolute;right:0;z-index:100}#comic .tools button{display:block;width:40px;height:40px;padding:0;margin:10px;box-sizing:content-box;font-size:0;color:transparent;background:#ddd;background-size:contain;border:1px solid #333;border-radius:3px;-webkit-transition:opacity .15s ease;transition:opacity .15s ease;opacity:.75}#comic .tools button:hover{opacity:1}#comic .tools button:disabled{opacity:.25}#comic .tools button.add-light{background-image:url("+n(104)+")}#comic .tools button.remove{background-color:#d22;background-image:url("+n(108)+")}#comic .tools button.remove:disabled{background-color:#777}#comic #data{width:100%;height:10em}",""])},function(t,e){t.exports=function(){var t=[];return t.toString=function(){for(var t=[],e=0;e<this.length;e++){var n=this[e];n[2]?t.push("@media "+n[2]+"{"+n[1]+"}"):t.push(n[1])}return t.join("")},t.i=function(e,n){"string"==typeof e&&(e=[[null,e,""]]);for(var i={},o=0;o<this.length;o++){var r=this[o][0];"number"==typeof r&&(i[r]=!0)}for(o=0;o<e.length;o++){var s=e[o];"number"==typeof s[0]&&i[s[0]]||(n&&!s[2]?s[2]=n:n&&(s[2]="("+s[2]+") and ("+n+")"),t.push(s))}},t}},function(t,e,n){function i(){function t(t,e){var n,i=t._co;if(e&&e._co&&e._co.instance){if(n=e._co.instance,n.shouldComponentUpdate&&n.shouldComponentUpdate(i.props,n.state)===!1)return!1}else n=new u[i.component],n.getInitialState&&(n.state=n.getInitialState());i.instance=n,n._node=e||t,n.props=i.props,e&&(e._co=i);var o=n.render();t.appendChild(o)}function e(i,o){if(o._co&&o._co.component)return t(o,i)!==!1&&s(i,o,{onBeforeMorphEl:e,onBeforeNodeDiscarded:n,childrenOnly:!0}),!1;if(o._co&&o._co.events){var r,a=o._co.events;for(r in a)a.hasOwnProperty(r)&&(i[r]=a[r]);if(i._co&&i._co.events){var c=i._co.events;for(r in c)a.hasOwnProperty(r)||(i[r]=void 0)}i._co=i._co||{},i._co.events=a}}function n(t){i(t,function(t){t._co&&t._co.instance&&t._co.instance.componentWillUnmount&&t._co.instance.componentWillUnmount()})}function i(t,e){e(t);for(var n=0;n<t.childNodes.length;n++)i(t.childNodes[n],e)}function a(o,r,a){s(o,r,{onBeforeMorphEl:e,onBeforeNodeDiscarded:n,childrenOnly:a}),i(o,function(e){var n=e._co;n&&n.component&&!n.instance&&(t(e),n.instance.componentDidMount&&n.instance.componentDidMount())})}var c=o(function(t,e,n){var i,o,s;if(u.hasOwnProperty(t)){i=r.createElement("co-"+t.toLowerCase(),{},[]);for(o in e){s=e[o];var a;"string"==typeof s&&("true"===s?e[o]=!0:"false"===s?e[o]=!1:""!==s&&(a=Number(s),isNaN(a)||(e[o]=a)))}i._co={component:t,props:e},n&&n.length&&(i._co.props.children=n)}else{var c={},h={};for(o in e)s=e[o],"on"===o.slice(0,2)&&(o=o.toLowerCase(),h[o]=s),c[o]=s;i=r.createElement(t,c,n),i._co={events:h}}return i}),u=c._components={},h={};return h.setState=function(t){var e,n={};for(e in this.state)this.state.hasOwnProperty(e)&&(n[e]=this.state[e]);for(e in t)t.hasOwnProperty(e)&&(n[e]=t[e]);var i=this.shouldComponentUpdate&&this.shouldComponentUpdate(this.props,n)!==!1;this.state=n,i!==!1&&a(this._node.firstChild,this.render())},h.getDOMNode=function(){return this._node},c.component=function(t,e){e.setState=h.setState,e.getDOMNode=h.getDOMNode;var n=function(){};n.prototype=e,u[t]=n},c.render=function(t,e){var n=r.createElement("div",{},[t]);a(e,n,!0)},c}var o=n(44),r=n(95),s=n(98);t.exports=i(),t.exports.scope=i},function(t,e,n){function i(t,e,n){function i(t){if(Array.isArray(t))for(var e=0;e<t.length;e++){var n=t[e];if(Array.isArray(n))i(n);else{if(("number"==typeof n||"boolean"==typeof n||n instanceof Date||n instanceof RegExp)&&(n=n.toString()),"string"==typeof n){if(r.lastChild&&"#text"===r.lastChild.nodeName){r.lastChild.nodeValue+=n;continue}n=o.createTextNode(n)}n&&n.nodeType&&r.appendChild(n)}}}var r;-1!==c.indexOf(t)&&(e.namespace=s);var u=!1;e.namespace&&(u=e.namespace,delete e.namespace),r=u?o.createElementNS(u,t):o.createElement(t);for(var h in e)if(e.hasOwnProperty(h)){var l=h.toLowerCase(),d=e[h];if("classname"===l&&(l="class",h="class"),a[l])if("true"===d)d=l;else if("false"===d)continue;"on"===l.slice(0,2)?r[h]=d:u?r.setAttributeNS(null,h,d):r.setAttribute(h,d)}return i(n),r}var o=n(96),r=n(44),s="http://www.w3.org/2000/svg",a={autofocus:1,checked:1,defaultchecked:1,disabled:1,formnovalidate:1,indeterminate:1,readonly:1,required:1,willvalidate:1},c=["svg","altGlyph","altGlyphDef","altGlyphItem","animate","animateColor","animateMotion","animateTransform","circle","clipPath","color-profile","cursor","defs","desc","ellipse","feBlend","feColorMatrix","feComponentTransfer","feComposite","feConvolveMatrix","feDiffuseLighting","feDisplacementMap","feDistantLight","feFlood","feFuncA","feFuncB","feFuncG","feFuncR","feGaussianBlur","feImage","feMerge","feMergeNode","feMorphology","feOffset","fePointLight","feSpecularLighting","feSpotLight","feTile","feTurbulence","filter","font","font-face","font-face-format","font-face-name","font-face-src","font-face-uri","foreignObject","glyph","glyphRef","hkern","image","line","linearGradient","marker","mask","metadata","missing-glyph","mpath","path","pattern","polygon","polyline","radialGradient","rect","set","stop","switch","symbol","text","textPath","title","tref","tspan","use","view","vkern"];t.exports=r(i),t.exports.createElement=i},function(t,e,n){(function(e){var i="undefined"!=typeof e?e:"undefined"!=typeof window?window:{},o=n(110);if("undefined"!=typeof document)t.exports=document;else{var r=i["__GLOBAL_DOCUMENT_CACHE@4"];r||(r=i["__GLOBAL_DOCUMENT_CACHE@4"]=o),t.exports=r}}).call(e,function(){return this}())},function(t,e){function n(t){return function(e,n,o){for(var r in n)r in i&&(n[i[r]]=n[r],delete n[r]);return t(e,n,o)}}t.exports=n;var i={"class":"className","for":"htmlFor","http-equiv":"httpEquiv"}},function(t,e){function n(t){for(var e in t)if(t.hasOwnProperty(e))return!1;return!0}function i(t){!u&&document.createRange&&(u=document.createRange(),u.selectNode(document.body));var e;return u&&u.createContextualFragment?e=u.createContextualFragment(t):(e=document.createElement("body"),e.innerHTML=t),e.childNodes[0]}function o(){}function r(t,e){var n,i,o,r,s=e.attributes,a={};for(n=s.length-1;n>=0;n--)i=s[n],i.specified!==!1&&(o=i.name,r=i.value,a[o]=!0,t.getAttribute(o)!==r&&t.setAttribute(o,r));for(s=t.attributes,n=s.length-1;n>=0;n--)i=s[n],i.specified!==!1&&(o=i.name,a.hasOwnProperty(o)||t.removeAttribute(o))}function s(t,e){for(var n=t.firstChild;n;){var i=n.nextSibling;e.appendChild(n),n=i}return e}function a(t){return t.id}function c(t,e,c){function u(t,e){var n=g(t);if(n?f[n]=t:e||v(t),1===t.nodeType)for(var i=t.firstChild;i;)u(i,e||n),i=i.nextSibling}function h(t){if(1===t.nodeType)for(var e=t.firstChild;e;)g(e)||(v(e),h(e)),e=e.nextSibling}function l(t,e,n){b(t)!==!1&&(e.removeChild(t),n?g(t)||(v(t),h(t)):u(t))}function A(t,e,n,i){var o=g(e);if(o&&delete f[o],!i){if(m(t,e)===!1)return;if(r(t,e),y(t,e)===!1)return}if("TEXTAREA"!=t.tagName){var s,a,c,u,h,v=e.firstChild,b=t.firstChild;t:for(;v;){for(c=v.nextSibling,s=g(v);b;){var w=g(b);if(a=b.nextSibling,!n&&w&&(h=p[w]))h.parentNode.replaceChild(b,h),A(b,h,n),b=a;else{var E=b.nodeType;if(E===v.nodeType){var C=!1;if(1===E?(b.tagName===v.tagName&&(w||s?s===w&&(C=!0):C=!0),C&&A(b,v,n)):3===E&&(C=!0,b.nodeValue=v.nodeValue),C){v=c,b=a;continue t}}l(b,t,n),b=a}}s&&((u=f[s])?(A(u,v,!0),v=u):p[s]=v),t.appendChild(v),1===v.nodeType&&(s||v.firstChild)&&x.push(v),v=c,b=a}for(;b;)a=b.nextSibling,l(b,t,n),b=a}var D=d[t.tagName];D&&D(t,e)}c||(c={}),"string"==typeof e&&(e=i(e));var f={},p={},g=c.getNodeKey||a,v=c.onNodeDiscarded||o,m=c.onBeforeMorphEl||o,y=c.onBeforeMorphElChildren||o,b=c.onBeforeNodeDiscarded||o,w=c.childrenOnly===!0,x=[],E=t,C=E.nodeType,D=e.nodeType;if(!w)if(1===C)1===D?t.tagName!==e.tagName&&(v(t),E=s(t,document.createElement(e.tagName))):E=e;else if(3===C){if(3===D)return E.nodeValue=e.nodeValue,E;E=e}if(E===e)v(t);else{A(E,e,!1,w);var M=function(t){for(var e=t.firstChild;e;){var i=e.nextSibling,o=g(e);if(o){var r=f[o];if(r&&e.tagName===r.tagName){if(e.parentNode.replaceChild(r,e),A(r,e,!0),e=i,n(f))return!1;continue}}1===e.nodeType&&M(e),e=i}};if(!n(f))t:for(;x.length;){var S=x;x=[];for(var R=0;R<S.length;R++)if(M(S[R])===!1)break t}for(var O in f)if(f.hasOwnProperty(O)){var k=f[O];v(k),h(k)}}return!w&&E!==t&&t.parentNode&&t.parentNode.replaceChild(E,t),E}var u,h,l="undefined"!=typeof document?document.body||document.createElement("div"):{};h=l.hasAttribute?function(t,e){return t.hasAttribute(e)}:function(t,e){return t.getAttributeNode(e)};var d={OPTION:function(t,e){(t.selected=e.selected)?t.setAttribute("selected",""):t.removeAttribute("selected","")},INPUT:function(t,e){t.checked=e.checked,t.value!=e.value&&(t.value=e.value),h(e,"checked")||t.removeAttribute("checked"),h(e,"value")||t.removeAttribute("value")},TEXTAREA:function(t,e){var n=e.value;t.value!=n&&(t.value=n),t.firstChild&&(t.firstChild.nodeValue=n)}};t.exports=c},function(t,e){/** @license + * eventsource.js + * Available under MIT License (MIT) + * https://github.com/Yaffle/EventSource/ + */+!function(t){"use strict";function e(){this.data={}}function n(){this.listeners=new e}function i(t){h(function(){throw t},0)}function o(t){this.type=t,this.target=void 0}function r(t,e){o.call(this,t),this.data=e.data,this.lastEventId=e.lastEventId}function s(t,e){var n=t;return n!==n&&(n=e),M>n?M:n>S?S:n}function a(t,e,n){try{"function"==typeof e&&e.call(t,n)}catch(o){i(o)}}function c(e,i){function c(){G=y,void 0!=_&&(_.abort(),_=void 0),0!==F&&(l(F),F=0),0!==j&&(l(j),j=0),L.readyState=y}function u(t){var n="";if(G===m||G===v)try{n=_.responseText}catch(i){}var u=void 0,d=!1;if(G===v){var A=0,f="",p=void 0;if("contentType"in _)""!==t&&"error"!==t&&(A=200,f="OK",p=_.contentType);else try{A=_.status,f=_.statusText,p=_.getResponseHeader("Content-Type")}catch(i){A=0,f="",p=void 0}if(void 0==p&&(p=""),0===A&&""===f&&"load"===t&&""!==n&&(A=200,f="OK",""===p)){var M=/^data\:([^,]*?)(?:;base64)?,[\S]*$/.exec(e);void 0!=M&&(p=M[1])}if(200===A&&D.test(p)){if(G=m,U=!0,I=k,L.readyState=m,u=new o("open"),L.dispatchEvent(u),a(L,L.onopen,u),G===y)return}else if(0!==A&&(200!==A||""!==p)){var R="";R=200!==A?"EventSource's response has a status "+A+" "+f.replace(/\s+/g," ")+" that is not 200. Aborting the connection.":"EventSource's response has a Content-Type specifying an unsupported type: "+p.replace(/\s+/g," ")+". Aborting the connection.",h(function(){throw new Error(R)},0),d=!0}}if(G===m){n.length>P&&(U=!0);for(var O=P-1,B=n.length,j="\n";++O<B;)if(j=n.charAt(O),K===b&&"\n"===j)K=w;else if(K===b&&(K=w),"\r"===j||"\n"===j){if("data"===z?Q.push(q):"id"===z?Y=q:"event"===z?H=q:"retry"===z?(k=s(Number(q),k),I=k):"heartbeatTimeout"===z&&(N=s(Number(q),N),0!==F&&(l(F),F=h(J,N))),q="",z="",K===w){if(0!==Q.length&&(T=Y,""===H&&(H="message"),u=new r(H,{data:Q.join("\n"),lastEventId:Y}),L.dispatchEvent(u),"message"===H&&a(L,L.onmessage,u),G===y))return;Q.length=0,H=""}K="\r"===j?b:w}else K===w&&(K=x),K===x?":"===j?K=E:z+=j:K===E?(" "!==j&&(q+=j),K=C):K===C&&(q+=j);P=B}G!==m&&G!==v||!("load"===t||"error"===t||d||P>1048576||0===F&&!U)?0===F&&(U=!1,F=h(J,N)):(d?c():(""!==t||0!==F||U||h(function(){throw new Error("No activity within "+N+" milliseconds. Reconnecting.")},0),G=g,_.abort(),0!==F&&(l(F),F=0),I>16*k&&(I=16*k),I>S&&(I=S),F=h(J,I),I=2*I+1,L.readyState=v),u=new o("error"),L.dispatchEvent(u),a(L,L.onerror,u))}function d(){u("progress")}function A(){u("load")}function M(){u("error")}function R(){u(4===_.readyState?0===_.status?"error":"load":"progress")}e=e.toString();var O=f&&void 0!=i&&Boolean(i.withCredentials),k=s(1e3,0),N=s(45e3,0),T="",L=this,I=k,U=!1,B=void 0!=i&&void 0!=i.Transport?i.Transport:p,_=new B,F=0,j=0,P=0,G=g,Q=[],Y="",H="",J=void 0,K=w,z="",q="";"readyState"in _&&void 0!=t.opera&&(j=h(function Z(){3===_.readyState&&u("progress"),j=h(Z,500)},0)),J=function(){if(F=0,G!==g)return void u("");if((!("ontimeout"in _)||"sendAsBinary"in _||"mozAnon"in _)&&void 0!=t.document&&void 0!=t.document.readyState&&"complete"!==t.document.readyState)return void(F=h(J,4));_.onload=A,_.onerror=M,"onabort"in _&&(_.onabort=M),"onprogress"in _&&(_.onprogress=d),"onreadystatechange"in _&&(_.onreadystatechange=R),U=!1,F=h(J,N),P=0,G=v,Q.length=0,H="",Y=T,q="",z="",K=w;var n=e.slice(0,5);n="data:"!==n&&"blob:"!==n?e+((-1===e.indexOf("?",0)?"?":"&")+"lastEventId="+encodeURIComponent(T)+"&r="+(Math.random()+1).toString().slice(2)):e,_.open("GET",n,!0),"withCredentials"in _&&(_.withCredentials=O),"responseType"in _&&(_.responseType="text"),"setRequestHeader"in _&&_.setRequestHeader("Accept","text/event-stream"),_.send(void 0)},n.call(this),this.close=c,this.url=e,this.readyState=v,this.withCredentials=O,this.onopen=void 0,this.onmessage=void 0,this.onerror=void 0,J()}function u(){this.CONNECTING=v,this.OPEN=m,this.CLOSED=y}var h=t.setTimeout,l=t.clearTimeout;e.prototype.get=function(t){return this.data[t+"~"]},e.prototype.set=function(t,e){this.data[t+"~"]=e},e.prototype["delete"]=function(t){delete this.data[t+"~"]},n.prototype.dispatchEvent=function(t){t.target=this;var e=t.type.toString(),n=this.listeners,o=n.get(e);if(void 0!=o)for(var r=o.length,s=-1,a=void 0;++s<r;){a=o[s];try{a.call(this,t)}catch(c){i(c)}}},n.prototype.addEventListener=function(t,e){t=t.toString();var n=this.listeners,i=n.get(t);void 0==i&&(i=[],n.set(t,i));for(var o=i.length;--o>=0;)if(i[o]===e)return;i.push(e)},n.prototype.removeEventListener=function(t,e){t=t.toString();var n=this.listeners,i=n.get(t);if(void 0!=i){for(var o=i.length,r=[],s=-1;++s<o;)i[s]!==e&&r.push(i[s]);0===r.length?n["delete"](t):n.set(t,r)}},r.prototype=o.prototype;var d=t.XMLHttpRequest,A=t.XDomainRequest,f=void 0!=d&&void 0!=(new d).withCredentials,p=f||void 0!=d&&void 0==A?d:A,g=-1,v=0,m=1,y=2,b=3,w=4,x=5,E=6,C=7,D=/^text\/event\-stream;?(\s*charset\=utf\-8)?$/i,M=1e3,S=18e6;u.prototype=n.prototype,c.prototype=new u,u.call(c),f&&(c.prototype.withCredentials=void 0);var R=function(){return void 0!=t.EventSource&&"withCredentials"in t.EventSource.prototype};void 0!=p&&(void 0==t.EventSource||f&&!R())&&(t.NativeEventSource=t.EventSource,t.EventSource=c)}("undefined"!=typeof window?window:this)},function(t,e,n){function i(t){if(!t)return"";var e=Object.keys(t),n=e.map(function(e){var n=o(e),i=n.concat(":").concat(t[e]);return i});return n.join(";")}/*!+ * Dependencies+ */+var o=n(101);/*!+ * Exports+ */+t.exports=i},function(t,e){/*!+ * dashify <https://github.com/jonschlinkert/dashify>+ *+ * Copyright (c) 2015 Jon Schlinkert.+ * Licensed under the MIT license.+ */+"use strict";t.exports=function(t){if("string"!=typeof t)throw new TypeError("dashify expects a string.");return t=t.replace(/[A-Z]/g,"-$&"),t=t.replace(/[ \t]/g,"-"),t.toLowerCase()}},function(t,e,n){function i(t,e){for(var n=0;n<t.length;n++){var i=t[n],o=A[i.id];if(o){o.refs++;for(var r=0;r<o.parts.length;r++)o.parts[r](i.parts[r]);for(;r<i.parts.length;r++)o.parts.push(u(i.parts[r],e))}else{for(var s=[],r=0;r<i.parts.length;r++)s.push(u(i.parts[r],e));A[i.id]={id:i.id,refs:1,parts:s}}}}function o(t){for(var e=[],n={},i=0;i<t.length;i++){var o=t[i],r=o[0],s=o[1],a=o[2],c=o[3],u={css:s,media:a,sourceMap:c};n[r]?n[r].parts.push(u):e.push(n[r]={id:r,parts:[u]})}return e}function r(t,e){var n=g(),i=y[y.length-1];if("top"===t.insertAt)i?i.nextSibling?n.insertBefore(e,i.nextSibling):n.appendChild(e):n.insertBefore(e,n.firstChild),y.push(e);else{if("bottom"!==t.insertAt)throw new Error("Invalid value for parameter 'insertAt'. Must be 'top' or 'bottom'.");n.appendChild(e)}}function s(t){t.parentNode.removeChild(t);var e=y.indexOf(t);e>=0&&y.splice(e,1)}function a(t){var e=document.createElement("style");return e.type="text/css",r(t,e),e}function c(t){var e=document.createElement("link");return e.rel="stylesheet",r(t,e),e}function u(t,e){var n,i,o;if(e.singleton){var r=m++;n=v||(v=a(e)),i=h.bind(null,n,r,!1),o=h.bind(null,n,r,!0)}else t.sourceMap&&"function"==typeof URL&&"function"==typeof URL.createObjectURL&&"function"==typeof URL.revokeObjectURL&&"function"==typeof Blob&&"function"==typeof btoa?(n=c(e),i=d.bind(null,n),o=function(){s(n),n.href&&URL.revokeObjectURL(n.href)}):(n=a(e),i=l.bind(null,n),o=function(){s(n)});return i(t),function(e){if(e){if(e.css===t.css&&e.media===t.media&&e.sourceMap===t.sourceMap)return;i(t=e)}else o()}}function h(t,e,n,i){var o=n?"":i.css;if(t.styleSheet)t.styleSheet.cssText=b(e,o);else{var r=document.createTextNode(o),s=t.childNodes;s[e]&&t.removeChild(s[e]),s.length?t.insertBefore(r,s[e]):t.appendChild(r)}}function l(t,e){var n=e.css,i=e.media;if(i&&t.setAttribute("media",i),t.styleSheet)t.styleSheet.cssText=n;else{for(;t.firstChild;)t.removeChild(t.firstChild);t.appendChild(document.createTextNode(n))}}function d(t,e){var n=e.css,i=e.sourceMap;i&&(n+="\n/*# sourceMappingURL=data:application/json;base64,"+btoa(unescape(encodeURIComponent(JSON.stringify(i))))+" */");var o=new Blob([n],{type:"text/css"}),r=t.href;t.href=URL.createObjectURL(o),r&&URL.revokeObjectURL(r)}var A={},f=function(t){var e;return function(){return"undefined"==typeof e&&(e=t.apply(this,arguments)),e}},p=f(function(){return/msie [6-9]\b/.test(window.navigator.userAgent.toLowerCase())}),g=f(function(){return document.head||document.getElementsByTagName("head")[0]}),v=null,m=0,y=[];t.exports=function(t,e){e=e||{},"undefined"==typeof e.singleton&&(e.singleton=p()),"undefined"==typeof e.insertAt&&(e.insertAt="bottom");var n=o(t);return i(n,e),function(t){for(var r=[],s=0;s<n.length;s++){var a=n[s],c=A[a.id];c.refs--,r.push(c)}if(t){var u=o(t);i(u,e)}for(var s=0;s<r.length;s++){var c=r[s];if(0===c.refs){for(var h=0;h<c.parts.length;h++)c.parts[h]();delete A[c.id]}}}};var b=function(){var t=[];return function(e,n){return t[e]=n,t.filter(Boolean).join("\n")}}()},function(t,e,n){var i=n(92);"string"==typeof i&&(i=[[t.id,i,""]]);n(102)(i,{});i.locals&&(t.exports=i.locals)},function(t,e){t.exports="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFAAAABQCAMAAAC5zwKfAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAYUExURUxpcQAAABYWFgAAAAAAAPv7+39/f8fHx/ktK9QAAAAEdFJOUwCK20hF+i26AAABpklEQVRYw+2Xya7DIAxF4wH4/z9+GNI2ScNg4yd1kSt10SyOjhnNtlmCzAEZQ+RTkDZbgDlmVgx8DdiA2eVOUGIVTJmVjoJxwZBEUAo+CMZsvCoY0pEXOaUFwXgWxBDqf5tezVEwiWAuWi9I+LHCU8HywwXcKblgzMLohKszkn9gHLxrsBSsFmzplfUtBTO56FVB7Qx3cGXTKWeYkAeJmoLHOCl7WnAKJyFf3JzgPG4KqMFNnINK3AioxvWBBlwXCMyeQBuuCTRV2wFa9RpAsuPuFzZ4AxdGsHm6mpHgvWzAeZ/0LxQLEobtkC9QLwmLN6eptVYhyftOIY+OwfKacAfCsU/1AOYFmRI6AkUx3L2X7C8yaXhDin5AKTqEoSWoms0JoqZ9xZ3YRcJwMdOJGAuxM5SoAQoRK7JpqQPW87EyG6tSCcxfsTJTS1ILzJZg7GChcx4BOgOLqeSLvAC8PyrXgZcx/QbCK3sx+P4whcT5sxmnbh0n4OcVDV7Al6QfcJ9FcgQWSU/DIkn3G6Bsgn2U3x+2xYCqt3iAD/B/gIQlfsAnP5A/fAAqEID2icEAAAAASUVORK5CYII="},function(t,e){t.exports="data:image/png;base64,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"},function(t,e){t.exports="data:image/gif;base64,R0lGODlhWwARAPfOAPf39wEBAR4eHgICAiIiIpmZmf39/YmJib6+vvb29oKCgkRERLq6uq2trbu7uz4+PgMDA7CwsAYGBvz8/BQUFAsLC/Hx8d3d3Y2NjaqqqoGBgRsbG19fX2hoaLOzs+vr6zQ0NKysrCkpKRcXFw4ODh8fHxwcHMLCwnp6ehMTE7S0tM/Pz4uLi1JSUpOTk/v7+9fX13FxccrKyuPj4woKCrGxsd/f3/X19VpaWu3t7ZWVlWZmZkFBQUBAQHR0dGVlZdnZ2TMzMxYWFjAwMBgYGNXV1d7e3gUFBRAQELKyspubm9PT0/n5+dzc3Ly8vEpKSgcHB1xcXBkZGSUlJUNDQ5aWloCAgMDAwBEREWxsbIiIiM3NzVVVVcjIyEtLS15eXg8PD/r6+qenp6Ghoejo6Gtra+Li4nZ2djIyMmBgYFZWVioqKisrK0ZGRoaGhjs7O/T09Lm5uQ0NDU5OTs7Oznl5eZiYmNLS0n5+fiEhIX19fa+vr8zMzO/v75GRkSMjIygoKGJiYtHR0SwsLLi4uMHBwaurq/Dw8CcnJ9TU1JycnKioqDk5OQgICIqKiubm5gwMDFdXV+Xl5cvLy56eni8vL5qammFhYSAgIFBQUMPDwzc3N8fHx3BwcL29vdDQ0FFRUQkJCYSEhPPz86SkpDY2NmRkZExMTKCgoE1NTerq6p+fn3t7e3Jyctvb2+Tk5EVFRampqYeHh6Ojo1hYWKampsnJyTw8PNbW1kJCQqWlpTo6OsbGxmdnZyYmJj09PX9/f4ODg2lpaeDg4Hd3dzExMezs7Nra2klJSVRUVHV1dTg4OBISEgAAAP///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH/C05FVFNDQVBFMi4wAwEAAAAh/wtYTVAgRGF0YVhNUDw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMDY3IDc5LjE1Nzc0NywgMjAxNS8wMy8zMC0yMzo0MDo0MiAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTUgKE1hY2ludG9zaCkiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6NDA1QzFEMDNGMjY3MTFFNTkwODJDMzUxMTUyRTJDNjMiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6NDA1QzFEMDRGMjY3MTFFNTkwODJDMzUxMTUyRTJDNjMiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo0MDVDMUQwMUYyNjcxMUU1OTA4MkMzNTExNTJFMkM2MyIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo0MDVDMUQwMkYyNjcxMUU1OTA4MkMzNTExNTJFMkM2MyIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PgH//v38+/r5+Pf29fTz8vHw7+7t7Ovq6ejn5uXk4+Lh4N/e3dzb2tnY19bV1NPS0dDPzs3My8rJyMfGxcTDwsHAv769vLu6ubi3trW0s7KxsK+urayrqqmop6alpKOioaCfnp2cm5qZmJeWlZSTkpGQj46NjIuKiYiHhoWEg4KBgH9+fXx7enl4d3Z1dHNycXBvbm1sa2ppaGdmZWRjYmFgX15dXFtaWVhXVlVUU1JRUE9OTUxLSklIR0ZFRENCQUA/Pj08Ozo5ODc2NTQzMjEwLy4tLCsqKSgnJiUkIyIhIB8eHRwbGhkYFxYVFBMSERAPDg0MCwoJCAcGBQQDAgEAACH5BAUyAM4ALAAAAABbABEAAAj/AJ0JdLZI08CDCA8hREhmS4KFHx51MeShUA6ENgwsFAgjBRoLCG8Y20iSJJ1mzQCUXCEiwBNVG+tAaEYhzkE8KHOifEBH4IlmeQS+QpgE5UWBQOYcabbBFK+SUJ3pajagpJEUOYfEQqhEZwUZA3GgDCCAQk4aCJw5aibAWZ1mHQ42QDmQjxydVA9EJcmALslTzYigyPnggkAmAppRmWSi2QiQzno1m3NDIABCif8Y0NEMkLNSzQIIGqjCbxESzUqE6IIBRDMITvYu7NuMpJEAzUI487AAJRKwhEJ/cmZLQjMrAok1W4DwZ7Mrs5phcda7WaCBc2sb2MXUsMAJT5rV/5KNkJTfhRqaSdEoMIKQzs5iNBMxsEwzEiD1NAOx8E8zDCFI58wDKEHgnQd01YBSEggB0Ad5CLHQTCMkUdFMGQhlx8cbcA1kA24NOHNAM0Es1JsWGTQDxYA5xSDQFSiRwUEzjEC4VxbNCEFSCc0UgJABFTRjyQjN6HDQEM384IwWzayxEIEaxIESAL3R0IwERTiTCEp3INKMBgfBIQgud7xgo0Bt7LcRACilhRABzaAwQDNuKKDGLWwg0cwUzlTRDAEL+dJMMA6gFMYmzezQGA/OmNFmYj4KxIlxKJGAwZlsNOPFRsegBMNCqHWCl04D3FDAnwjlMKcKhdYGSzNpjP+Bkg4JoLRHY3oJNANur014JpFcbGSDpwitgBIGKNHQQwcK2IFsMwycCuhBKTbzQavORNEMMs700EwoTQRpRwvNDDLBQEuY4YwIip0JSTNfbFRrM24OlEYzYASYEkKNKUAJTQj90kwxzmDrQzNoODOMnj0QQadzPiCEAEoZnInSDiRh0owoB6mAmxVdpbAQKM2kkqIEKrWHkhgFo2SBhNM2MCdKEePYjAID9QFnEGbaaFYkA0kixhkcBOIHUx84A4cCUDSZAE5DLDTYIAg2s0V7d5XoDG0JnErEQF2h5KIzMzajxhIy8PDaCmc6w+EIX4AQpE57GCeFF2CglIcRZDeDE8VCRwvhBEot6JELSgJ4Vy0DI5ZwkCwoufAdF6PmemZReB1RSRs7zODCUigN8EPSzvzQDAsLeYISKqNOkaXKzSwRwQYtIDTGGUwcVEViqUXatjM1XEILK4qckPJAQGjQgQszHDRBE2EsxEQrwoyyCjA4ZIJDDT0L1IQIjJ7pChC/BwQAIfkEBTIAzgAsTAAIAAYACAAACCIADTkbOPAJwYHKDjorpHCCwoEqoAggiKJZMwIDnaRIdiEgACH5BAUyAM4ALFEADAAGAAUAAAgfAJ0JfPFCoLMVFCooOuQsSrNmUww4O9AMkQuDFiQGBAAh+QQFMgDOACxWAA0ABQADAAAIFABhOGMhZdkiZ4yaMUvkDEELRwEBADs="},function(t,e){t.exports="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABoCAMAAAAw/qUdAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAYUExURQAAAExpcQAAAAAAAAAAAAAAAAAAAAAAAO1JYJEAAAAIdFJOU/8A4rxAaZMcjWgm3wAABTlJREFUaN69Wtd2hSAQxK3//8dBAWkLCibhKedGGbbNFnTH5hLHr591mxjs3HuUXRB0CyjrIAyAFEDeorj32gG5RXACLi7RXwOJe+qhrlu/BpI21ChJueA7CDtvgaPYsBfFK+wUkLZBLvWgQt6QOxA56HpqG0SiBFkUZ1gFvoG0JgDqMeJv8Gsg1hKJWvsKAjwBeYrMJ5BbO/ws0L4LD3Y8LIGQaQ+E3NKSvYgfoPDg901aITA3s/2ON0AYAThFZB0c7wWZgyQ2KQ4NuOFh7om36oVetgkGrbsw9QLARR9FzIBfJ7j4fClbcQK1tilJA00ugYsciZl3gvFWDZITqkn3ziX+KYSUkFE3vOvWNSJYIBjkLSylG9yFIqw1HZcgyb+L33QVxDsYaONqUsQNJjcXmcTjBMTz3aUubFyNb6kogxfJDBZAzrdUbvk578FlXCQJlcZR7+bVbgahXpCqEKP8xKIkKOmQolnj2DlX3PlyQFkwvBJHFgzRHrgYKMoonPQfBTj0DHrSlTjhTLQhQOi4A1r9bkc2chBMN1j49sigscYvsyk4Poq4CqIlb4lZuGn2V9hJWj5PnV2CaowzYCEdFhhM+Fh9uwEpwiUQIxJhxTI+RLXUJ74o8d1ID2eRHawjHflfHmCnLfAlAT9LItWhapfMBAasJsjlAPQIcpvUG1GbtF1ujO8zvRsl3TOrYpe44V2J9xakoo4NEHZXi9aA4Nnf6gOIvANJPUCEcY0AfChMQN6KUsema7J5ptsGJIqu2FSQVKQXsHrWEkSGjQjGchgDiQc6Fm4rJTtqBiDdcQCx9k1ln8zkBsHHirJTV3DeghtrHZ/qOktFsUqXkcu4vqVSfTYomoJnkb34kjnZHZ0oDP07Lz23sE/4swtGcZ9XUWaGQqyP+HWU2RtoeFfIFiM9DNxUYA6CLYgSkZooeJbTaM07ZvEPhgtfL8Dw8fJ/7N54BuQ2wtXd4ao7Tf9HV/rOIGAo9A3/TUPpHlA5a7iBj+FoG0+k4sabh11LKu08yDauHKbuuCr4ITmx6/gRasfiAQg/GQhCyVzYRDpOsWyTz2+BGPxIQWGurOiIpu8WJSm/mB5hko0qEOxKNmmyA9Yaf0c7UIF4URr9Vl4AWa1wJuJnmPBIAqFmiJVH2hQHG/H8RfKeK4wxWUVu7+Ko6jYnaekYmKmBD30YFnHNwoJ+16s+CAEEhSdjKIdvm51akqqoASMQMZpAEkh0WjgHr74x8G2JhPwZqpJT9ZDUmHWb7RHsSJXPBAk4tTWuaPLP7c72B8MeRhlCPZv6Q3MYohKMupQcbnB4Rost+xV1KKM6Cga1NZtFZykjQVnBw6yQIjea1qDZOd5sHXhV7NoeUmKWZg5Gdk/QtQ4UGlFu5AygkPiG6+J20usSI4/6E2x7JDErBaiK89UrQGxVfDF1b5uix8djC4S6i1coYytqSGeDgSkIZ/GVLs/nm3ax1FG2CK1fyyLUSVKwcdzEWuIWtWUMC7Av3dm8Q6NtkEwZmM8cUGjSSq6BcJE9i6ED9ZdotA9SFqPFtBGtsfouCJU3PXI0E1J+nGytgYTqJ89hqbt1/GCTGyNGIdYMAiuX12MXlpMuKfkrhlQEYKSADyBhApkkOAf1vjtSy/2+f+0xdlRcF8VN702s48rspmQPBCcgf6gu+l8Q+QoiQ73reji6+R2jTMz13vJuepOJ88vH798S+RpZHu5qQb+CPN15LOjrG4j8EYhupOD177tgPQWvg8g/GL6smT7HyeOnLAB/+mGfsqB1zTVcP58nJEXsQyUBAAAAAElFTkSuQmCC"},function(t,e){t.exports="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFAAAABQCAMAAAC5zwKfAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAYUExURUxpcf///////////////////////////95C5tQAAAAHdFJOUwABnpj+bGXDMIZhAAAA4ElEQVRYw+3XwQ6EIAxFUUCZ/v8fz8QYk1Gghd6V6VvTk6gIbUqRSCTynnxK1hflrZq9XXQxb7JXsyeq+PPEKB6eJh6eTTy9sXh6FvHyRuLlGcQioor5b1EyvOyxaFgytXzSUwumPaVkwRsWLXmDskWvW7jsdUodXrPY5TXKnd4DcHt30e/dRMDrieteW/R4LdHnPUWvdxf9Hg7Sj0x/FHrb0Bub/vXow4E+vugDlr4C6EuKvkbpi55uRehmiW7n6IYTb4nxph0fK/DBhx/N+OGRH29TqqYNW2qKRCKR9+QLfbEaz7uhlPAAAAAASUVORK5CYII="},function(t,e){!function(t){"use strict";function e(t){if("string"!=typeof t&&(t=String(t)),/[^a-z0-9\-#$%&'*+.\^_`|~]/i.test(t))throw new TypeError("Invalid character in header field name");return t.toLowerCase()}function n(t){return"string"!=typeof t&&(t=String(t)),t}function i(t){this.map={},t instanceof i?t.forEach(function(t,e){this.append(e,t)},this):t&&Object.getOwnPropertyNames(t).forEach(function(e){this.append(e,t[e])},this)}function o(t){return t.bodyUsed?Promise.reject(new TypeError("Already read")):void(t.bodyUsed=!0)}function r(t){return new Promise(function(e,n){t.onload=function(){e(t.result)},t.onerror=function(){n(t.error)}})}function s(t){var e=new FileReader;return e.readAsArrayBuffer(t),r(e)}function a(t){var e=new FileReader;return e.readAsText(t),r(e)}function c(){return this.bodyUsed=!1,this._initBody=function(t){if(this._bodyInit=t,"string"==typeof t)this._bodyText=t;else if(f.blob&&Blob.prototype.isPrototypeOf(t))this._bodyBlob=t;else if(f.formData&&FormData.prototype.isPrototypeOf(t))this._bodyFormData=t;else if(t){if(!f.arrayBuffer||!ArrayBuffer.prototype.isPrototypeOf(t))throw new Error("unsupported BodyInit type")}else this._bodyText="";this.headers.get("content-type")||("string"==typeof t?this.headers.set("content-type","text/plain;charset=UTF-8"):this._bodyBlob&&this._bodyBlob.type&&this.headers.set("content-type",this._bodyBlob.type))},f.blob?(this.blob=function(){var t=o(this);if(t)return t;if(this._bodyBlob)return Promise.resolve(this._bodyBlob);if(this._bodyFormData)throw new Error("could not read FormData body as blob");return Promise.resolve(new Blob([this._bodyText]))},this.arrayBuffer=function(){return this.blob().then(s)},this.text=function(){var t=o(this);if(t)return t;if(this._bodyBlob)return a(this._bodyBlob);if(this._bodyFormData)throw new Error("could not read FormData body as text");return Promise.resolve(this._bodyText)}):this.text=function(){var t=o(this);return t?t:Promise.resolve(this._bodyText)},f.formData&&(this.formData=function(){return this.text().then(l)}),this.json=function(){return this.text().then(JSON.parse)},this}function u(t){var e=t.toUpperCase();return p.indexOf(e)>-1?e:t}function h(t,e){e=e||{};var n=e.body;if(h.prototype.isPrototypeOf(t)){if(t.bodyUsed)throw new TypeError("Already read");this.url=t.url,this.credentials=t.credentials,e.headers||(this.headers=new i(t.headers)),this.method=t.method,this.mode=t.mode,n||(n=t._bodyInit,t.bodyUsed=!0)}else this.url=t;if(this.credentials=e.credentials||this.credentials||"omit",!e.headers&&this.headers||(this.headers=new i(e.headers)),this.method=u(e.method||this.method||"GET"),this.mode=e.mode||this.mode||null,this.referrer=null,("GET"===this.method||"HEAD"===this.method)&&n)throw new TypeError("Body not allowed for GET or HEAD requests");this._initBody(n)}function l(t){var e=new FormData;return t.trim().split("&").forEach(function(t){if(t){var n=t.split("="),i=n.shift().replace(/\+/g," "),o=n.join("=").replace(/\+/g," ");e.append(decodeURIComponent(i),decodeURIComponent(o))}}),e}function d(t){var e=new i,n=t.getAllResponseHeaders().trim().split("\n");return n.forEach(function(t){var n=t.trim().split(":"),i=n.shift().trim(),o=n.join(":").trim();e.append(i,o)}),e}function A(t,e){e||(e={}),this.type="default",this.status=e.status,this.ok=this.status>=200&&this.status<300,this.statusText=e.statusText,this.headers=e.headers instanceof i?e.headers:new i(e.headers),this.url=e.url||"",this._initBody(t)}if(!t.fetch){i.prototype.append=function(t,i){t=e(t),i=n(i);var o=this.map[t];o||(o=[],this.map[t]=o),o.push(i)},i.prototype["delete"]=function(t){delete this.map[e(t)]},i.prototype.get=function(t){var n=this.map[e(t)];return n?n[0]:null},i.prototype.getAll=function(t){return this.map[e(t)]||[]},i.prototype.has=function(t){return this.map.hasOwnProperty(e(t))},i.prototype.set=function(t,i){this.map[e(t)]=[n(i)]},i.prototype.forEach=function(t,e){Object.getOwnPropertyNames(this.map).forEach(function(n){this.map[n].forEach(function(i){t.call(e,i,n,this)},this)},this)};var f={blob:"FileReader"in t&&"Blob"in t&&function(){try{return new Blob,!0}catch(t){return!1}}(),formData:"FormData"in t,arrayBuffer:"ArrayBuffer"in t},p=["DELETE","GET","HEAD","OPTIONS","POST","PUT"];h.prototype.clone=function(){return new h(this)},c.call(h.prototype),c.call(A.prototype),A.prototype.clone=function(){return new A(this._bodyInit,{status:this.status,statusText:this.statusText,headers:new i(this.headers),url:this.url})},A.error=function(){var t=new A(null,{status:0,statusText:""});return t.type="error",t};var g=[301,302,303,307,308];A.redirect=function(t,e){if(-1===g.indexOf(e))throw new RangeError("Invalid status code");return new A(null,{status:e,headers:{location:t}})},t.Headers=i,t.Request=h,t.Response=A,t.fetch=function(t,e){return new Promise(function(n,i){function o(){return"responseURL"in s?s.responseURL:/^X-Request-URL:/m.test(s.getAllResponseHeaders())?s.getResponseHeader("X-Request-URL"):void 0}var r;r=h.prototype.isPrototypeOf(t)&&!e?t:new h(t,e);var s=new XMLHttpRequest;s.onload=function(){var t=1223===s.status?204:s.status;if(100>t||t>599)return void i(new TypeError("Network request failed"));var e={status:t,statusText:s.statusText,headers:d(s),url:o()},r="response"in s?s.response:s.responseText;n(new A(r,e))},s.onerror=function(){i(new TypeError("Network request failed"))},s.open(r.method,r.url,!0),"include"===r.credentials&&(s.withCredentials=!0),"responseType"in s&&f.blob&&(s.responseType="blob"),r.headers.forEach(function(t,e){s.setRequestHeader(e,t)}),s.send("undefined"==typeof r._bodyInit?null:r._bodyInit)})},t.fetch.polyfill=!0}}("undefined"!=typeof self?self:this)},function(t,e){}]);
+ client/package.json view
@@ -0,0 +1,44 @@+{+ "name": "xkcd-linden",+ "version": "0.0.1",+ "description": "Client side of 2016-4-1",+ "keywords": [+ "xkcd"+ ],+ "author": {+ "name": "Max Goodman",+ "email": "c@chromakode.com"+ },+ "license": "MIT",+ "scripts": {+ "lint": "eslint .",+ "start": "webpack-dev-server --devtool inline-source-map --content-base build/",+ "build": "webpack -p --progress"+ },+ "main": "src/index.js",+ "dependencies": {+ "core-js": "~2.2.1",+ "diablo": "~0.0.2",+ "clamp": "~1.0.1",+ "classnames": "~2.2.3",+ "inline-style": "~2.0.0",+ "event-source-polyfill": "~0.0.7",+ "whatwg-fetch": "~0.11.0"+ },+ "devDependencies": {+ "url-loader": "~0.5.7",+ "css-loader": "~0.23.1",+ "style-loader": "~0.13.1",+ "autoprefixer-loader": "~3.2.0",+ "less-loader": "~2.2.3",+ "less": "~2.6.1",+ "babel-loader": "~6.2.4",+ "babel-core": "~6.7.4",+ "babel-plugin-transform-es2015-template-literals": "~6.6.5",+ "webpack": "~1.12.14",+ "webpack-dev-server": "~1.14.1",+ "eslint": "~2.5.3",+ "eslint-config-airbnb": "~6.2.0",+ "babel-eslint": "~6.0.2"+ }+}
+ client/src/GardenClient.js view
@@ -0,0 +1,251 @@+require('event-source-polyfill')++var Linden = require('./Linden')+var walkTree = require('./walkTree')+var randomWind = require('./randomWind')++var GardenClient = function GardenClient(opts) {+ this.opts = opts+ this.data = null+ this.gardenId = null+ this.gardenKey = null+}+GardenClient.prototype = {+ record: function record(ref, name) {+ new Image().src = 'http://xkcd.com/events.gif?re=' + ref + '&ev=' + name+ },++ log: function log() {+ if (this.opts.debug) {+ console.log.apply(console, arguments) // eslint-disable-line no-console+ }+ },++ _url: function _url() {+ var serverURL = this.opts.endpoint + 'run'+ if (this.gardenId) {+ serverURL += '/' + this.gardenId+ }+ return serverURL+ },++ _connect: function() {+ var serverURL = this._url()++ this.source = new EventSource(serverURL)+ this.log('connecting to event source:', serverURL)++ this.source.addEventListener('open', this.onConnect.bind(this), false)+ this.source.addEventListener('error', this.onConnectionError.bind(this), false)+ this.source.addEventListener('linden-key', this.onLindenKey.bind(this), false)+ this.source.addEventListener('linden', this.onLindenUpdate.bind(this), false)+ this.source.addEventListener('reload', this.onReload.bind(this), false)+ },++ run: function run() {+ try {+ if (this.opts.endpoint) {+ if (location.hash) {+ this.gardenId = location.hash.substr(1)+ this.gardenKey = localStorage['key-' + this.gardenId]+ }+ this._connect()+ }++ this.linden = new Linden({+ el: this.opts.el,+ width: this.opts.width,+ height: this.opts.height,+ title: this.opts.title,+ urlPrefix: this.opts.imgPrefix,+ loading: true,+ readOnly: !this.gardenKey,+ debug: this.opts.debug,+ onLightChanging: this.handleLightChanging.bind(this),+ onSetLight: this.handleSetLight.bind(this),+ onRemoveLight: this.handleRemoveLight.bind(this),+ onPruneNode: this.handlePruneNode.bind(this),+ })++ if (this.opts.data) {+ this.linden.update(this.opts.data)+ }++ randomWind(this.linden)+ } catch (e) {+ this.record(this.opts.name, 'js_error')+ throw e+ }++ return this+ },++ onConnect: function onConnect() {+ this.record(this.opts.name, 'connect_start')+ this.dirtyLights = {}+ this.inFlightRequests = 0+ this.linden.renderUI()+ },++ onConnectionError: function onConnectionError(ev) {+ this.log('connection error', ev)+ this.record(this.opts.name, 'connect_error')+ this.linden.opts.loading = true+ this.linden.resetTree()+ this.linden.renderUI()+ if (this.source.url !== this._url()) {+ // we need to change the url to re-fetch the current tree+ this.source.close()+ this._connect()+ }+ },++ onLindenKey: function onLindenKey(ev) {+ this.log('linden key from EventSource', ev)+ var data = JSON.parse(ev.data)+ this.gardenId = location.hash = data.garden+ this.gardenKey = localStorage['key-' + data.garden] = data.key++ // TODO: this violates encapsulation+ this.linden.opts.readOnly = false+ this.linden.renderUI()+ },++ _acceptUpdate: function _acceptUpdate() {+ if (this.inFlightRequests > 0) {+ this.log('rejecting update, inflight:', this.inFlightRequests)+ return false+ }++ for (var k in this.dirtyLights) {+ if (this.dirtyLights.hasOwnProperty(k) && this.dirtyLights[k] === true) {+ this.log('rejecting update, dirty light:', k)+ return false+ }+ }++ return true+ },++ _update: function _update(data) {+ if (!this._acceptUpdate()) {+ // discard possibly conflicting updates+ return+ }+ this.data = data+ this.linden.update(data)+ },++ onLindenUpdate: function onLindenUpdate(ev) {+ this.log('linden from EventSource', ev)+ this._update(JSON.parse(ev.data))+ },++ onReload: function onReload() {+ var delay = Math.round(Math.random() * this.opts.spread.reload)+ this.log('reloading in', delay + 5, 'seconds')+ setTimeout(function doReload() {+ this.record(this.opts.name, 'reloading')++ // give the record a little time to be sent+ setTimeout(function delayedReload() {+ location.reload()+ }, 5 * 1000)+ }, delay * 1000)+ },++ request: function request(url, options) {+ var onFinished = function onRequestFinished() {+ this.inFlightRequests--+ this.log('inflight--', this.inFlightRequests)+ }.bind(this)++ var fetchOptions = {+ method: 'POST',+ }+ Object.assign(fetchOptions, options)++ fetchOptions.body = JSON.stringify(options.body)++ this.log('request', url, fetchOptions)+ var promise = fetch(url, fetchOptions)+ promise.then(onFinished, onFinished)+ this.inFlightRequests+++ this.log('inflight++', this.inFlightRequests)+ return promise+ },++ handleLightChanging: function handleLightChanging(id) {+ this.dirtyLights[id] = true+ },++ handleSetLight: function handleSetLight(id, data) {+ this.log('set light:', id, data)++ this.request(this.opts.endpoint + 'edit', {+ body: Object.assign({+ tag: 'EditLight',+ garden: this.data.garden,+ editKey: this.gardenKey,+ light: id,+ }, data),+ })+ .then(function onResponse(resp) { return resp.json() })+ .then(function onJSON(newLights) {+ this._update(Object.assign({}, this.data, {+ lights: newLights,+ }))+ }.bind(this))++ delete this.dirtyLights[id]+ },++ _sendRemove: function _sendRemove(id) {+ this.request(this.opts.endpoint + 'edit', {+ body: {+ tag: 'Del',+ garden: this.data.garden,+ editKey: this.gardenKey,+ node: id,+ },+ })+ },++ handleRemoveLight: function handleRemoveLight(id) {+ this.log('remove light:', id)++ var newLights = this.data.lights.filter(function filterLights(light) {+ return light.id !== id+ })++ this._update(Object.assign({}, this.data, {+ lights: newLights,+ }))++ this._sendRemove(id)+ },++ handlePruneNode: function handlePruneNode(id) {+ this.log('prune node:', id)++ var rootNode = { children: [] }+ var parentNode = rootNode+ walkTree(this.data.tree, function pruneVisit(node, descend) {+ var origParentNode = parentNode+ parentNode = { node: node, children: [] }+ if (node.id !== id) {+ origParentNode.children.push(parentNode)+ }+ descend()+ parentNode = origParentNode+ })++ this._update(Object.assign({}, this.data, {+ tree: rootNode.children[0],+ }))++ this._sendRemove(id)+ },+}++module.exports = GardenClient
+ client/src/ImgCache.js view
@@ -0,0 +1,62 @@+function ImgCache() {+ this.imgs = {}+ this.callbacks = {}+}+ImgCache.prototype = {+ get: function get(src, callback) {+ var img = this.imgs[src]++ if (img === undefined) {+ this.imgs[src] = true+ this.callbacks[src] = [callback]+ img = new Image()+ img.onload = this._onDone.bind(this, src, img, img)+ img.onerror = this._onDone.bind(this, src, img, false)+ img.crossOrigin = 'anonymous'+ img.src = src+ } else if (img === true) {+ // loading+ this.callbacks[src].push(callback)+ } else {+ // release zalgo+ if (callback) {+ callback(src, img)+ }+ }+ },++ _onDone: function _onDone(src, img, result) {+ this.imgs[src] = result+ for (var i = 0; i < this.callbacks[src].length; i++) {+ var callback = this.callbacks[src][i]+ if (callback) {+ callback(src, result)+ }+ }+ delete this.callbacks[src]+ },++ getAll: function getAll(srcs, callback) {+ if (!srcs.length) {+ callback([])+ return+ }++ var remaining = srcs.length+ var results = {}++ function loaded(src, img) {+ results[src] = img+ remaining--+ if (remaining === 0) {+ callback(results)+ }+ }++ for (var i = 0; i < srcs.length; i++) {+ this.get(srcs[i], loaded)+ }+ },+}++module.exports = ImgCache
+ client/src/Linden.js view
@@ -0,0 +1,794 @@+var x = require('diablo')+var classNames = require('classnames')+var clamp = require('clamp')+var inlineStyle = require('inline-style')++var ImgCache = require('./ImgCache')+var walkTree = require('./walkTree')++var PI2 = Math.PI / 2+var TWOPI = 2 * Math.PI+var DEG = PI2 / 90++function sin(angle) {+ return Math.sin(angle * DEG)+}++function cos(angle) {+ return Math.cos(angle * DEG)+}++function mod(v, m) {+ return (v % m + m) % m+}++function rotBounds(a, ox, oy, x1, y1, x2, y2) {+ var rx1+ var ry1+ var rx2+ var ry2+ var am = mod(a, 360)+ if (am <= 90) {+ rx1 = x1+ ry1 = y2+ rx2 = x2+ ry2 = y1+ } else if (am <= 180) {+ rx1 = x2+ ry1 = y1+ rx2 = x1+ ry2 = y2+ } else if (am <= 270) {+ rx1 = x2+ ry1 = y2+ rx2 = x1+ ry2 = y1+ } else {+ rx1 = x1+ ry1 = y2+ rx2 = x2+ ry2 = y1+ }+ rx1 -= ox+ ry1 -= oy+ rx2 -= ox+ ry2 -= oy+ var newBounds = {+ x1: ox + rx1 * cos(am) - ry1 * sin(am),+ y1: oy + rx1 * sin(am) + ry1 * cos(am),+ x2: ox + rx2 * cos(am) - ry2 * sin(am),+ y2: oy + rx2 * sin(am) + ry2 * cos(am),+ }++ // FIXME: not sure why these are transposed :(+ if (newBounds.x2 < newBounds.x1) {+ var swapx = newBounds.x2+ newBounds.x2 = newBounds.x1+ newBounds.x1 = swapx+ }+ if (newBounds.y2 < newBounds.y1) {+ var swapy = newBounds.y2+ newBounds.y2 = newBounds.y1+ newBounds.y1 = swapy+ }++ return newBounds+}++function rgba(r, g, b, a) {+ return 'rgba(' + 100 * r + '%, ' + 100 * g + '%, ' + 100 * b + '%, ' + a + ')'+}++function tempToRGB(t) {+ if (t < 0.32) {+ return [1, 0.5 + 0.5 * t / 0.32, 0.5]+ }+ if (t < 0.64) {+ return [1, 1, 0.5 + 0.5 * (t - 0.32) / 0.32]+ }+ return [0.75 + 0.25 * (1 - t) / 0.36, 0.75 + 0.25 * (1 - t) / 0.36, 1]+}++function walkPositions(l, state, visit) {+ // parent cache+ var pc = {+ x2: 0,+ y2: 0,+ scale: 1,+ angle: 0,+ }+ walkTree(l, function visitPosition(node, descend) {+ var nodeState = state[node.id]+ var c = node.cache+ if (!c) {+ c = node.cache = {} // eslint-disable-line no-param-reassign+ }+ c.scale = pc.scale * node.scale+ c.angle = pc.angle + nodeState.angle+ var dx = node.attach[0] - node.root[0]+ var dy = node.attach[1] - node.root[1]+ c.x1 = pc.x2+ c.y1 = pc.y2+ c.x2 = c.x1 + c.scale * (dx * cos(c.angle) + dy * sin(c.angle))+ c.y2 = c.y1 + c.scale * (-dx * sin(c.angle) + dy * cos(c.angle))++ var origPC = pc+ pc = c+ visit(node, descend)+ pc = origPC+ })+}++function calcBox(node) {+ var c = node.cache+ var bx1 = c.x1 - node.root[0]+ var by1 = c.y1 - node.root[1]+ return rotBounds(+ -c.angle,+ bx1 + node.root[0],+ by1 + node.root[1],+ bx1,+ by1,+ bx1 + c.scale * c.width,+ by1 + c.scale * c.height+ )+}++function renderLinden(l, state, lights, opts) {+ var w = opts.width+ var h = opts.height+ var debug = opts.debug+ var urlPrefix = opts.urlPrefix+ var imgPrefix = ''+ var imgCache = opts.imgCache+ var ctx = opts.el.getContext('2d')+ var density = Number(opts.el.getAttribute('data-density'))++ if (density > 1) {+ imgPrefix = '2x-'+ }++ var imgs = []+ walkTree(l, function visitImg(node, descend) {+ if (node.img) {+ imgs.push(urlPrefix + imgPrefix + node.img)+ }+ descend()+ })++ function getState(node, prop) {+ return state[node.id][prop]+ }++ imgCache.getAll(imgs, function withImgs(loadedImgs) {+ ctx.save()+ ctx.scale(density, -density)+ ctx.translate(0, -h)+ ctx.clearRect(0, 0, w, h)++ var circledNode+ walkPositions(l, state, function visitDraw(node, descend) {+ var c = node.cache+ ctx.save()+ ctx.rotate(getState(node, 'angle') * -DEG)+ ctx.scale(node.scale, node.scale)+ if (state._selected) {+ if (node.id === state._selected.id) {+ ctx.globalAlpha = 0.5+ }+ if (node.id === state._selected.circled) {+ circledNode = node+ }+ }+ if (node.img) {+ var imgUrl = urlPrefix + imgPrefix + node.img+ var img = loadedImgs[imgUrl]+ if (img) {+ ctx.save()+ ctx.scale(1 / density, -1 / density)+ ctx.translate(0, -img.height)+ ctx.drawImage(img, -node.root[0] * density, node.root[1] * density)+ ctx.restore()+ c.width = img.width / density+ c.height = img.height / density+ }+ }+ ctx.translate(node.attach[0] - node.root[0], node.attach[1] - node.root[1])+ descend()+ ctx.restore()+ })++ // lights+ var mixCanvas = document.createElement('canvas')+ mixCanvas.width = opts.el.width+ mixCanvas.height = opts.el.height+ var lctx = mixCanvas.getContext('2d')+ lctx.scale(density, density)+ var lightRadius = Math.sqrt(Math.pow(w, 2) + Math.pow(h, 2))+ function drawLights(callback) {+ for (var i = 0; i < lights.length; i++) {+ var light = lights[i]+ lctx.save()+ lctx.beginPath()+ lctx.moveTo(light.pos[0], light.pos[1])+ var x1 = light.pos[0] + lightRadius * cos(light.pointAngle - light.beamAngle / 2)+ var y1 = light.pos[1] + lightRadius * sin(light.pointAngle - light.beamAngle / 2)+ lctx.lineTo(x1, y1)+ lctx.arc(light.pos[0], light.pos[1], lightRadius,+ (light.pointAngle - light.beamAngle / 2) * DEG,+ (light.pointAngle + light.beamAngle / 2) * DEG+ )+ lctx.lineTo(light.pos[0], light.pos[1])+ callback(light)+ lctx.restore()+ }+ }+ drawLights(function drawLightFill(light) {+ var grad = lctx.createRadialGradient(+ light.pos[0], light.pos[1], 0,+ light.pos[0], light.pos[1], lightRadius+ )+ var color = tempToRGB(light.temp)+ grad.addColorStop(0, rgba(color[0], color[1], color[2], 1))+ grad.addColorStop(1, rgba(color[0], color[1], color[2], 0))+ lctx.fillStyle = grad+ lctx.globalCompositeOperation = 'lighten'+ lctx.fill()+ })+ drawLights(function drawLightStroke(light) {+ lctx.globalAlpha = 0.4+ lctx.strokeStyle = 'black'+ lctx.stroke()+ })+ ctx.save()+ ctx.scale(1 / density, 1 / density)+ ctx.globalCompositeOperation = 'source-atop'+ ctx.globalAlpha = 0.65+ ctx.drawImage(mixCanvas, 0, 0)+ ctx.globalAlpha = 0.25+ ctx.globalCompositeOperation = 'destination-over'+ ctx.drawImage(mixCanvas, 0, 0)+ ctx.restore()++ if (circledNode) {+ var c = circledNode.cache+ ctx.save()+ ctx.beginPath()+ var circledBox = calcBox(circledNode)+ var cx = circledBox.x1 + (circledBox.x2 - circledBox.x1) / 2+ var cy = circledBox.y1 + (circledBox.y2 - circledBox.y1) / 2+ var d = Math.sqrt(Math.pow(circledBox.x2 - circledBox.x1, 2) + Math.pow(circledBox.y2 - circledBox.y1, 2))+ ctx.arc(cx, cy, d / 2 + 6, 0, TWOPI)+ ctx.strokeStyle = 'red'+ ctx.lineWidth = 2+ ctx.globalAlpha = 0.9+ ctx.stroke()+ ctx.restore()+ }++ if (debug) {+ walkTree(l, function visitDrawDebug(node, descend) {+ ctx.save()+ var c2 = node.cache+ ctx.beginPath()+ ctx.globalAlpha = 0.75+ ctx.moveTo(c2.x1, c2.y1)+ ctx.lineTo(c2.x2, c2.y2)+ ctx.strokeStyle = node.proxyFor ? 'lightgreen' : 'magenta'+ ctx.lineWidth = 1+ ctx.stroke()+ descend()+ ctx.restore()+ if (c2.width) {+ var box = calcBox(node)+ ctx.beginPath()+ ctx.rect(box.x1, box.y1, (box.x2 - box.x1), (box.y2 - box.y1))+ ctx.strokeStyle = 'red'+ ctx.stroke()+ }+ })+ }++ ctx.restore()++ if (opts.onFinish) {+ opts.onFinish()+ }+ })+}++x.component('Handle', {+ componentDidMount: function componentDidMount() {+ this._handleDragMove = this.handleDragMove.bind(this)+ this._handleDragUp = this.handleDragUp.bind(this)++ // ontouchstart attribute doesn't seem to work :(+ this.getDOMNode().firstChild.addEventListener('touchstart', this.handleDragDown.bind(this))+ },++ _stopListening: function _stopListening() {+ window.removeEventListener('mousemove', this._handleDragMove, false)+ window.removeEventListener('mouseup', this._handleDragUp, false)+ window.removeEventListener('touchmove', this._handleDragMove, false)+ window.removeEventListener('touchend', this._handleDragUp, false)+ },++ componentWillUnmount: function componentWillUnmount() {+ this._stopListening()+ },++ _normalizeEvent: function _normalizeEvent(ev) {+ if (ev.touches && ev.touches[0]) {+ ev.clientX = ev.touches[0].clientX // eslint-disable-line no-param-reassign+ ev.clientY = ev.touches[0].clientY // eslint-disable-line no-param-reassign+ }+ },++ handleDragDown: function handleDragDown(ev) {+ if (this.props.disabled) {+ return+ }+ document.documentElement.style.cursor = 'move'+ window.addEventListener('mousemove', this._handleDragMove, false)+ window.addEventListener('mouseup', this._handleDragUp, false)+ window.addEventListener('touchmove', this._handleDragMove, false)+ window.addEventListener('touchend', this._handleDragUp, false)+ if (this.props.onGrab) {+ this._normalizeEvent(ev)+ this.props.onGrab(ev)+ }+ ev.preventDefault()+ },++ handleDragUp: function handleDragUp(ev) {+ document.documentElement.style.cursor = null+ this._stopListening()+ if (this.props.onRelease) {+ this._normalizeEvent(ev)+ this.props.onRelease(ev)+ }+ ev.preventDefault()+ },++ handleDragMove: function handleDragMove(ev) {+ if (this.props.onDrag) {+ this._normalizeEvent(ev)+ this.props.onDrag(ev)+ }+ ev.preventDefault()+ },++ render: function render() {+ // FIXME: diablo issue: undefined / null gets stringified+ return x`<div+ className=${classNames('handle', this.props.className)}+ style=${this.props.style || ''}+ onMouseDown=${this.handleDragDown.bind(this)}+ />`+ },+})++x.component('Light', {+ handleRotate: function handleRotate(ev) {+ // TODO: key?+ var box = this.getDOMNode().firstChild.getBoundingClientRect()+ var px = ev.clientX - box.left+ var py = ev.clientY - box.top+ var angle = mod(-Math.atan2(py, px) / DEG, 360)+ var distance = Math.sqrt(Math.pow(px, 2) + Math.pow(py, 2))+ this.props.onRotate(angle, clamp(0, 95 - (distance - 42) * 3, 95))+ },++ handleRotateColor: function handleRotateColor(ev) {+ var box = this.getDOMNode().firstChild.getBoundingClientRect()+ var px = ev.clientX - box.left+ var py = ev.clientY - box.top+ var angle = clamp(90, mod(-Math.atan2(py, px) / DEG - this.props.pointAngle, 360), 270) - 90+ this.props.onChangeColor(angle / 180)+ },++ render: function render() {+ var style = inlineStyle({+ left: this.props.x + 'px',+ top: this.props.y + 'px',+ transform: `rotate(${-this.props.pointAngle - 90}deg)`,+ })++ var handles = ''+ if (this.props.selected) {+ var d = 42 + clamp(0, (95 - this.props.beamAngle) / 3, 95)+ var rotHandlePosStyle = inlineStyle({+ position: 'absolute',+ top: d + 'px',+ })++ var colorAngle = 180 * this.props.temp+ var color = tempToRGB(this.props.temp)+ var colorHandlePosStyle = inlineStyle({+ position: 'absolute',+ left: 39 * cos(-colorAngle) + 'px',+ top: 39 * sin(-colorAngle) + 'px',+ })+ var colorHandleStyle = inlineStyle({+ backgroundColor: rgba(color[0], color[1], color[2], 1),+ })++ handles = x`+ <div>+ <div style=${rotHandlePosStyle}>+ <Handle+ className="rotate"+ disabled=${this.props.disabled}+ onDrag=${this.handleRotate.bind(this)}+ onRelease=${this.props.onSave}+ ></Handle>+ </div>+ <div style=${colorHandlePosStyle}>+ <Handle+ className="color"+ style=${colorHandleStyle}+ disabled=${this.props.disabled}+ onDrag=${this.handleRotateColor.bind(this)}+ onRelease=${this.props.onSave}+ ></Handle>+ </div>+ </div>+ `+ }++ return x`+ <div+ className=${classNames('light', this.props.selected && 'selected')}+ style=${style}+ data-light-id=${this.props.id}+ >+ <div>+ <Handle+ className="position"+ disabled=${this.props.disabled}+ onGrab=${this.props.onInteract}+ onDrag=${this.props.onMove}+ onRelease=${this.props.onSave}+ ></Handle>+ </div>+ ${handles}+ </div>+ `+ },+})++function Linden(opts) {+ this.el = opts.el+ this.opts = opts+ this.imgCache = new ImgCache()+ this._queued = false+ this._rendering = false+ this._afterNextRender = null+ this.resetTree()+ this.renderUI()+}+Linden.prototype = {+ STIFFNESS: 0.25,+ FRICTION: 0.1,+ MAX_LIGHTS: 3,+ FPS: 15,++ resetTree: function resetTree() {+ this.l = null+ this.lights = []+ this.state = { _selected: null }+ },++ renderUI: function renderUI() {+ var tools = ''+ if (!this.opts.readOnly && !this.opts.loading) {+ tools = x`+ <div className="tools">+ <button+ className="add-light"+ disabled=${this.lights.length === this.MAX_LIGHTS}+ title="Create new light"+ onClick=${this.handleCreateLight.bind(this)}+ >+</button>+ <button+ className="remove"+ disabled=${!this.state._selected}+ title="Remove selected (delete key)"+ onClick=${this.handleRemoveSelection.bind(this)}+ >x</button>+ </div>+ `+ }++ var style = inlineStyle({+ width: this.opts.width + 'px',+ height: this.opts.height + 'px',+ })++ var canvasMultiplier = window.devicePixelRatio > 1 ? 2 : 1+ var canvasStyle = inlineStyle({+ transformOrigin: '0 0',+ transform: `scale(${1 / canvasMultiplier}, ${1 / canvasMultiplier})`,+ })++ var className = classNames('frame', this.opts.loading && 'loading')+ x.render(x`+ <div+ onMouseDown=${this.handleMouseDown.bind(this)}+ onKeyDown=${this.handleKeyDown.bind(this)}+ style=${style}+ className=${className}+ title=${this.opts.title}+ tabIndex="1"+ >+ ${tools}+ ${this.opts.loading ? x`<div><div className="spinner"></div><div className="spinner-text">loading</div>` : ''}+ <div className="content">+ <canvas+ data-density=${canvasMultiplier}+ width=${this.opts.width * canvasMultiplier}+ height=${this.opts.height * canvasMultiplier}+ style=${canvasStyle}+ onMouseDown=${this.handleCanvasMouseDown.bind(this)}+ ></canvas>+ ${this.lights.map(function renderLight(light) {+ var selected = Boolean(+ this.state._selected &&+ this.state._selected.kind === 'light' &&+ this.state._selected.id === light.id+ )+ return x`<Light+ id=${light.id}+ disabled=${this.opts.readOnly}+ selected=${selected}+ x=${light.pos[0]}+ y=${this.opts.height - light.pos[1]}+ temp=${light.temp}+ pointAngle=${light.pointAngle}+ beamAngle=${light.beamAngle}+ onInteract=${this.handleLightInteract.bind(this, light.id)}+ onMove=${this.handleLightMove.bind(this, light.id)}+ onRotate=${this.handleLightRotate.bind(this, light.id)}+ onChangeColor=${this.handleLightChangeColor.bind(this, light.id)}+ onSave=${this.saveLight.bind(this, light.id)}+ />`+ }.bind(this))}+ </div>+ </div>+ `, this.el)+ },++ update: function update(data) {+ this.l = data.tree++ var nodeCount = 0+ var nodeTypeCount = {}+ walkTree(this.l, function visitInitState(node, descend) {+ nodeCount+++ var nodeType = node.img || 'proxy'+ nodeTypeCount[nodeType] = nodeTypeCount[nodeType] || {count: 0}+ nodeTypeCount[nodeType].count++++ var nodeState = this.state[node.id]+ if (!nodeState) {+ nodeState = this.state[node.id] = {+ angle: node.angle,+ av: 0,+ }+ }+ nodeState.node = node+ descend()+ }.bind(this))++ this.lights = data.lights || []++ var startTime = Date.now()++ this._afterNextRender = function afterRender() {+ this.opts.loading = false+ this.renderUI()+ this._afterNextRender = null++ if (this.opts.debug) {+ var duration = Date.now() - startTime+ console.debug('updated. rendered', nodeCount, 'nodes in', duration, 'ms', '| supply:', data.supply)+ console.table(nodeTypeCount)+ }+ }.bind(this)++ this.renderCanvas()+ },++ addWind: function addWind(angle, magnitude, spread) {+ walkTree(this.l, function visitWind(node, descend) {+ if (!node.rigid && node.cache) {+ var nodeState = this.state[node.id]+ var alignment = sin(angle - node.cache.angle)+ var spreadMag = (1 - spread) + Math.random() * spread+ nodeState.av += node.waviness * spreadMag * (1 / Math.sqrt(node.cache.scale)) * alignment * magnitude+ }+ descend()+ }.bind(this))++ this.queueStep()+ },++ queueStep: function queueStep() {+ if (!this._queued) {+ // note that actual FPS is below this.FPS since we don't adapt sleep time+ setTimeout(this.step.bind(this), 1000 / this.FPS)+ this._queued = Date.now()+ }+ },++ step: function step() {+ if (this.opts.beforeStep) {+ this.opts.beforeStep()+ }++ walkTree(this.l, function visitStep(node, descend) {+ var nodeState = this.state[node.id]+ nodeState.av += (1 - node.waviness) * node.scale * this.STIFFNESS * (node.angle - nodeState.angle)+ nodeState.av += -nodeState.av * this.FRICTION+ nodeState.angle += nodeState.av+ descend()+ }.bind(this))++ this.renderCanvas()++ this._queued = false+ this.queueStep()+ },++ renderCanvas: function renderCanvas() {+ if (this._rendering || !this.l) {+ return+ }+ this._rendering = true+ renderLinden(this.l, this.state, this.lights, {+ width: this.opts.width,+ height: this.opts.height,+ el: this.el.querySelector('canvas'),+ urlPrefix: this.opts.urlPrefix,+ imgCache: this.imgCache,+ debug: this.opts.debug,+ onFinish: function onRenderFinish() {+ if (this._afterNextRender) {+ this._afterNextRender()+ }+ this._rendering = false+ }.bind(this),+ })+ },++ handleMouseDown: function handleMouseDown() {+ this.el.querySelector('.frame').focus()+ },++ _deselect: function _deselect() {+ this.state._selected = null+ this.renderCanvas()+ this.renderUI()+ },++ handleCanvasMouseDown: function handleCanvasMouseDown(ev) {+ if (this.opts.readOnly) {+ return+ }+ this._deselect()++ var box = this.el.getBoundingClientRect()+ var x0 = ev.clientX - box.left+ var y0 = this.opts.height - (ev.clientY - box.top)+ var hit = null+ var hitArea = Number.MAX_VALUE+ walkTree(this.l, function visitCheckProximity(node, descend) {+ var c = node.cache+ if (c && c.width && !node.immutable) {+ var box = calcBox(node)+ if (box.x1 <= x0 && box.x2 >= x0 &&+ box.y1 <= y0 && box.y2 >= y0) {+ var area = (box.x2 - box.x1) * (box.y2 - box.y1)+ if (area < hitArea) {+ hit = node+ hitArea = area+ }+ }+ }+ descend()+ })++ if (hit) {+ var selectedId = hit.proxyFor || hit.id+ this.state._selected = { kind: 'node', id: selectedId, circled: hit.id }+ }+ this.renderCanvas()+ this.renderUI()+ },++ handleKeyDown: function handleKeyDown(ev) {+ if (ev.which === 46 || ev.which === 8) { // backspace or delete+ this.handleRemoveSelection()+ ev.preventDefault()+ }+ },++ handleLightInteract: function handleLightInteract(id) {+ if (this.opts.readOnly) {+ return+ }+ this.state._selected = { kind: 'light', id: id }+ this.renderCanvas()+ this.renderUI()+ },++ handleLightMove: function handleLightMove(id, ev) {+ var box = this.el.getBoundingClientRect()+ var px = clamp(0, ev.clientX - box.left, this.opts.width)+ var py = clamp(0, this.opts.height - (ev.clientY - box.top), this.opts.height)+ this.setLight(id, {+ pos: [px, py],+ })+ },++ handleLightRotate: function handleLightRotate(id, pointAngle, beamAngle) {+ this.setLight(id, {+ pointAngle: pointAngle,+ beamAngle: beamAngle,+ })+ },++ handleLightChangeColor: function handleLightChangeColor(id, temp) {+ this.setLight(id, {+ temp: temp,+ })+ },++ setLight: function setLight(id, data) {+ this.opts.onLightChanging(id)+ var idx = this.lights.findIndex(function matchLight(light) { return light.id === id })+ if (idx === -1) {+ return+ }+ Object.assign(this.lights[idx], data)+ this.renderCanvas()+ this.renderUI()+ },++ saveLight: function saveLight(id) {+ var idx = this.lights.findIndex(function matchLight(light) { return light.id === id })+ if (idx === -1) {+ return+ }+ this.opts.onSetLight(id, this.lights[idx])+ },++ handleCreateLight: function handleCreateLight() {+ var lx = this.opts.width * Math.random()+ var ly = (this.opts.height + this.opts.height * Math.random()) / 2+ var tx = this.opts.width / 2+ var ty = 0+ this.opts.onSetLight(null, {+ pos: [lx, ly],+ pointAngle: Math.atan2(ty - ly, tx - lx) / DEG,+ beamAngle: 45 + 45 * Math.random(),+ temp: 0.5,+ })+ },++ handleRemoveSelection: function handleRemoveSelection() {+ if (!this.state._selected) {+ return+ }++ if (this.state._selected.kind === 'node') {+ this.opts.onPruneNode(this.state._selected.id)+ } else if (this.state._selected.kind === 'light') {+ this.opts.onRemoveLight(this.state._selected.id)+ }++ this._deselect()+ },+}++module.exports = Linden
+ client/src/index.js view
@@ -0,0 +1,34 @@+require('core-js/fn/object/assign')+require('core-js/fn/array/map')+require('core-js/fn/array/find-index')+require('core-js/fn/array/filter')+require('core-js/fn/date/now')+require('core-js/es6/promise')+require('whatwg-fetch')++require('./index.less')+var GardenClient = require('./GardenClient')++var scripts = document.getElementsByTagName('script')+var script = scripts[scripts.length - 1]++window.garden = new GardenClient({+ name: 'garden',+ el: document.getElementById('comic'),+ width: 740,+ height: 462,+ title: script.getAttribute('data-title'),+ endpoint: script.getAttribute('data-endpoint'),+ imgPrefix: script.getAttribute('data-image-endpoint'),+ spread: {+ reload: 60,+ },+ debug: location.search === '?debug',+}).run()++window.addEventListener('keydown', function preventBackNav(ev) {+ // prevent backspace from navigating+ if (ev.which === 8) {+ ev.preventDefault()+ }+}, false)
+ client/src/index.less view
@@ -0,0 +1,166 @@+#comic {+ position: relative;+ display: inline-block;+ outline: 2px solid black;+ overflow: hidden;+ user-select: none;++ .frame {+ background: white;+ transition: background-color .5s ease;++ &:focus {+ outline: none;+ }++ .content {+ opacity: 1;+ transition: opacity .5s ease;+ }++ &.loading {+ background: white;++ .content {+ opacity: 0;+ }+ }+ }++ .spinner {+ position: absolute;+ left: 50%;+ top: 45%;+ width: 50px;+ height: 52px;+ margin-left: -30px;+ margin-top: -26px;+ background-image: url('../../art/loading.png');+ background-size: contain;++ animation-name: rotate;+ animation-iteration-count: infinite;+ animation-timing-function: linear;+ animation-duration: 2s;+ transform-origin: 56% 50%;+ @keyframes rotate {+ 0% {+ transform: rotateY(0);+ }+ 50% {+ transform: rotateY(360deg);+ }+ 100% {+ transform: rotateY(0);+ }+ }+ }++ .spinner-text {+ font-size: 0;+ position: absolute;+ left: 50%;+ top: 53%;+ width: 91px;+ height: 17px;+ margin-left: -91px / 2;+ background-image: url('../../art/loading.gif');+ background-repeat: no-repeat;+ }++ .light {+ position: absolute;+ z-index: 200;++ .handle {+ position: absolute;+ transform: translate(-50%, -50%);++ &.position {+ width: 40px;+ height: 44px;+ background-image: url('../../art/light.png');+ background-size: contain;+ }++ &.rotate {+ width: 0;+ height: 0;+ background: transparent;+ border-left: 10px solid transparent;+ border-right: 10px solid transparent;+ border-top: 10px solid red;+ }++ &.color {+ width: 8px;+ height: 8px;+ background: black;+ border: 2px solid black;+ border-radius: 100%;+ }+ }++ &.selected:before {+ content: '';+ display: block;+ position: absolute;+ left: -40px;+ right: -40px;+ top: -40px;+ bottom: -40px;+ border: 2px solid red;+ border-radius: 100%;+ opacity: .75;+ }+ }++ .tools {+ position: absolute;+ right: 0;+ z-index: 100;++ button {+ display: block;+ width: 40px;+ height: 40px;+ padding: 0;+ margin: 10px;+ box-sizing: content-box;+ font-size: 0;+ color: transparent;+ background: #ddd;+ background-size: contain;+ border: 1px solid #333;+ border-radius: 3px;+ transition: opacity .15s ease;+ opacity: .75;++ &:hover {+ opacity: 1;+ }++ &:disabled {+ opacity: .25;+ }++ &.add-light {+ background-image: url('../../art/add-light.png');+ }++ &.remove {+ background-color: #d22;+ background-image: url('../../art/remove.png');++ &:disabled {+ background-color: #777;+ }+ }+ }+ }++ #data {+ width: 100%;+ height: 10em;+ }+}
+ client/src/randomWind.js view
@@ -0,0 +1,19 @@+var clamp = require('clamp')++module.exports = function randomWind(linden) {+ var gust = 0+ var angle = 90++ function updateWind() {+ var jitter = 2 * (Math.random() - 0.5)+ if (Math.random() > 0.9) {+ gust += Math.random() - 0.5+ }+ angle = clamp(60, angle + 2 * (Math.random() - 0.5), 120)+ gust *= 0.995+ linden.addWind(angle, 0.025 * jitter + 0.25 * gust, 0.5)+ }++ linden.opts.beforeStep = updateWind // eslint-disable-line no-param-reassign+ updateWind()+}
+ client/src/walkTree.js view
@@ -0,0 +1,12 @@+module.exports = function walk(obj, visit) {+ if (!obj) {+ return+ }++ function descend() {+ for (var i = 0; i < obj.children.length; i++) {+ walk(obj.children[i], visit)+ }+ }+ visit(obj.node, descend)+}
+ client/webpack.config.js view
@@ -0,0 +1,35 @@+var path = require('path')+var webpack = require('webpack')++module.exports = {+ entry: {+ linden: './src/index.js',+ },++ output: {+ filename: '[name].js',+ path: path.join(__dirname, 'build'),+ },++ module: {+ loaders: [+ {+ test: /\.js$/,+ exclude: /node_modules/,+ loader: 'babel',+ },+ {+ test: /\.less$/,+ loaders: ['style', 'css', 'autoprefixer', 'less'],+ },+ {+ test: /\.png$|\.gif$/,+ loader: 'url',+ },+ ],+ },++ plugins: [+ new webpack.BannerPlugin('linden by chromako.de'),+ ],+}
+ extract_grips.py view
@@ -0,0 +1,142 @@+#!/usr/bin/python+from __future__ import print_function+import sys+import gc+import glob, os+import shutil+import hashlib+import json++from PIL import Image++mirror_names = glob.glob("tiff/*-m.tif")+for m in mirror_names:+ im = Image.open(m)+ im_mirrored = im.transpose(Image.FLIP_LEFT_RIGHT)+ im.save(m.replace("-m.tif", "-l.tif"))+ im_mirrored.save(m.replace("-m.tif", "-r.tif"))++FROM_DPI = 1200+TO_DPI_2X = 2 * 80+RATIO_2X = float(TO_DPI_2X) / float(FROM_DPI)+RATIO_1X = RATIO_2X / 2++def print_status(*args):+ print(*args, end=" ")+ sys.stdout.flush()++def pixIn(b, p):+ g = 4+ return (((b['x1']-g) < p[0]) and ((b['x2']+g) > p[0]) and ((b['y1']-g) < p[1]) and ((b['y2']+g) > p[1]))++def boxCenter(b):+ return { "x": (b['x1']+b['x2'])/2+ , "y": ((b['y1']+b['y2'])/2)-1 }++def boxAdd(b, cx, cy):+ if b:+ b['x1'] = min(b['x1'], cx)+ b['x2'] = max(b['x2'], cx)+ b['y1'] = min(b['y1'], cy)+ b['y2'] = max(b['y2'], cy)+ else:+ b = {'x1': cx, 'x2': cx, 'y1':cy, 'y2': cy}+ return b++def floodFill(im, pix, color, cx, cy, to_color=(0, 0, 0)):+ global b+ b = None++ global queue+ queue = [(cx, cy)]++ def visit((px, py)):+ global b+ global queue++ try:+ p = pix[px, py][:3]+ except IndexError:+ return++ if p == color:+ b = boxAdd(b, px, im.size[1] - py)+ pix[px, py] = to_color+ queue.append((px + 1, py))+ queue.append((px - 1, py))+ queue.append((px, py + 1))+ queue.append((px, py - 1))++ while queue:+ visit(queue.pop(0))++ return b and boxCenter(b)++if not os.path.exists("assets"):+ os.makedirs("assets")++art_names = glob.glob("tiff/*.tif")++sources_seen = 0+print("")+print_status("| generating...")+for in_name in art_names:+ sources_seen += 1+ print_status(": [{} of {}]".format(sources_seen, len(art_names)), in_name, "|")+ print_status("h")+ with open(in_name, "r") as im_data:+ im_hash = hashlib.sha1(im_data.read()).hexdigest()[:16]+ print_status("l")+ im = Image.open(in_name)+ grip_name = os.path.join("assets", "{}.json".format(im_hash))+ if os.path.exists(grip_name):+ print_status("=")+ else:+ print_status("g")+ root = []+ attach = []+ pix = im.load()+ for cx in range(im.size[0]):+ for cy in range(im.size[1]):+ p = pix[cx, cy][:3]+ if p == (255, 255, 0) or p == (0, 0, 255): # root+ to_color = (255, 255, 255, 0) if p[2] == 255 else (0, 0, 0, 255)+ root.append(floodFill(im, pix, p, cx, cy, to_color))+ print_status("a")+ elif p == (255, 0, 0) or p == (0, 255, 0): # attach+ to_color = (255, 255, 255, 0) if p[1] == 255 else (0, 0, 0, 255)+ attach.append(floodFill(im, pix, p, cx, cy, to_color))+ print_status("r")++ # scale points to 1x pixel density+ for points in (root, attach):+ for point in points:+ point["x"] = int(point["x"] * RATIO_1X)+ point["y"] = int(point["y"] * RATIO_1X)++ with open(grip_name, "w") as f:+ json.dump({"attach": attach, "root": root and root[0]}, f)++ fname = os.path.basename(in_name)+ fbarename = os.path.splitext(fname)[0]++ for ratio, prefix in ((RATIO_1X, ''), (RATIO_2X, '2x-')):+ resized_name = os.path.join("assets", "{}{}.png".format(prefix, fbarename))+ print_status("r")+ im_resized = im.resize((int(im.size[0] * ratio), int(im.size[1] * ratio)), Image.ANTIALIAS)++ im_resized = im_resized.convert('RGBA')+ pix_resized = im_resized.load()+ # convert white to alpha+ for cx in range(im_resized.size[0]):+ for cy in range(im_resized.size[1]):+ p = pix_resized[cx, cy]+ pix_resized[cx, cy] = (0, 0, 0, 255 - (sum(p[:3]) / 3))++ print_status("w")+ if im_resized.info.has_key("icc_profile"):+ del im_resized.info["icc_profile"]+ im_resized.save(resized_name, "PNG", optimize=True)++ print(".")+
+ linden.cabal view
@@ -0,0 +1,87 @@+name: linden+version: 0+synopsis: Zen gardening, based on l-systems+description: A system for defining and running interactive l-systems over the web.+homepage: http://xkcd.com/1663/+license: BSD3+license-file: LICENSE+author: davean+maintainer: davean@xkcd.com+copyright: davean 2016+category: Web+build-type: Simple+cabal-version: >=1.10++data-files:+ art/*.png+ art/*.svg+ client/package.json+ client/webpack.config.js+ client/build/index.html+ client/build/linden.js+ client/src/*.js+ client/src/*.less+ tiff/*.tif+ extract_grips.py+ assets/*.png+ assets/*.json++source-repository head+ type: git+ location: https://code.xkrd.net/xkcd/linden.git++library+ hs-source-dirs: src+ default-language: Haskell2010+ exposed-modules:+ Linden.TH+ , Linden.Types+ , Linden.RuleDSL+ , Linden.Store.Memory+ , Linden.Store.Redis+ , Linden.Server+ build-depends:+ base >=4.9 && <4.11+ , bifunctors >= 5.4 && < 5.5+ , deepseq >= 1.4 && < 1.5+ , containers >= 0.5 && < 0.6+ , rosezipper >= 0.2 && < 0.3+ , random-fu >= 0.2.6.2+ , aeson >= 1.2 && < 1.3+ , mtl >= 2.2 && < 2.3+ , monad-supply == 0.6.*+ , bytestring >= 0.10 && < 0.11+ , text >= 1.2 && < 1.3+ , wai >= 3.2 && < 3.3+ , http-types >= 0.9 && < 0.10+ , wai-extra >= 3.0 && < 3.1+ , wai-app-static >= 3.1 && < 3.2+ , wai-cors >= 0.2 && < 0.3+ , delay >= 0 && < 0.1+ , time >= 1.7 && < 1.9+ , filepath >= 1.4 && < 1.5+ , SHA >= 1.6 && < 1.7+ , uuid >= 1.3 && < 1.4+ , unordered-containers >= 0.2 && < 0.3+ , lens >= 4.15 && < 4.16+ , exceptions >= 0.8 && < 0.9+ , hashable >= 1.2 && < 1.3+ , hedis >= 0.9 && < 0.10+ , zlib >= 0.6 && < 0.7++executable linden-example+ default-language: Haskell2010+ ghc-options: -rtsopts -threaded+ main-is: Linden.hs+ other-modules: Paths_linden+ build-depends:+ base >=4.9 && <4.11+ , linden+ , containers+ , rosezipper+ , mtl+ , random-fu+ , warp >= 3.2 && < 3.3+ , uuid+ , hashable+ , text
+ src/Linden/RuleDSL.hs view
@@ -0,0 +1,261 @@+{-# LANGUAGE OverloadedStrings, DeriveGeneric #-}+module Linden.RuleDSL (+ RuleContext(..), RuleDSL, compileRules+ , addOption+ , doNothing, deleteMe, addBranch, addBranches+ , alreadyExists, noDup, nodeCount, maxNodes+ , curBranch, gaiaPathSyms, angleSum, angleToVertical+ , distance, distanceSq, curChildren+ , lightCones, lightHits, lightAng, lightIntensity+ , colorTemp+ , materialChildrenSyms, directSiblings+ , weighted, readBranchSym, attachLocation, allLights+ -- To suppress warnings about constructors used for TH naming+ , pX, pY, gRoot, gAttach+ , Point(..), Grips(..)+ ) where++import Data.Char+import Data.Maybe+import Data.Tree+import Data.Tree.Zipper+import Data.Bifunctor+import Data.Biapplicative+import Data.Random.RVar+import Data.Random.Distribution.Categorical+import Control.Monad.Identity+import Control.Monad.Reader+import Control.Monad.Writer.Strict+import Control.Monad.Supply (MonadSupply(..))+import qualified Data.ByteString as BS+import qualified Data.ByteString.Lazy as BSL+import Data.Aeson ((.:))+import qualified Data.Aeson as JS+import qualified Data.Text as T+import GHC.Generics+import System.FilePath+import Data.Digest.Pure.SHA (sha1, showDigest)+import qualified Data.Map.Lazy as Map+import Data.Hashable (hash)++import Debug.Trace++import Linden.Types++data Point =+ Point { pX :: Int, pY :: Int }+ deriving (Read, Show, Eq, Ord, Generic)++instance JS.ToJSON Point where+ toJSON (Point x y) = JS.object [("x", JS.toJSON x), ("y", JS.toJSON y)]++instance JS.FromJSON Point where+ parseJSON (JS.Object o) =+ Point <$> o .: "x" <*> o .: "y"+ parseJSON _ = mzero++data Grips =+ Grips {+ gRoot :: Point+ , gAttach :: [Point]+ }+ deriving (Read, Show, Eq, Ord, Generic)++instance JS.ToJSON Grips where+ toJSON (Grips r a) = JS.object [("attach", JS.toJSON a), ("root", JS.toJSON r)]++instance JS.FromJSON Grips where+ parseJSON (JS.Object o) =+ Grips <$> o .: "root" <*> o .: "attach"+ parseJSON _ = mzero++unPoint :: Point -> (Int, Int)+unPoint (Point x y) = (x, y)++readBranchSym :: [Int] -> FilePath -> ReaderT FilePath IO BranchSym+readBranchSym = readBranchSym'++readBranchSym' :: [Int] -> FilePath -> ReaderT FilePath IO BranchSym+readBranchSym' cls fp = do+ bp <- ask+ imfl <- liftIO $ BSL.readFile (bp </> "tiff" </> (addExtension fp "tif"))+ let imhash = take 16 . showDigest . sha1 $ imfl+ let gfl = bp </> "assets" </> (take 16 imhash++".json")+ gjson <- liftIO $ BS.readFile gfl+ case JS.eitherDecodeStrict' gjson of+ Left err -> fail $ "Failed to parse json of "++fp++" because "++show err+ Right (Grips (Point rX rY) as) ->+ return $ BranchSym (Just . T.pack $ addExtension fp "png")+ (rX, rY) (map unPoint as) False False (map hash cls)++weighted :: [(Double, LEnv EditCommand)] -> RVar (LEnv EditCommand)+weighted = weightedCategorical++data RuleContext =+ RC {+ rcLights :: [Light]+ , rcAttachLocs :: BranchPossition+ , rcTreePos :: TreePos Full Branch+ }+ deriving (Read, Show, Eq)++type RuleDSL a = ReaderT RuleContext (WriterT [(Double, LEnv EditCommand)] RVar) a++compileRules :: RuleDSL () -> Rule+compileRules act bps lights tp =+ weighted =<< (execWriterT . runReaderT act $ RC lights bps tp)++curBranch :: RuleDSL Branch+curBranch = label <$> asks rcTreePos++curChildren :: RuleDSL Int+curChildren = (length . offspring) <$> asks rcTreePos++directSiblings :: RuleDSL Int+directSiblings =+ (fromMaybe 0 . fmap ((1-) . length . subForest . tree) . parent) <$> asks rcTreePos++-- | Finds if a command adds an edge that already exists.+alreadyExists :: LEnv EditCommand -> RuleDSL Bool+alreadyExists mc = do+ let c = runLEnv mc (Supply 0) -- We're an ID-independant comparison.+ case fst c of+ DeleteMe -> return False+ DoNothing -> return False+ AddChildren nsf -> do+ esf <- offspring <$> asks rcTreePos+ return . or $ [ ((bAngle nc) == (bAngle ec)) && ((bImg nc) == (bImg ec))+ | nc <- rootLabel <$> nsf, ec <- fmap label esf]++nodeCount :: RuleDSL Int+nodeCount = (length . flatten . toTree) <$> asks rcTreePos++maxNodes :: Int -> RuleDSL () -> RuleDSL ()+maxNodes mx act = do+ nc <- nodeCount+ case nc of+ l | l >= mx -> doNothing 1+ _ -> act++isMaterial :: RuleDSL Bool+isMaterial = (not . isJust . bProxyFor . label) <$> asks rcTreePos++materialChildrenSyms :: RuleDSL [Branch]+materialChildrenSyms = do+ tp <- asks rcTreePos+ im <- isMaterial+ case im of+ -- We're material, so only our children can be immaterial.+ True -> return $ searchDown . tree $ tp+ False -> return $ maybe [] searchDown . findMaterial $ tp+ where+ findMaterial :: TreePos Full Branch -> Maybe (Tree Branch)+ findMaterial tp =+ case parent tp of+ Nothing -> Nothing+ Just tp' | (isJust . bProxyFor . label $ tp') -> findMaterial tp'+ Just tp' -> Just . tree $ tp'+ searchDown :: Tree Branch -> [Branch]+ searchDown (Node b sf) =+ case isJust . bProxyFor $ b of+ True -> concatMap searchDown sf+ False -> [b]++noDup :: RuleDSL a -> RuleDSL a+noDup act = do+ rc <- ask+ (a, w) <- lift . lift . runWriterT . runReaderT act $ rc+ forM_ w $ \(weight, ec) -> do+ d <- alreadyExists ec+ unless d $ tell [(weight, ec)]+ return a++addOption :: Double -> LEnv EditCommand -> RuleDSL ()+addOption w ec = tell [(w, ec)]++doNothing :: Double -> RuleDSL ()+doNothing w = addOption w . return $ DoNothing++deleteMe :: Double -> RuleDSL ()+deleteMe w = do+ im <- (not . bImmutable) <$> curBranch+ when im $ addOption w . return $ DeleteMe++explodeSym :: BranchSym -> Angle -> Scale -> Double -> LEnv (Tree Branch)+explodeSym (BranchSym img r [attach] im rgd cls) a s wv = do+ i <- supply+ return $ Node (Branch i img r attach a s im rgd cls Nothing wv) []+explodeSym (BranchSym img r attachs im rgd cls) a s wv = do+ i <- supply+ sf <- forM attachs $ \at -> do+ i' <- supply+ return $ Node (Branch i' Nothing (0, 0) at 0 1 im True cls (Just i) 0) []+ return $ Node (Branch i img r (0, 0) a s im rgd cls Nothing wv) sf++addBranch :: Double -> BranchSym -> Angle -> Scale -> Double -> RuleDSL ()+addBranch w bs a s wv =+ addOption w ((AddChildren . pure) <$> explodeSym bs a s wv)++addBranches :: Double -> [(BranchSym, Angle, Scale, Double)] -> RuleDSL ()+addBranches w as = addOption w $ do+ cs <- forM as $ \(bs, a, s, wv) -> do+ explodeSym bs a s wv+ return . AddChildren $ cs++gaiaPathSyms :: RuleDSL [Branch]+gaiaPathSyms = (map (\(_, p, _) -> p) . parents) <$> asks rcTreePos++angleToVertical :: RuleDSL Angle+angleToVertical = negate <$> angleSum++angleSum :: RuleDSL Angle+angleSum = (absAngle . sum . map bAngle) <$> gaiaPathSyms++absAngle :: Angle -> Angle+absAngle d = d + (fromIntegral $ (ceiling ((negate d) / 360.0)::Integer)) * 360.0++lightAng :: (X, Y) -> Light -> Angle+lightAng p l =+ let (dx, dy) = ((-), (-)) <<*>> p <<*>> (bimap fromIntegral fromIntegral $ lPos l)+ in absAngle $ (180/pi) * atan2 dy dx++lightHits :: (X, Y) -> Light -> Bool+lightHits p l =+ let ang = lightAng p l+ in (abs $ ang-(absAngle $ lPointAngle l)) <= (lBeamAngle l)/2++attachLocation :: RuleDSL (X, Y)+attachLocation =+ (fst . fromJust) <$> (Map.lookup <$> (bId <$> curBranch) <*> (asks rcAttachLocs))++allLights :: RuleDSL [Light]+allLights = asks rcLights++lightCones :: RuleDSL [Light]+lightCones = do+ p <- attachLocation+ filter (lightHits p) <$> asks rcLights++distance :: (X, Y) -> (Int, Int) -> Double+distance a b = sqrt . distanceSq a $ b++distanceSq :: (X, Y) -> (Int, Int) -> Double+distanceSq (x0, y0) (x1', y1') =+ ((x0-x1)^(2::Int))+((y0-y1)^(2::Int))+ where+ x1 = fromIntegral x1'+ y1 = fromIntegral y1'++colorTemp :: RuleDSL Double+colorTemp = do+ lcs <- lightCones+ return $ if (null lcs)+ then -10+ else (sum . map lTemp $ lcs) / (fromIntegral $ length lcs)++lightIntensity :: RuleDSL Double+lightIntensity = do+ p <- attachLocation+ hitting <- lightCones+ let intenses = map (\l -> (10^(4::Int))*(360/(lBeamAngle l)) / (distanceSq p (lPos l))) hitting+ return . sum $ 0:intenses
+ src/Linden/Server.hs view
@@ -0,0 +1,152 @@+{-# LANGUAGE OverloadedStrings, ScopedTypeVariables #-}+module Linden.Server (+ lindenApp+ ) where++import Control.Applicative+import Control.Monad+import qualified Data.ByteString.Lazy as BSL+import qualified Data.ByteString.Lazy.Builder as BSLB+import qualified Data.Aeson as JS+import Data.Maybe+import Data.IORef+import Data.Char+import Data.Bits+import Data.Monoid+import Data.Function+import Data.List+import qualified Data.Text as T+import qualified Data.Text.Encoding as TE+import Data.Random+import Control.Time+import Data.Time+import Data.UUID (UUID)+import qualified Data.UUID as UUID+import qualified Data.UUID.V1 as V1+import qualified Data.Map as Map+import Data.Digest.Pure.SHA (sha512, showDigest)+import System.FilePath++import qualified Network.HTTP.Types.Method as HTTP+import qualified Network.HTTP.Types.Status as HTTP+import qualified Network.Wai as WAI+import Network.Wai.EventSource+import Network.Wai.Middleware.Gzip+import Network.Wai.Application.Static+import Network.Wai.Middleware.Rewrite+import Network.Wai.UrlMap+import Network.Wai.Middleware.Cors+import Network.Wai.Request (requestSizeCheck)++import Linden.Types++lindenApp :: FilePath -> GardenStore -> BSL.ByteString -> NominalDiffTime -> AxiomSource -> IO Rule -> IO T.Text -> WAI.Application+lindenApp bp (GardenStore gsave gcas _) sec upRate axiom iorule upRule =+ gzip def . simpleCors . rewritePure rr . mapUrls $+ mount "run" eventTree+ <|> mount "rule" reRule+ <|> mount "edit" treeEdit+ <|> mount "art" (staticApp $ defaultWebAppSettings (bp </> "assets"))+ <|> mountRoot (staticApp $ defaultWebAppSettings (bp </> "client/build"))+ where+ rr [] _ = ["index.html"]+ rr ts _ = ts++ genEditKey :: UUID -> String+ genEditKey u = showDigest . sha512 $ sec <> UUID.toByteString u++ editKeyCheck :: UUID -> String -> Bool+ editKeyCheck u k =+ (length gk == length k) && 0 == (foldl1 (.|.) . zipWith (xor `on` ord) k $ gk)+ where gk = genEditKey u++ upDiff :: DiffTime+ upDiff = realToFrac upRate++ keyAuth :: UUID -> String -> (WAI.Response -> IO WAI.ResponseReceived) -> IO WAI.ResponseReceived -> IO WAI.ResponseReceived+ keyAuth u k _ app | editKeyCheck u k = app+ keyAuth _ _ respond _ | otherwise =+ respond $ WAI.responseBuilder HTTP.status403 [] "Bad Edit Key"++ reRule :: WAI.Application+ reRule _ respond = do+ errs <- upRule+ respond $ WAI.responseLBS HTTP.status200 [] . BSL.fromStrict . TE.encodeUtf8 $ errs++ treeEdit :: WAI.Application+ treeEdit req respond | (WAI.requestMethod req == HTTP.methodPost) = do+ reqBody <- WAI.strictRequestBody =<< requestSizeCheck 1024 req+ case JS.eitherDecode' reqBody of+ Left err -> respond $ WAI.responseBuilder HTTP.status400 [] .+ BSLB.byteString . TE.encodeUtf8 . T.pack $ err+ Right (UserDel gdn k tgt) -> keyAuth gdn k respond $ do+ now <- getCurrentTime+ void . gcas gdn $ \(LS i _ s0 ls (Just (PrettyJSTree t0))) -> do+ return ( Just . LS i now s0 (filter (\lt -> (lId lt) /=tgt) ls) .+ fmap PrettyJSTree . filterTree (\b -> tgt /= (bId b)) $ t0+ , ())+ respond $ WAI.responseBuilder HTTP.status200 [] "Ok"+ Right (UserEditLight gdn k ml loc p w c) -> keyAuth gdn k respond $ do+ now <- getCurrentTime+ nlight <- gcas gdn $ \(ls@(LS _ _ s lts _)) -> do+ let (tl, ol) = partition (\l' -> (Exists $ lId l')==ml) lts+ let (s', newLights) =+ case (tl, ml, length ol < 3) of+ ([_], Exists l, _) -> (s, [Light l loc p w c])+ ([], IsNull, True) -> (s+1, [Light s loc p w c])+ _ -> (s, [])+ let allLights = newLights++ol+ return ( Just $ ls { lsSupply = s'+ , lsLastUpdate = now+ , lsLights = allLights }+ , Just allLights)+ respond $ WAI.responseBuilder HTTP.status200 [] .+ BSLB.lazyByteString . JS.encode $ nlight+ treeEdit _ respond =+ respond $ WAI.responseLBS HTTP.status405 [] "Bad method"++ eventSendGarden :: Maybe LState -> IO ServerEvent+ eventSendGarden mns = return $+ case mns of+ Nothing -> CloseEvent+ Just ns ->+ ServerEvent (Just "linden") Nothing .+ pure . BSLB.lazyByteString . JS.encode $ ns++ eventTree :: WAI.Application+ eventTree req respond = do+ (u, ek) <- case join . fmap UUID.fromText . listToMaybe . WAI.pathInfo $ req of+ Nothing -> do+ Just uuid <- V1.nextUUID+ now <- getCurrentTime+ ax <- runRVar (axiom uuid now) StdRandom+ gsave ax+ return (uuid, Left $ genEditKey uuid)+ Just u' -> return (u', Right False)+ stepR <- newIORef ek+ eventSourceAppIO (do+ stp <- readIORef stepR+ case stp of+ Left k -> do+ writeIORef stepR (Right False)+ return . ServerEvent (Just "linden-key") Nothing .+ pure . BSLB.lazyByteString . JS.encode . Map.fromList $+ [ ("garden", UUID.toText u)+ , ("key"::T.Text, T.pack k) ]+ Right False -> do+ writeIORef stepR (Right True)+ mns <- gcas u (\ls -> return (Nothing, ls))+ eventSendGarden mns+ _ -> do+ delay upDiff+ now <- getCurrentTime+ rule <- iorule+ mns <- gcas u (\ls ->+ -- make sure we only update if its been long enough.+ case (now `diffUTCTime` (lsLastUpdate ls)) <= upRate of+ True -> return (Nothing, ls)+ False -> do+ upped <- step rule now ls+ return (Just upped, upped))+ eventSendGarden mns) req respond+
+ src/Linden/Store/Memory.hs view
@@ -0,0 +1,37 @@+{-# LANGUAGE ScopedTypeVariables, ImpredicativeTypes, RankNTypes #-}+module Linden.Store.Memory (+ memStore+ ) where++import Data.UUID (UUID)+import Data.Random+import Control.Concurrent.MVar+import Data.HashMap.Strict (HashMap)+import qualified Data.HashMap.Strict as HMap++import Linden.Types++memStore :: IO GardenStore+memStore = do+ ms <- newMVar HMap.empty+ return $ GardenStore (memSave ms) (memCAS ms) (memExists ms)+ where+ memSave :: MVar (HashMap UUID (MVar LState)) -> GardenSave+ memSave ms lcs = do+ nmv <- newMVar lcs+ modifyMVar_ ms (return . HMap.insert (lsGarden lcs) nmv)++ memCAS :: MVar (HashMap UUID (MVar LState)) -> GardenCAS+ memCAS ms = \u mut -> do+ mm <- (HMap.lookup u) <$> readMVar ms+ case mm of+ Nothing -> return Nothing+ Just m -> modifyMVar m $ \lcs -> do+ (mlcs', a) <- runRVar (mut lcs) StdRandom+ case mlcs' of+ Nothing -> return (lcs, Just a)+ Just lcs' -> return (lcs', Just a)++ memExists :: MVar (HashMap UUID (MVar LState)) -> GardenExists+ memExists ms u =+ (maybe False (const True) . HMap.lookup u) <$> takeMVar ms
+ src/Linden/Store/Redis.hs view
@@ -0,0 +1,75 @@+{-# LANGUAGE OverloadedStrings, RankNTypes, ScopedTypeVariables #-}+module Linden.Store.Redis (+ rStore+ ) where++import Control.Monad+import Control.Monad.Trans+import qualified Data.UUID as UUID+import qualified Data.Aeson as JS+import qualified Control.Monad.Catch as E+import qualified Control.Exception.Base as BE+import qualified Data.ByteString.Lazy as BSL+import Data.Random+import Control.Time+import Data.Time+import Database.Redis+import Codec.Compression.Zlib (compress, decompress)++import Linden.Types++backOff :: forall a. IO (Maybe a) -> IO (Maybe a)+backOff act =+ go 0+ where+ go :: Int -> IO (Maybe a)+ go 5 = return Nothing+ go e = E.handle (\(ex::E.SomeException) -> print ex >> delay (secondsToDiffTime $ 2^e) >> act) act++rStore :: Bool -> ConnectInfo -> IO GardenStore+rStore debug cinfo = do+ conn <- connect cinfo+ return $ GardenStore (rSave conn) (rCAS conn) undefined+ where+ rSave :: Connection -> GardenSave+ rSave conn lcs = runRedis conn $ do+ let ubs = UUID.toASCIIBytes . lsGarden $ lcs+ let bdy = BSL.toStrict . compress . JS.encode $ lcs+ void $ set ubs bdy++ rCAS :: Connection -> GardenCAS+ rCAS conn = \u mut -> backOff . runRedis conn $ do+ when debug . liftIO . putStrLn $ "CASing "++show u+ let ubs = UUID.toASCIIBytes u+ watch [ubs]+ eo <- get ubs+ case fmap (join . fmap (JS.decode' . decompress . BSL.fromStrict)) eo of+ Left err -> do+ liftIO . putStrLn $ " - "++show u ++ " got error: "++show err+ return Nothing+ Right Nothing -> do+ liftIO . putStrLn $ " - "++show u ++ " was not pressent or didn't decode!"+ return Nothing+ Right (Just g) -> do+ when debug . liftIO . putStrLn $ " - "++show u ++ " downloaded" + (mg', a) <- liftIO $ runRVar (mut g) StdRandom+ case mg' of+ Nothing -> do+ when debug . liftIO . putStrLn $ " - "++show u ++ " no action."+ return $ Just a+ Just g' | g' == g -> do+ when debug . liftIO . putStrLn $ " - "++show u ++ " updated but same"+ return $ Just a+ Just g' -> do+ when debug . liftIO . putStrLn $ " - "++show u ++ " updated"+ let bdy = BSL.toStrict . compress . JS.encode $ g'+ txRes <- multiExec $ do+ (fmap (const ())) <$> set ubs bdy+ case txRes of+ TxSuccess _ -> do+ when debug . liftIO . putStrLn $ " - "++show u ++ " CAS"+ return $ Just a+ TxAborted -> liftIO . E.throwM . BE.AssertionFailed $ "Raced"+ TxError err ->+ -- liftIO . putStrLn $ " - Error on multi: "++err+ liftIO . E.throwM . BE.AssertionFailed $ "retry"
+ src/Linden/TH.hs view
@@ -0,0 +1,8 @@+module Linden.TH (+ makeCamel+ ) where++import Data.Char++makeCamel :: Int -> String -> String+makeCamel d = (\l -> (toLower . head $ l):(tail l)) . drop d
+ src/Linden/Types.hs view
@@ -0,0 +1,255 @@+{-# LANGUAGE OverloadedStrings, DeriveGeneric, DeriveFunctor, DeriveAnyClass, TemplateHaskell, RankNTypes, FlexibleInstances, MultiParamTypeClasses, ScopedTypeVariables, ImpredicativeTypes #-}+module Linden.Types (+ BranchSym(..), Branch(..), X, Y, Angle, Scale+ , offspring+ , LState(..), Light(..), BranchPossition+ , step, EditCommand(..)+ , LEnvT, LEnv, runLEnvT, runLEnv, Supply(..)+ , Rule, AxiomSource+ , PrettyJSTree(..)+ , GardenStore(..), GardenSave, GardenCAS, GardenExists+ , UserCommand(..)+ , filterTree+ , Nullable(..)+ , makePossitions+ ) where++import GHC.Generics+import qualified Data.Text as T+import qualified Data.Aeson as JS+import Data.Aeson.TH+import Data.Bifunctor+import Data.Biapplicative+import Data.Word+import Data.Tree+import Data.Maybe+import Data.Time+import Data.UUID (UUID)+import Data.Tree.Zipper+import Data.Random.RVar+import Control.Monad.Identity+import Control.Monad.State.Lazy+import Control.Monad.Supply (MonadSupply(..))+import qualified Data.Map.Lazy as Map+import Control.DeepSeq++import Linden.TH++data BranchSym =+ BranchSym {+ bsImg :: Maybe T.Text+ , bsRoot :: !(Int, Int)+ , bsAttach :: ![(Int, Int)]+ , bsImmutable :: !Bool+ , bsRigid :: !Bool+ , bsClasses :: ![Int]+ }+ deriving (Read, Show, Eq, Ord, Generic, NFData)++type Angle = Double+type Scale = Double++data Branch =+ Branch {+ bId :: {-# UNPACK #-} !Word32+ , bImg :: Maybe T.Text+ , bRoot :: !(Int, Int)+ , bAttach :: !(Int, Int)+ , bAngle :: {-# UNPACK #-} !Angle+ , bScale :: {-# UNPACK #-}!Scale+ , bImmutable :: !Bool+ , bRigid :: !Bool+ , bClass :: ![Int]+ , bProxyFor :: Maybe Word32+ , bWaviness :: {-# UNPACK #-}!Double+ }+ deriving (Read, Show, Eq, Ord, Generic, NFData)++$(deriveJSON defaultOptions{fieldLabelModifier = makeCamel 1} ''Branch)++data Light =+ Light {+ lId :: {-# UNPACK #-}!Word32+ , lPos :: !(Int, Int)+ , lPointAngle :: {-# UNPACK #-}!Angle+ , lBeamAngle :: {-# UNPACK #-}!Angle+ , lTemp :: {-# UNPACK #-}!Double+ }+ deriving (Read, Show, Eq, Ord, Generic, NFData)++$(deriveJSON defaultOptions{fieldLabelModifier = makeCamel 1} ''Light)++newtype PrettyJSTree a =+ PrettyJSTree (Tree a)+ deriving (Read, Show, Eq, Generic, NFData)++instance JS.ToJSON a => JS.ToJSON (PrettyJSTree a) where+ toJSON (PrettyJSTree (Node a c)) =+ JS.object [ ("node", JS.toJSON a)+ , ("children", JS.toJSON . map PrettyJSTree $ c)]++instance JS.FromJSON a => JS.FromJSON (PrettyJSTree a) where+ parseJSON (JS.Object v) = do+ n <- v JS..: "node"+ c <- v JS..: "children"+ return . PrettyJSTree . Node n . map (\(PrettyJSTree t) -> t) $ c+ parseJSON _ = mzero++data LState =+ LS {+ lsGarden :: {-# UNPACK #-} !UUID+ , lsLastUpdate :: {-# UNPACK #-} !UTCTime+ , lsSupply :: {-# UNPACK #-}!Word32+ , lsLights :: ![Light]+ , lsTree :: Maybe (PrettyJSTree Branch)+ }+ deriving (Read, Show, Eq, Generic, NFData)++$(deriveJSON defaultOptions{fieldLabelModifier = makeCamel 2} ''LState)++type GardenSave = LState -> IO ()+-- Returns Nothing if the UUID doesn't exist in the store.+-- Action returns Nothing if there is no update.+type GardenCAS = forall a. UUID -> (LState -> RVar (Maybe LState, a)) -> IO (Maybe a)+type GardenExists = UUID -> IO Bool++data GardenStore =+ GardenStore GardenSave GardenCAS GardenExists++data Nullable a =+ IsNull+ | Exists a+ deriving (Read, Show, Eq, Ord, Generic, NFData)++instance JS.ToJSON a => JS.ToJSON (Nullable a) where+ toJSON (Exists a) = JS.toJSON a+ toJSON IsNull = JS.Null++instance JS.FromJSON a => JS.FromJSON (Nullable a) where+ parseJSON JS.Null = return IsNull+ parseJSON v = Exists <$> JS.parseJSON v++data UserCommand =+ UserDel { ucGarden :: UUID, ucEditKey :: String, ugNode :: Word32 }+ | UserEditLight { ucGarden :: UUID, ucEditKey :: String+ , ucLight :: Nullable Word32, ucPos :: (Int, Int)+ , ucPointAngle :: Angle, ucBeamAngle :: Angle+ , ucTemp :: Double }+ deriving (Read, Show, Eq, Ord, Generic, NFData)++$(deriveJSON defaultOptions {+ fieldLabelModifier = makeCamel 2+ , constructorTagModifier = drop 4} ''UserCommand)++filterTree :: (Branch -> Bool) -> Tree Branch -> Maybe (Tree Branch)+filterTree f (Node b sf) =+ if f b+ then Just (Node b (mapMaybe (filterTree f) sf))+ else Nothing++data EditCommand = DoNothing | AddChildren [Tree Branch] | DeleteMe++newtype Supply = Supply Word32++type X = Double+type Y = Double++newtype LEnvT m a =+ LEnvT { runLEnvT :: Supply -> m (a, Supply) }++type LEnv = LEnvT Identity++runLEnv :: LEnv a -> Supply -> (a, Supply)+runLEnv a = runIdentity . runLEnvT a++instance (Functor m) => Functor (LEnvT m) where+ fmap f m = LEnvT $ \s ->+ fmap (\ ~(a, s') -> (f a, s')) $ runLEnvT m s++instance (Functor m, Monad m) => Applicative (LEnvT m) where+ pure a = LEnvT $ \s -> return (a, s)+ LEnvT mf <*> LEnvT mx = LEnvT $ \s -> do+ ~(f, s') <- mf s+ ~(x, s'') <- mx s'+ return (f x, s'')+ {-# INLINE (<*>) #-}++instance (Monad m) => Monad (LEnvT m) where+ m >>= k = LEnvT $ \s -> do+ ~(a, s') <- runLEnvT m s+ runLEnvT (k a) s'+ fail str = LEnvT $ \_ -> fail str++instance (Monad m) => MonadState Supply (LEnvT m) where+ state f = LEnvT (return . f)++instance MonadTrans LEnvT where+ lift m = LEnvT $ \s -> do+ a <- m+ return (a, s)++instance Monad m => MonadSupply Word32 (LEnvT m) where+ supply = state (\(Supply s) -> (s, Supply $ s+1))+ peek = (\(Supply s) -> s) <$> get+ exhausted = (\(Supply s) -> s == maxBound) <$> get++type BranchPossition = Map.Map Word32 ((X, Y), Angle)++makePossitions :: Tree Branch -> BranchPossition+makePossitions (Node b' sf') =+ go (Map.singleton (bId b') (attachOffset b', bAngle b')) (bId b') sf'+ where+ doublefy :: (Int, Int) -> (Double, Double)+ doublefy = bimap fromIntegral fromIntegral+ attachOffset :: Branch -> (X, Y)+ attachOffset b =+ ((-), (-)) <<*>> (doublefy $ bAttach b) <<*>> (doublefy $ bRoot b)+ go :: BranchPossition -> Word32 -> Forest Branch -> BranchPossition+ go memo pid cl = foldl (addChild pid) memo cl+ addChild :: Word32 -> BranchPossition -> Tree Branch -> BranchPossition+ addChild pid memo (Node c sf) =+ go (Map.insert (bId c) (attachPos pid memo c) memo) (bId c) sf+ attachPos :: Word32 -> BranchPossition -> Branch -> ((X, Y), Angle)+ attachPos pid memo c =+ let ((parX, parY), pAng) = memo Map.! pid+ ang = pAng + (bAngle c)+ (offX, offY) = attachOffset c+ (xosq, yosq) = ((^(2::Int)), (^(2::Int))) <<*>> attachOffset c+ cLen = (bScale c) * sqrt(xosq+yosq)+ rang = ang*(pi/180) + atan2 offY offX+ cX = cLen*(cos rang)+ cY = cLen*(sin rang)+ in ((parX+cX, parY+cY), ang)++type Rule = BranchPossition -> [Light] -> TreePos Full Branch -> RVar (LEnv EditCommand)++-- Axiom generator+type AxiomSource = UUID -> UTCTime -> RVar LState++offspring :: TreePos Full Branch -> [TreePos Full Branch]+offspring tp =+ go . children $ tp+ where+ go :: TreePos Empty Branch -> [TreePos Full Branch]+ go cf =+ case nextTree cf of+ Nothing -> []+ Just c -> c:(go . nextSpace $ c)++step :: Rule -> UTCTime -> LState -> RVar LState+step _ _ l@(LS _ _ _ _ Nothing) = return l+step rule n (LS i _ s0 lights (Just (PrettyJSTree t0))) =+ fmap (\(t1, Supply s1) -> force $ LS i n s1 lights . fmap PrettyJSTree $ t1) .+ (`runLEnvT` (Supply s0)) . applyProductions .+ fromTree $ (t0::Tree Branch)+ where+ bps = makePossitions t0+ applyProductions :: TreePos Full Branch -> LEnvT RVar (Maybe (Tree Branch))+ applyProductions tp = do+ let sym = label tp+ cuped <- fmap catMaybes . forM (offspring tp) $ \c -> applyProductions c+ com <- (lift . rule bps lights $ tp) >>= (\a -> state (runLEnv a))+ case com of+ DoNothing -> return . Just $ Node sym cuped+ AddChildren ts -> return . Just $ Node sym (ts++cuped)+ DeleteMe -> return Nothing
+ tiff/branch.tif view
binary file changed (absent → 687 bytes)