Render SVG to PNG from Elm via ports
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"type": "application", | |
"source-directories": [ | |
"src" | |
], | |
"elm-version": "0.19.1", | |
"dependencies": { | |
"direct": { | |
"Fresheyeball/elm-return": "7.1.0", | |
"danfishgold/base64-bytes": "1.1.0", | |
"elm/browser": "1.0.2", | |
"elm/bytes": "1.0.8", | |
"elm/core": "1.0.5", | |
"elm/file": "1.0.5", | |
"elm/html": "1.0.0", | |
"elm/json": "1.1.3", | |
"elm/svg": "1.0.1" | |
}, | |
"indirect": { | |
"elm/time": "1.0.0", | |
"elm/url": "1.0.0", | |
"elm/virtual-dom": "1.0.2" | |
} | |
}, | |
"test-dependencies": { | |
"direct": {}, | |
"indirect": {} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE HTML> | |
<html> | |
<head> | |
<meta charset="UTF-8"> | |
<title>Rendering SVG to PNG from Elm</title> | |
<script type="text/javascript" src="main.js"></script> | |
</head> | |
<body> | |
<main id="main"></main> | |
</body> | |
<script type="text/javascript"> | |
var app = Elm.Main.init({ | |
node: document.getElementById('main') | |
}) | |
if (app.ports.renderPngRequest && app.ports.renderPngResult) { | |
app.ports.renderPngRequest.subscribe(function(config) { | |
// Wait for next frame to ensure SVG is rendered | |
requestAnimationFrame(function() { | |
config.ids.forEach(id => { | |
exportSvgAsPng(id, config.size, function(error, dataUrl) { | |
var result = { id: id } | |
if (error) { | |
var msg = | |
'An error occured while rendering PNG of node #' + | |
id | |
console.error(msg, error) | |
result['error'] = | |
error && error.hasOwnProperty('message') | |
? error.message | |
: msg | |
} else { | |
result['dataUrl'] = dataUrl | |
} | |
app.ports.renderPngResult.send(result) | |
}) | |
}) | |
}) | |
}) | |
} | |
function exportSvgAsPng(id, targetSize, callback) { | |
var node = document.getElementById(id) | |
if (!node) { | |
callback(new Error('Element with id `' + id + '` not found')) | |
return | |
} | |
try { | |
var baseSvgString = new XMLSerializer().serializeToString(node) | |
// Detached canvas to render to | |
var canvas = document.createElement('canvas') | |
// Set dimensions of rendering canvas to match SVG; using targetSize for longer edge | |
var aspectRatio = getBestAspectRatio(node) | |
if (aspectRatio >= 1) { | |
canvas.width = targetSize | |
canvas.height = targetSize / aspectRatio | |
} else { | |
canvas.height = targetSize | |
canvas.width = targetSize * aspectRatio | |
} | |
// Parse the SVG into a new DOM which we adapt for rendering | |
var renderableSvg = new DOMParser().parseFromString( | |
baseSvgString, | |
'image/svg+xml' | |
) | |
// Firefox can't draw an SVG to canvas without knowing pixel size of SVG | |
// See https://bugzilla.mozilla.org/show_bug.cgi?id=700533#c39 | |
renderableSvg.documentElement.width.baseVal.newValueSpecifiedUnits( | |
SVGLength.SVG_LENGTHTYPE_PX, | |
canvas.width | |
) | |
renderableSvg.documentElement.height.baseVal.newValueSpecifiedUnits( | |
SVGLength.SVG_LENGTHTYPE_PX, | |
canvas.height | |
) | |
// Copy all page styles into the replicated SVG element | |
// This makes SVGs referencing stylesheets from the environment render properly | |
var styleNode = document.createElementNS( | |
'http://www.w3.org/2000/svg', | |
'style' | |
) | |
styleNode.textContent = serializePageStyles() | |
renderableSvg.documentElement.appendChild(styleNode) | |
var svgString = new XMLSerializer().serializeToString(renderableSvg) | |
var DOMURL = self.URL || self.webkitURL || self | |
// An URL targeting the in-memory SVG blob | |
var svg = new Blob([svgString], { | |
type: 'image/svg+xml;charset=utf-8' | |
}) | |
var url = DOMURL.createObjectURL(svg) | |
var img = new Image() | |
img.onload = function() { | |
try { | |
var ctx = canvas.getContext('2d') | |
ctx.drawImage(img, 0, 0) | |
// Data URI scheme use base64 to encode blob | |
var dataUrl = canvas.toDataURL('image/png') | |
// Free memory | |
DOMURL.revokeObjectURL(url) | |
callback(null, dataUrl) | |
} catch (error) { | |
callback(error) | |
} | |
} | |
img.src = url | |
} catch (error) { | |
callback(error) | |
} | |
} | |
/* Determine the best general aspect ratio of an SVG element. | |
* | |
* SVG elements appearing inline in an HTML document may vary in concrete | |
* dimensions, fluidly adapting to the surrounding page. | |
* | |
* We want our renderings to be as uniform as possible, no matter whether they | |
* were rendered on a tablet or desktop computer with a large screen. For this | |
* reason we try to determine the intrinsic ratio and only use the concrete | |
* ratio within the page as a last resort. | |
* | |
* The steps are informed by the sizing rules for replaced elements: | |
* https://www.w3.org/TR/CSS22/visudet.html#inline-replaced-width | |
* | |
*/ | |
function getBestAspectRatio(node) { | |
var aspectRatio | |
// 1. If explicit dimensions are set, they define the ratio | |
aspectRatio = coercedRatio( | |
node.getAttribute('width'), | |
node.getAttribute('height') | |
) | |
if (isLegalAspectRatio(aspectRatio)) return aspectRatio | |
// 2. If there were no explicit dimensions but there is a viewBox, it defines the ratio | |
if (typeof node.getAttribute('viewBox') === 'string') { | |
// viewBox is min-x, min-y, width, height; separated by whitespace and/or comma | |
var parts = node.getAttribute('viewBox').split(/[\s,]+/) | |
aspectRatio = coercedRatio(parts[2], parts[3]) | |
if (isLegalAspectRatio(aspectRatio)) return aspectRatio | |
} | |
// 3. If no explicit information is available, default to dimensions in page | |
// | |
// Here we deviate from the rules for replaced elements, assuming that how the | |
// SVG is rendered in the page is still more appropriate than the completely | |
// uninformed default value. | |
aspectRatio = coercedRatio(node.clientWidth, node.clientHeight) | |
if (isLegalAspectRatio(aspectRatio)) return aspectRatio | |
// 4. Fall back to 2:1 (https://www.w3.org/TR/CSS22/visudet.html#inline-replaced-width) | |
return 2 | |
} | |
function coercedRatio(x, y) { | |
x = typeof x === 'number' ? x : parseInt(x, 10) | |
y = typeof y === 'number' ? y : parseInt(y, 10) | |
return x / y | |
} | |
function isLegalAspectRatio(n) { | |
return typeof n === 'number' && !isNaN(n) && isFinite(n) && n > 0 | |
} | |
function serializePageStyles() { | |
let styles = '' | |
for (let sheet of document.styleSheets) { | |
for (let rule of sheet.rules) { | |
styles += rule.cssText + '\n' | |
} | |
} | |
return styles | |
} | |
</script> | |
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
module Loadable exposing | |
( Loadable(..), toMaybe, withDefault, fromResult | |
, map, andThen, apply, map2, mapError | |
, isInit, isLoading, isLoaded, isError, isDone, isPending | |
) | |
{-| Model loadable values | |
This is inspired by: <http://blog.jenkster.com/2016/06/how-elm-slays-a-ui-antipattern.html> | |
# Basics | |
@docs Loadable, toMaybe, withDefault, fromResult | |
# Mapping and applying | |
@docs map, andThen, apply, map2, mapError | |
# State | |
Helpers to get state. | |
@docs isInit, isLoading, isLoaded, isError, isDone, isPending | |
-} | |
{-| A loadable value | |
-} | |
type Loadable err a | |
= Init | |
| Loading | |
| Loaded a | |
| Error err | |
map : (a -> b) -> Loadable err a -> Loadable err b | |
map func ra = | |
case ra of | |
Loaded a -> | |
Loaded (func a) | |
Error err -> | |
Error err | |
Init -> | |
Init | |
Loading -> | |
Loading | |
mapError : (errA -> errB) -> Loadable errA a -> Loadable errB a | |
mapError func ra = | |
case ra of | |
Loaded a -> | |
Loaded a | |
Error err -> | |
Error (func err) | |
Init -> | |
Init | |
Loading -> | |
Loading | |
andThen : (a -> Loadable err b) -> Loadable err a -> Loadable err b | |
andThen func la = | |
case la of | |
Init -> | |
Init | |
Loading -> | |
Loading | |
Loaded a -> | |
func a | |
Error err -> | |
Error err | |
{-| map2 for loadables | |
The more critical states are propagated, the result is | |
- init if one branch is still in init, then | |
- loading if one branch is loading, then | |
- error if one branch is an error, finally | |
- loaded if both are loaded. | |
This means that the combined loadable will be pending while any of the branches | |
is. | |
-} | |
map2 : (a -> b -> c) -> Loadable err a -> Loadable err b -> Loadable err c | |
map2 fn la lb = | |
case ( la, lb ) of | |
( Init, _ ) -> | |
Init | |
( _, Init ) -> | |
Init | |
( Loading, _ ) -> | |
Loading | |
( _, Loading ) -> | |
Loading | |
( Error err, _ ) -> | |
Error err | |
( _, Error err ) -> | |
Error err | |
( Loaded a, Loaded b ) -> | |
Loaded (fn a b) | |
apply : Loadable err a -> Loadable err (a -> b) -> Loadable err b | |
apply la lf = | |
map2 (\a f -> f a) la lf | |
toMaybe : Loadable err a -> Maybe a | |
toMaybe ra = | |
case ra of | |
Loaded a -> | |
Just a | |
_ -> | |
Nothing | |
isPending : Loadable err a -> Bool | |
isPending loadable = | |
isInit loadable || isLoading loadable | |
isInit : Loadable err a -> Bool | |
isInit loadable = | |
case loadable of | |
Init -> | |
True | |
_ -> | |
False | |
isLoading : Loadable err a -> Bool | |
isLoading ra = | |
case ra of | |
Loading -> | |
True | |
_ -> | |
False | |
isLoaded : Loadable err a -> Bool | |
isLoaded ra = | |
case ra of | |
Loaded _ -> | |
True | |
_ -> | |
False | |
isError : Loadable err a -> Bool | |
isError ra = | |
case ra of | |
Error _ -> | |
True | |
_ -> | |
False | |
isDone : Loadable err a -> Bool | |
isDone ra = | |
isLoaded ra || isError ra | |
fromResult : Result err a -> Loadable err a | |
fromResult result = | |
case result of | |
Ok a -> | |
Loaded a | |
Err err -> | |
Error err | |
withDefault : a -> Loadable err a -> a | |
withDefault default loadable = | |
case loadable of | |
Loaded a -> | |
a | |
_ -> | |
default |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
module Main exposing (main) | |
{-| A simple example of using `RenderSvg` to rasterize an SVG element. | |
An SVG element can be rendered to custom pixel dimensions and the generated PNG downloaded. | |
-} | |
import Base64 | |
import Browser | |
import Bytes exposing (Bytes) | |
import File.Download | |
import Html as H | |
import Html.Attributes as HA | |
import Html.Events as HE | |
import RenderSvg | |
import Svg as S | |
import Svg.Attributes as SA | |
shapesClubId : String | |
shapesClubId = | |
"shapes-club" | |
type alias Model = | |
{ size : Int | |
, renderer : Maybe RenderSvg.Model | |
} | |
getFirstSuccessfulRender : RenderSvg.Model -> Maybe ( String, Bytes ) | |
getFirstSuccessfulRender = | |
RenderSvg.getResults >> Maybe.andThen (.successes >> List.head) | |
type Msg | |
= RenderSvgMsg RenderSvg.Msg | |
| SizeChanged String | |
| RenderRequested | |
| DownloadRequested | |
main : Program () Model Msg | |
main = | |
Browser.element | |
{ init = always ( { size = 800, renderer = Nothing }, Cmd.none ) | |
, subscriptions = always (Sub.map RenderSvgMsg RenderSvg.subscriptions) | |
, view = view | |
, update = update | |
} | |
update : Msg -> Model -> ( Model, Cmd Msg ) | |
update msg model = | |
case ( msg, model.renderer ) of | |
( SizeChanged newValue, _ ) -> | |
( { model | size = String.toInt newValue |> Maybe.withDefault model.size }, Cmd.none ) | |
( RenderRequested, _ ) -> | |
let | |
( subModel, subCmd ) = | |
-- Start rendering with user-defined size | |
RenderSvg.init { ids = [ shapesClubId ], size = model.size } | |
in | |
( { model | renderer = Just subModel }, Cmd.map RenderSvgMsg subCmd ) | |
( DownloadRequested, Just subModel ) -> | |
( model | |
, getFirstSuccessfulRender subModel | |
|> Maybe.map (\( _, bytes ) -> File.Download.bytes (shapesClubId ++ ".png") "image/png" bytes) | |
|> Maybe.withDefault Cmd.none | |
) | |
( RenderSvgMsg subMsg, Just subModel ) -> | |
let | |
( newSubModel, subCmd ) = | |
RenderSvg.update subMsg subModel | |
in | |
( { model | renderer = Just newSubModel }, Cmd.map RenderSvgMsg subCmd ) | |
_ -> | |
( model, Cmd.none ) | |
view : Model -> H.Html Msg | |
view model = | |
let | |
toDataUri = | |
Base64.fromBytes >> Maybe.map (\b64 -> "data:image/png;base64," ++ b64) | |
viewFailures { failures } = | |
H.dl | |
[] | |
(List.map | |
(\( id, error ) -> | |
H.div [] [ H.dt [] [ H.text id ], H.dd [] [ H.text error ] ] | |
) | |
failures | |
) | |
in | |
H.div | |
[] | |
[ explanation | |
, H.div | |
[ HA.style "display" "grid", HA.style "grid-template-columns" "1fr 1fr" ] | |
[ H.section | |
[] | |
[ H.h2 [] [ H.text "SVG Graphic" ] | |
, H.form | |
[ HE.onSubmit RenderRequested ] | |
[ H.label [] [ H.text "Size: ", H.input [ HA.value (String.fromInt model.size), HE.onInput SizeChanged ] [] ] | |
, H.text " " | |
, H.button [ HA.type_ "submit" ] [ H.text "Render to PNG" ] | |
] | |
, shapesClub | |
] | |
, H.section | |
[] | |
[ model.renderer | |
|> Maybe.andThen getFirstSuccessfulRender | |
|> Maybe.andThen (\( id, bytes ) -> toDataUri bytes) | |
|> Maybe.map | |
(\uri -> | |
H.div | |
[] | |
[ H.h2 [] [ H.text "Rasterized PNG" ] | |
, H.p [] [ H.button [ HE.onClick DownloadRequested ] [ H.text "Download" ] ] | |
, H.img [ HA.src uri ] [] | |
] | |
) | |
|> Maybe.withDefault (H.text "") | |
, model.renderer | |
|> Maybe.andThen RenderSvg.getResults | |
|> Maybe.map viewFailures | |
|> Maybe.withDefault (H.text "") | |
] | |
] | |
] | |
{-| A dummy SVG graphic. | |
Stolen from <https://elm-lang.org/examples/shapes> | |
-} | |
shapesClub : H.Html msg | |
shapesClub = | |
S.svg | |
[ SA.viewBox "0 0 400 220" | |
, SA.width "400" | |
, SA.height "220" | |
, SA.id shapesClubId | |
] | |
[ S.circle | |
[ SA.cx "50" | |
, SA.cy "50" | |
, SA.r "40" | |
, SA.fill "red" | |
, SA.stroke "black" | |
, SA.strokeWidth "3" | |
] | |
[] | |
, S.rect | |
[ SA.x "100" | |
, SA.y "10" | |
, SA.width "40" | |
, SA.height "40" | |
, SA.fill "green" | |
, SA.stroke "black" | |
, SA.strokeWidth "2" | |
] | |
[] | |
, S.line | |
[ SA.x1 "20" | |
, SA.y1 "200" | |
, SA.x2 "200" | |
, SA.y2 "20" | |
, SA.stroke "blue" | |
, SA.strokeWidth "10" | |
, SA.strokeLinecap "round" | |
] | |
[] | |
, S.polyline | |
[ SA.points "200,40 240,40 240,80 280,80 280,120 320,120 320,160" | |
, SA.fill "none" | |
, SA.stroke "red" | |
, SA.strokeWidth "4" | |
, SA.strokeDasharray "20,2" | |
] | |
[] | |
, S.text_ | |
[ SA.x "130" | |
, SA.y "130" | |
, SA.fill "black" | |
, SA.textAnchor "middle" | |
, SA.dominantBaseline "central" | |
, SA.transform "rotate(-45 130,130)" | |
] | |
[ S.text "Welcome to Shapes Club" | |
] | |
] | |
explanation : H.Html msg | |
explanation = | |
H.section | |
[] | |
[ H.h1 [] [ H.text "SVG rendering from Elm" ] | |
, H.p | |
[] | |
[ H.text | |
"""This is a quick demo of rendering SVG to PNG from within an Elm application using ports. The core mechanism is to request rendering of particular elements by passing their IDs to a JS rendering function through a port. The SVG element is serialized to XML, which is loaded as an image and drawn to an invisible canvas. The canvas is serialized to a PNG data URL and the result passed back to the Elm application for deserialization.""" | |
] | |
, H.p | |
[] | |
[ H.text """A limitation of this approach is that assets referenced by URL in stylesheets or the SVG can not be loaded in the sandboxed environment the SVG is rendered in. A workaround is embedding external assets as data URLs.""" | |
] | |
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(function(scope){ | |
'use strict'; | |
function F(arity, fun, wrapper) { | |
wrapper.a = arity; | |
wrapper.f = fun; | |
return wrapper; | |
} | |
function F2(fun) { | |
return F(2, fun, function(a) { return function(b) { return fun(a,b); }; }) | |
} | |
function F3(fun) { | |
return F(3, fun, function(a) { | |
return function(b) { return function(c) { return fun(a, b, c); }; }; | |
}); | |
} | |
function F4(fun) { | |
return F(4, fun, function(a) { return function(b) { return function(c) { | |
return function(d) { return fun(a, b, c, d); }; }; }; | |
}); | |
} | |
function F5(fun) { | |
return F(5, fun, function(a) { return function(b) { return function(c) { | |
return function(d) { return function(e) { return fun(a, b, c, d, e); }; }; }; }; | |
}); | |
} | |
function F6(fun) { | |
return F(6, fun, function(a) { return function(b) { return function(c) { | |
return function(d) { return function(e) { return function(f) { | |
return fun(a, b, c, d, e, f); }; }; }; }; }; | |
}); | |
} | |
function F7(fun) { | |
return F(7, fun, function(a) { return function(b) { return function(c) { | |
return function(d) { return function(e) { return function(f) { | |
return function(g) { return fun(a, b, c, d, e, f, g); }; }; }; }; }; }; | |
}); | |
} | |
function F8(fun) { | |
return F(8, fun, function(a) { return function(b) { return function(c) { | |
return function(d) { return function(e) { return function(f) { | |
return function(g) { return function(h) { | |
return fun(a, b, c, d, e, f, g, h); }; }; }; }; }; }; }; | |
}); | |
} | |
function F9(fun) { | |
return F(9, fun, function(a) { return function(b) { return function(c) { | |
return function(d) { return function(e) { return function(f) { | |
return function(g) { return function(h) { return function(i) { | |
return fun(a, b, c, d, e, f, g, h, i); }; }; }; }; }; }; }; }; | |
}); | |
} | |
function A2(fun, a, b) { | |
return fun.a === 2 ? fun.f(a, b) : fun(a)(b); | |
} | |
function A3(fun, a, b, c) { | |
return fun.a === 3 ? fun.f(a, b, c) : fun(a)(b)(c); | |
} | |
function A4(fun, a, b, c, d) { | |
return fun.a === 4 ? fun.f(a, b, c, d) : fun(a)(b)(c)(d); | |
} | |
function A5(fun, a, b, c, d, e) { | |
return fun.a === 5 ? fun.f(a, b, c, d, e) : fun(a)(b)(c)(d)(e); | |
} | |
function A6(fun, a, b, c, d, e, f) { | |
return fun.a === 6 ? fun.f(a, b, c, d, e, f) : fun(a)(b)(c)(d)(e)(f); | |
} | |
function A7(fun, a, b, c, d, e, f, g) { | |
return fun.a === 7 ? fun.f(a, b, c, d, e, f, g) : fun(a)(b)(c)(d)(e)(f)(g); | |
} | |
function A8(fun, a, b, c, d, e, f, g, h) { | |
return fun.a === 8 ? fun.f(a, b, c, d, e, f, g, h) : fun(a)(b)(c)(d)(e)(f)(g)(h); | |
} | |
function A9(fun, a, b, c, d, e, f, g, h, i) { | |
return fun.a === 9 ? fun.f(a, b, c, d, e, f, g, h, i) : fun(a)(b)(c)(d)(e)(f)(g)(h)(i); | |
} | |
console.warn('Compiled in DEV mode. Follow the advice at https://elm-lang.org/0.19.1/optimize for better performance and smaller assets.'); | |
var _List_Nil_UNUSED = { $: 0 }; | |
var _List_Nil = { $: '[]' }; | |
function _List_Cons_UNUSED(hd, tl) { return { $: 1, a: hd, b: tl }; } | |
function _List_Cons(hd, tl) { return { $: '::', a: hd, b: tl }; } | |
var _List_cons = F2(_List_Cons); | |
function _List_fromArray(arr) | |
{ | |
var out = _List_Nil; | |
for (var i = arr.length; i--; ) | |
{ | |
out = _List_Cons(arr[i], out); | |
} | |
return out; | |
} | |
function _List_toArray(xs) | |
{ | |
for (var out = []; xs.b; xs = xs.b) // WHILE_CONS | |
{ | |
out.push(xs.a); | |
} | |
return out; | |
} | |
var _List_map2 = F3(function(f, xs, ys) | |
{ | |
for (var arr = []; xs.b && ys.b; xs = xs.b, ys = ys.b) // WHILE_CONSES | |
{ | |
arr.push(A2(f, xs.a, ys.a)); | |
} | |
return _List_fromArray(arr); | |
}); | |
var _List_map3 = F4(function(f, xs, ys, zs) | |
{ | |
for (var arr = []; xs.b && ys.b && zs.b; xs = xs.b, ys = ys.b, zs = zs.b) // WHILE_CONSES | |
{ | |
arr.push(A3(f, xs.a, ys.a, zs.a)); | |
} | |
return _List_fromArray(arr); | |
}); | |
var _List_map4 = F5(function(f, ws, xs, ys, zs) | |
{ | |
for (var arr = []; ws.b && xs.b && ys.b && zs.b; ws = ws.b, xs = xs.b, ys = ys.b, zs = zs.b) // WHILE_CONSES | |
{ | |
arr.push(A4(f, ws.a, xs.a, ys.a, zs.a)); | |
} | |
return _List_fromArray(arr); | |
}); | |
var _List_map5 = F6(function(f, vs, ws, xs, ys, zs) | |
{ | |
for (var arr = []; vs.b && ws.b && xs.b && ys.b && zs.b; vs = vs.b, ws = ws.b, xs = xs.b, ys = ys.b, zs = zs.b) // WHILE_CONSES | |
{ | |
arr.push(A5(f, vs.a, ws.a, xs.a, ys.a, zs.a)); | |
} | |
return _List_fromArray(arr); | |
}); | |
var _List_sortBy = F2(function(f, xs) | |
{ | |
return _List_fromArray(_List_toArray(xs).sort(function(a, b) { | |
return _Utils_cmp(f(a), f(b)); | |
})); | |
}); | |
var _List_sortWith = F2(function(f, xs) | |
{ | |
return _List_fromArray(_List_toArray(xs).sort(function(a, b) { | |
var ord = A2(f, a, b); | |
return ord === $elm$core$Basics$EQ ? 0 : ord === $elm$core$Basics$LT ? -1 : 1; | |
})); | |
}); | |
var _JsArray_empty = []; | |
function _JsArray_singleton(value) | |
{ | |
return [value]; | |
} | |
function _JsArray_length(array) | |
{ | |
return array.length; | |
} | |
var _JsArray_initialize = F3(function(size, offset, func) | |
{ | |
var result = new Array(size); | |
for (var i = 0; i < size; i++) | |
{ | |
result[i] = func(offset + i); | |
} | |
return result; | |
}); | |
var _JsArray_initializeFromList = F2(function (max, ls) | |
{ | |
var result = new Array(max); | |
for (var i = 0; i < max && ls.b; i++) | |
{ | |
result[i] = ls.a; | |
ls = ls.b; | |
} | |
result.length = i; | |
return _Utils_Tuple2(result, ls); | |
}); | |
var _JsArray_unsafeGet = F2(function(index, array) | |
{ | |
return array[index]; | |
}); | |
var _JsArray_unsafeSet = F3(function(index, value, array) | |
{ | |
var length = array.length; | |
var result = new Array(length); | |
for (var i = 0; i < length; i++) | |
{ | |
result[i] = array[i]; | |
} | |
result[index] = value; | |
return result; | |
}); | |
var _JsArray_push = F2(function(value, array) | |
{ | |
var length = array.length; | |
var result = new Array(length + 1); | |
for (var i = 0; i < length; i++) | |
{ | |
result[i] = array[i]; | |
} | |
result[length] = value; | |
return result; | |
}); | |
var _JsArray_foldl = F3(function(func, acc, array) | |
{ | |
var length = array.length; | |
for (var i = 0; i < length; i++) | |
{ | |
acc = A2(func, array[i], acc); | |
} | |
return acc; | |
}); | |
var _JsArray_foldr = F3(function(func, acc, array) | |
{ | |
for (var i = array.length - 1; i >= 0; i--) | |
{ | |
acc = A2(func, array[i], acc); | |
} | |
return acc; | |
}); | |
var _JsArray_map = F2(function(func, array) | |
{ | |
var length = array.length; | |
var result = new Array(length); | |
for (var i = 0; i < length; i++) | |
{ | |
result[i] = func(array[i]); | |
} | |
return result; | |
}); | |
var _JsArray_indexedMap = F3(function(func, offset, array) | |
{ | |
var length = array.length; | |
var result = new Array(length); | |
for (var i = 0; i < length; i++) | |
{ | |
result[i] = A2(func, offset + i, array[i]); | |
} | |
return result; | |
}); | |
var _JsArray_slice = F3(function(from, to, array) | |
{ | |
return array.slice(from, to); | |
}); | |
var _JsArray_appendN = F3(function(n, dest, source) | |
{ | |
var destLen = dest.length; | |
var itemsToCopy = n - destLen; | |
if (itemsToCopy > source.length) | |
{ | |
itemsToCopy = source.length; | |
} | |
var size = destLen + itemsToCopy; | |
var result = new Array(size); | |
for (var i = 0; i < destLen; i++) | |
{ | |
result[i] = dest[i]; | |
} | |
for (var i = 0; i < itemsToCopy; i++) | |
{ | |
result[i + destLen] = source[i]; | |
} | |
return result; | |
}); | |
// LOG | |
var _Debug_log_UNUSED = F2(function(tag, value) | |
{ | |
return value; | |
}); | |
var _Debug_log = F2(function(tag, value) | |
{ | |
console.log(tag + ': ' + _Debug_toString(value)); | |
return value; | |
}); | |
// TODOS | |
function _Debug_todo(moduleName, region) | |
{ | |
return function(message) { | |
_Debug_crash(8, moduleName, region, message); | |
}; | |
} | |
function _Debug_todoCase(moduleName, region, value) | |
{ | |
return function(message) { | |
_Debug_crash(9, moduleName, region, value, message); | |
}; | |
} | |
// TO STRING | |
function _Debug_toString_UNUSED(value) | |
{ | |
return '<internals>'; | |
} | |
function _Debug_toString(value) | |
{ | |
return _Debug_toAnsiString(false, value); | |
} | |
function _Debug_toAnsiString(ansi, value) | |
{ | |
if (typeof value === 'function') | |
{ | |
return _Debug_internalColor(ansi, '<function>'); | |
} | |
if (typeof value === 'boolean') | |
{ | |
return _Debug_ctorColor(ansi, value ? 'True' : 'False'); | |
} | |
if (typeof value === 'number') | |
{ | |
return _Debug_numberColor(ansi, value + ''); | |
} | |
if (value instanceof String) | |
{ | |
return _Debug_charColor(ansi, "'" + _Debug_addSlashes(value, true) + "'"); | |
} | |
if (typeof value === 'string') | |
{ | |
return _Debug_stringColor(ansi, '"' + _Debug_addSlashes(value, false) + '"'); | |
} | |
if (typeof value === 'object' && '$' in value) | |
{ | |
var tag = value.$; | |
if (typeof tag === 'number') | |
{ | |
return _Debug_internalColor(ansi, '<internals>'); | |
} | |
if (tag[0] === '#') | |
{ | |
var output = []; | |
for (var k in value) | |
{ | |
if (k === '$') continue; | |
output.push(_Debug_toAnsiString(ansi, value[k])); | |
} | |
return '(' + output.join(',') + ')'; | |
} | |
if (tag === 'Set_elm_builtin') | |
{ | |
return _Debug_ctorColor(ansi, 'Set') | |
+ _Debug_fadeColor(ansi, '.fromList') + ' ' | |
+ _Debug_toAnsiString(ansi, $elm$core$Set$toList(value)); | |
} | |
if (tag === 'RBNode_elm_builtin' || tag === 'RBEmpty_elm_builtin') | |
{ | |
return _Debug_ctorColor(ansi, 'Dict') | |
+ _Debug_fadeColor(ansi, '.fromList') + ' ' | |
+ _Debug_toAnsiString(ansi, $elm$core$Dict$toList(value)); | |
} | |
if (tag === 'Array_elm_builtin') | |
{ | |
return _Debug_ctorColor(ansi, 'Array') | |
+ _Debug_fadeColor(ansi, '.fromList') + ' ' | |
+ _Debug_toAnsiString(ansi, $elm$core$Array$toList(value)); | |
} | |
if (tag === '::' || tag === '[]') | |
{ | |
var output = '['; | |
value.b && (output += _Debug_toAnsiString(ansi, value.a), value = value.b) | |
for (; value.b; value = value.b) // WHILE_CONS | |
{ | |
output += ',' + _Debug_toAnsiString(ansi, value.a); | |
} | |
return output + ']'; | |
} | |
var output = ''; | |
for (var i in value) | |
{ | |
if (i === '$') continue; | |
var str = _Debug_toAnsiString(ansi, value[i]); | |
var c0 = str[0]; | |
var parenless = c0 === '{' || c0 === '(' || c0 === '[' || c0 === '<' || c0 === '"' || str.indexOf(' ') < 0; | |
output += ' ' + (parenless ? str : '(' + str + ')'); | |
} | |
return _Debug_ctorColor(ansi, tag) + output; | |
} | |
if (typeof DataView === 'function' && value instanceof DataView) | |
{ | |
return _Debug_stringColor(ansi, '<' + value.byteLength + ' bytes>'); | |
} | |
if (typeof File !== 'undefined' && value instanceof File) | |
{ | |
return _Debug_internalColor(ansi, '<' + value.name + '>'); | |
} | |
if (typeof value === 'object') | |
{ | |
var output = []; | |
for (var key in value) | |
{ | |
var field = key[0] === '_' ? key.slice(1) : key; | |
output.push(_Debug_fadeColor(ansi, field) + ' = ' + _Debug_toAnsiString(ansi, value[key])); | |
} | |
if (output.length === 0) | |
{ | |
return '{}'; | |
} | |
return '{ ' + output.join(', ') + ' }'; | |
} | |
return _Debug_internalColor(ansi, '<internals>'); | |
} | |
function _Debug_addSlashes(str, isChar) | |
{ | |
var s = str | |
.replace(/\\/g, '\\\\') | |
.replace(/\n/g, '\\n') | |
.replace(/\t/g, '\\t') | |
.replace(/\r/g, '\\r') | |
.replace(/\v/g, '\\v') | |
.replace(/\0/g, '\\0'); | |
if (isChar) | |
{ | |
return s.replace(/\'/g, '\\\''); | |
} | |
else | |
{ | |
return s.replace(/\"/g, '\\"'); | |
} | |
} | |
function _Debug_ctorColor(ansi, string) | |
{ | |
return ansi ? '\x1b[96m' + string + '\x1b[0m' : string; | |
} | |
function _Debug_numberColor(ansi, string) | |
{ | |
return ansi ? '\x1b[95m' + string + '\x1b[0m' : string; | |
} | |
function _Debug_stringColor(ansi, string) | |
{ | |
return ansi ? '\x1b[93m' + string + '\x1b[0m' : string; | |
} | |
function _Debug_charColor(ansi, string) | |
{ | |
return ansi ? '\x1b[92m' + string + '\x1b[0m' : string; | |
} | |
function _Debug_fadeColor(ansi, string) | |
{ | |
return ansi ? '\x1b[37m' + string + '\x1b[0m' : string; | |
} | |
function _Debug_internalColor(ansi, string) | |
{ | |
return ansi ? '\x1b[36m' + string + '\x1b[0m' : string; | |
} | |
function _Debug_toHexDigit(n) | |
{ | |
return String.fromCharCode(n < 10 ? 48 + n : 55 + n); | |
} | |
// CRASH | |
function _Debug_crash_UNUSED(identifier) | |
{ | |
throw new Error('https://github.com/elm/core/blob/1.0.0/hints/' + identifier + '.md'); | |
} | |
function _Debug_crash(identifier, fact1, fact2, fact3, fact4) | |
{ | |
switch(identifier) | |
{ | |
case 0: | |
throw new Error('What node should I take over? In JavaScript I need something like:\n\n Elm.Main.init({\n node: document.getElementById("elm-node")\n })\n\nYou need to do this with any Browser.sandbox or Browser.element program.'); | |
case 1: | |
throw new Error('Browser.application programs cannot handle URLs like this:\n\n ' + document.location.href + '\n\nWhat is the root? The root of your file system? Try looking at this program with `elm reactor` or some other server.'); | |
case 2: | |
var jsonErrorString = fact1; | |
throw new Error('Problem with the flags given to your Elm program on initialization.\n\n' + jsonErrorString); | |
case 3: | |
var portName = fact1; | |
throw new Error('There can only be one port named `' + portName + '`, but your program has multiple.'); | |
case 4: | |
var portName = fact1; | |
var problem = fact2; | |
throw new Error('Trying to send an unexpected type of value through port `' + portName + '`:\n' + problem); | |
case 5: | |
throw new Error('Trying to use `(==)` on functions.\nThere is no way to know if functions are "the same" in the Elm sense.\nRead more about this at https://package.elm-lang.org/packages/elm/core/latest/Basics#== which describes why it is this way and what the better version will look like.'); | |
case 6: | |
var moduleName = fact1; | |
throw new Error('Your page is loading multiple Elm scripts with a module named ' + moduleName + '. Maybe a duplicate script is getting loaded accidentally? If not, rename one of them so I know which is which!'); | |
case 8: | |
var moduleName = fact1; | |
var region = fact2; | |
var message = fact3; | |
throw new Error('TODO in module `' + moduleName + '` ' + _Debug_regionToString(region) + '\n\n' + message); | |
case 9: | |
var moduleName = fact1; | |
var region = fact2; | |
var value = fact3; | |
var message = fact4; | |
throw new Error( | |
'TODO in module `' + moduleName + '` from the `case` expression ' | |
+ _Debug_regionToString(region) + '\n\nIt received the following value:\n\n ' | |
+ _Debug_toString(value).replace('\n', '\n ') | |
+ '\n\nBut the branch that handles it says:\n\n ' + message.replace('\n', '\n ') | |
); | |
case 10: | |
throw new Error('Bug in https://github.com/elm/virtual-dom/issues'); | |
case 11: | |
throw new Error('Cannot perform mod 0. Division by zero error.'); | |
} | |
} | |
function _Debug_regionToString(region) | |
{ | |
if (region.start.line === region.end.line) | |
{ | |
return 'on line ' + region.start.line; | |
} | |
return 'on lines ' + region.start.line + ' through ' + region.end.line; | |
} | |
// EQUALITY | |
function _Utils_eq(x, y) | |
{ | |
for ( | |
var pair, stack = [], isEqual = _Utils_eqHelp(x, y, 0, stack); | |
isEqual && (pair = stack.pop()); | |
isEqual = _Utils_eqHelp(pair.a, pair.b, 0, stack) | |
) | |
{} | |
return isEqual; | |
} | |
function _Utils_eqHelp(x, y, depth, stack) | |
{ | |
if (x === y) | |
{ | |
return true; | |
} | |
if (typeof x !== 'object' || x === null || y === null) | |
{ | |
typeof x === 'function' && _Debug_crash(5); | |
return false; | |
} | |
if (depth > 100) | |
{ | |
stack.push(_Utils_Tuple2(x,y)); | |
return true; | |
} | |
/**/ | |
if (x.$ === 'Set_elm_builtin') | |
{ | |
x = $elm$core$Set$toList(x); | |
y = $elm$core$Set$toList(y); | |
} | |
if (x.$ === 'RBNode_elm_builtin' || x.$ === 'RBEmpty_elm_builtin') | |
{ | |
x = $elm$core$Dict$toList(x); | |
y = $elm$core$Dict$toList(y); | |
} | |
//*/ | |
/**_UNUSED/ | |
if (x.$ < 0) | |
{ | |
x = $elm$core$Dict$toList(x); | |
y = $elm$core$Dict$toList(y); | |
} | |
//*/ | |
for (var key in x) | |
{ | |
if (!_Utils_eqHelp(x[key], y[key], depth + 1, stack)) | |
{ | |
return false; | |
} | |
} | |
return true; | |
} | |
var _Utils_equal = F2(_Utils_eq); | |
var _Utils_notEqual = F2(function(a, b) { return !_Utils_eq(a,b); }); | |
// COMPARISONS | |
// Code in Generate/JavaScript.hs, Basics.js, and List.js depends on | |
// the particular integer values assigned to LT, EQ, and GT. | |
function _Utils_cmp(x, y, ord) | |
{ | |
if (typeof x !== 'object') | |
{ | |
return x === y ? /*EQ*/ 0 : x < y ? /*LT*/ -1 : /*GT*/ 1; | |
} | |
/**/ | |
if (x instanceof String) | |
{ | |
var a = x.valueOf(); | |
var b = y.valueOf(); | |
return a === b ? 0 : a < b ? -1 : 1; | |
} | |
//*/ | |
/**_UNUSED/ | |
if (typeof x.$ === 'undefined') | |
//*/ | |
/**/ | |
if (x.$[0] === '#') | |
//*/ | |
{ | |
return (ord = _Utils_cmp(x.a, y.a)) | |
? ord | |
: (ord = _Utils_cmp(x.b, y.b)) | |
? ord | |
: _Utils_cmp(x.c, y.c); | |
} | |
// traverse conses until end of a list or a mismatch | |
for (; x.b && y.b && !(ord = _Utils_cmp(x.a, y.a)); x = x.b, y = y.b) {} // WHILE_CONSES | |
return ord || (x.b ? /*GT*/ 1 : y.b ? /*LT*/ -1 : /*EQ*/ 0); | |
} | |
var _Utils_lt = F2(function(a, b) { return _Utils_cmp(a, b) < 0; }); | |
var _Utils_le = F2(function(a, b) { return _Utils_cmp(a, b) < 1; }); | |
var _Utils_gt = F2(function(a, b) { return _Utils_cmp(a, b) > 0; }); | |
var _Utils_ge = F2(function(a, b) { return _Utils_cmp(a, b) >= 0; }); | |
var _Utils_compare = F2(function(x, y) | |
{ | |
var n = _Utils_cmp(x, y); | |
return n < 0 ? $elm$core$Basics$LT : n ? $elm$core$Basics$GT : $elm$core$Basics$EQ; | |
}); | |
// COMMON VALUES | |
var _Utils_Tuple0_UNUSED = 0; | |
var _Utils_Tuple0 = { $: '#0' }; | |
function _Utils_Tuple2_UNUSED(a, b) { return { a: a, b: b }; } | |
function _Utils_Tuple2(a, b) { return { $: '#2', a: a, b: b }; } | |
function _Utils_Tuple3_UNUSED(a, b, c) { return { a: a, b: b, c: c }; } | |
function _Utils_Tuple3(a, b, c) { return { $: '#3', a: a, b: b, c: c }; } | |
function _Utils_chr_UNUSED(c) { return c; } | |
function _Utils_chr(c) { return new String(c); } | |
// RECORDS | |
function _Utils_update(oldRecord, updatedFields) | |
{ | |
var newRecord = {}; | |
for (var key in oldRecord) | |
{ | |
newRecord[key] = oldRecord[key]; | |
} | |
for (var key in updatedFields) | |
{ | |
newRecord[key] = updatedFields[key]; | |
} | |
return newRecord; | |
} | |
// APPEND | |
var _Utils_append = F2(_Utils_ap); | |
function _Utils_ap(xs, ys) | |
{ | |
// append Strings | |
if (typeof xs === 'string') | |
{ | |
return xs + ys; | |
} | |
// append Lists | |
if (!xs.b) | |
{ | |
return ys; | |
} | |
var root = _List_Cons(xs.a, ys); | |
xs = xs.b | |
for (var curr = root; xs.b; xs = xs.b) // WHILE_CONS | |
{ | |
curr = curr.b = _List_Cons(xs.a, ys); | |
} | |
return root; | |
} | |
// MATH | |
var _Basics_add = F2(function(a, b) { return a + b; }); | |
var _Basics_sub = F2(function(a, b) { return a - b; }); | |
var _Basics_mul = F2(function(a, b) { return a * b; }); | |
var _Basics_fdiv = F2(function(a, b) { return a / b; }); | |
var _Basics_idiv = F2(function(a, b) { return (a / b) | 0; }); | |
var _Basics_pow = F2(Math.pow); | |
var _Basics_remainderBy = F2(function(b, a) { return a % b; }); | |
// https://www.microsoft.com/en-us/research/wp-content/uploads/2016/02/divmodnote-letter.pdf | |
var _Basics_modBy = F2(function(modulus, x) | |
{ | |
var answer = x % modulus; | |
return modulus === 0 | |
? _Debug_crash(11) | |
: | |
((answer > 0 && modulus < 0) || (answer < 0 && modulus > 0)) | |
? answer + modulus | |
: answer; | |
}); | |
// TRIGONOMETRY | |
var _Basics_pi = Math.PI; | |
var _Basics_e = Math.E; | |
var _Basics_cos = Math.cos; | |
var _Basics_sin = Math.sin; | |
var _Basics_tan = Math.tan; | |
var _Basics_acos = Math.acos; | |
var _Basics_asin = Math.asin; | |
var _Basics_atan = Math.atan; | |
var _Basics_atan2 = F2(Math.atan2); | |
// MORE MATH | |
function _Basics_toFloat(x) { return x; } | |
function _Basics_truncate(n) { return n | 0; } | |
function _Basics_isInfinite(n) { return n === Infinity || n === -Infinity; } | |
var _Basics_ceiling = Math.ceil; | |
var _Basics_floor = Math.floor; | |
var _Basics_round = Math.round; | |
var _Basics_sqrt = Math.sqrt; | |
var _Basics_log = Math.log; | |
var _Basics_isNaN = isNaN; | |
// BOOLEANS | |
function _Basics_not(bool) { return !bool; } | |
var _Basics_and = F2(function(a, b) { return a && b; }); | |
var _Basics_or = F2(function(a, b) { return a || b; }); | |
var _Basics_xor = F2(function(a, b) { return a !== b; }); | |
var _String_cons = F2(function(chr, str) | |
{ | |
return chr + str; | |
}); | |
function _String_uncons(string) | |
{ | |
var word = string.charCodeAt(0); | |
return !isNaN(word) | |
? $elm$core$Maybe$Just( | |
0xD800 <= word && word <= 0xDBFF | |
? _Utils_Tuple2(_Utils_chr(string[0] + string[1]), string.slice(2)) | |
: _Utils_Tuple2(_Utils_chr(string[0]), string.slice(1)) | |
) | |
: $elm$core$Maybe$Nothing; | |
} | |
var _String_append = F2(function(a, b) | |
{ | |
return a + b; | |
}); | |
function _String_length(str) | |
{ | |
return str.length; | |
} | |
var _String_map = F2(function(func, string) | |
{ | |
var len = string.length; | |
var array = new Array(len); | |
var i = 0; | |
while (i < len) | |
{ | |
var word = string.charCodeAt(i); | |
if (0xD800 <= word && word <= 0xDBFF) | |
{ | |
array[i] = func(_Utils_chr(string[i] + string[i+1])); | |
i += 2; | |
continue; | |
} | |
array[i] = func(_Utils_chr(string[i])); | |
i++; | |
} | |
return array.join(''); | |
}); | |
var _String_filter = F2(function(isGood, str) | |
{ | |
var arr = []; | |
var len = str.length; | |
var i = 0; | |
while (i < len) | |
{ | |
var char = str[i]; | |
var word = str.charCodeAt(i); | |
i++; | |
if (0xD800 <= word && word <= 0xDBFF) | |
{ | |
char += str[i]; | |
i++; | |
} | |
if (isGood(_Utils_chr(char))) | |
{ | |
arr.push(char); | |
} | |
} | |
return arr.join(''); | |
}); | |
function _String_reverse(str) | |
{ | |
var len = str.length; | |
var arr = new Array(len); | |
var i = 0; | |
while (i < len) | |
{ | |
var word = str.charCodeAt(i); | |
if (0xD800 <= word && word <= 0xDBFF) | |
{ | |
arr[len - i] = str[i + 1]; | |
i++; | |
arr[len - i] = str[i - 1]; | |
i++; | |
} | |
else | |
{ | |
arr[len - i] = str[i]; | |
i++; | |
} | |
} | |
return arr.join(''); | |
} | |
var _String_foldl = F3(function(func, state, string) | |
{ | |
var len = string.length; | |
var i = 0; | |
while (i < len) | |
{ | |
var char = string[i]; | |
var word = string.charCodeAt(i); | |
i++; | |
if (0xD800 <= word && word <= 0xDBFF) | |
{ | |
char += string[i]; | |
i++; | |
} | |
state = A2(func, _Utils_chr(char), state); | |
} | |
return state; | |
}); | |
var _String_foldr = F3(function(func, state, string) | |
{ | |
var i = string.length; | |
while (i--) | |
{ | |
var char = string[i]; | |
var word = string.charCodeAt(i); | |
if (0xDC00 <= word && word <= 0xDFFF) | |
{ | |
i--; | |
char = string[i] + char; | |
} | |
state = A2(func, _Utils_chr(char), state); | |
} | |
return state; | |
}); | |
var _String_split = F2(function(sep, str) | |
{ | |
return str.split(sep); | |
}); | |
var _String_join = F2(function(sep, strs) | |
{ | |
return strs.join(sep); | |
}); | |
var _String_slice = F3(function(start, end, str) { | |
return str.slice(start, end); | |
}); | |
function _String_trim(str) | |
{ | |
return str.trim(); | |
} | |
function _String_trimLeft(str) | |
{ | |
return str.replace(/^\s+/, ''); | |
} | |
function _String_trimRight(str) | |
{ | |
return str.replace(/\s+$/, ''); | |
} | |
function _String_words(str) | |
{ | |
return _List_fromArray(str.trim().split(/\s+/g)); | |
} | |
function _String_lines(str) | |
{ | |
return _List_fromArray(str.split(/\r\n|\r|\n/g)); | |
} | |
function _String_toUpper(str) | |
{ | |
return str.toUpperCase(); | |
} | |
function _String_toLower(str) | |
{ | |
return str.toLowerCase(); | |
} | |
var _String_any = F2(function(isGood, string) | |
{ | |
var i = string.length; | |
while (i--) | |
{ | |
var char = string[i]; | |
var word = string.charCodeAt(i); | |
if (0xDC00 <= word && word <= 0xDFFF) | |
{ | |
i--; | |
char = string[i] + char; | |
} | |
if (isGood(_Utils_chr(char))) | |
{ | |
return true; | |
} | |
} | |
return false; | |
}); | |
var _String_all = F2(function(isGood, string) | |
{ | |
var i = string.length; | |
while (i--) | |
{ | |
var char = string[i]; | |
var word = string.charCodeAt(i); | |
if (0xDC00 <= word && word <= 0xDFFF) | |
{ | |
i--; | |
char = string[i] + char; | |
} | |
if (!isGood(_Utils_chr(char))) | |
{ | |
return false; | |
} | |
} | |
return true; | |
}); | |
var _String_contains = F2(function(sub, str) | |
{ | |
return str.indexOf(sub) > -1; | |
}); | |
var _String_startsWith = F2(function(sub, str) | |
{ | |
return str.indexOf(sub) === 0; | |
}); | |
var _String_endsWith = F2(function(sub, str) | |
{ | |
return str.length >= sub.length && | |
str.lastIndexOf(sub) === str.length - sub.length; | |
}); | |
var _String_indexes = F2(function(sub, str) | |
{ | |
var subLen = sub.length; | |
if (subLen < 1) | |
{ | |
return _List_Nil; | |
} | |
var i = 0; | |
var is = []; | |
while ((i = str.indexOf(sub, i)) > -1) | |
{ | |
is.push(i); | |
i = i + subLen; | |
} | |
return _List_fromArray(is); | |
}); | |
// TO STRING | |
function _String_fromNumber(number) | |
{ | |
return number + ''; | |
} | |
// INT CONVERSIONS | |
function _String_toInt(str) | |
{ | |
var total = 0; | |
var code0 = str.charCodeAt(0); | |
var start = code0 == 0x2B /* + */ || code0 == 0x2D /* - */ ? 1 : 0; | |
for (var i = start; i < str.length; ++i) | |
{ | |
var code = str.charCodeAt(i); | |
if (code < 0x30 || 0x39 < code) | |
{ | |
return $elm$core$Maybe$Nothing; | |
} | |
total = 10 * total + code - 0x30; | |
} | |
return i == start | |
? $elm$core$Maybe$Nothing | |
: $elm$core$Maybe$Just(code0 == 0x2D ? -total : total); | |
} | |
// FLOAT CONVERSIONS | |
function _String_toFloat(s) | |
{ | |
// check if it is a hex, octal, or binary number | |
if (s.length === 0 || /[\sxbo]/.test(s)) | |
{ | |
return $elm$core$Maybe$Nothing; | |
} | |
var n = +s; | |
// faster isNaN check | |
return n === n ? $elm$core$Maybe$Just(n) : $elm$core$Maybe$Nothing; | |
} | |
function _String_fromList(chars) | |
{ | |
return _List_toArray(chars).join(''); | |
} | |
function _Char_toCode(char) | |
{ | |
var code = char.charCodeAt(0); | |
if (0xD800 <= code && code <= 0xDBFF) | |
{ | |
return (code - 0xD800) * 0x400 + char.charCodeAt(1) - 0xDC00 + 0x10000 | |
} | |
return code; | |
} | |
function _Char_fromCode(code) | |
{ | |
return _Utils_chr( | |
(code < 0 || 0x10FFFF < code) | |
? '\uFFFD' | |
: | |
(code <= 0xFFFF) | |
? String.fromCharCode(code) | |
: | |
(code -= 0x10000, | |
String.fromCharCode(Math.floor(code / 0x400) + 0xD800, code % 0x400 + 0xDC00) | |
) | |
); | |
} | |
function _Char_toUpper(char) | |
{ | |
return _Utils_chr(char.toUpperCase()); | |
} | |
function _Char_toLower(char) | |
{ | |
return _Utils_chr(char.toLowerCase()); | |
} | |
function _Char_toLocaleUpper(char) | |
{ | |
return _Utils_chr(char.toLocaleUpperCase()); | |
} | |
function _Char_toLocaleLower(char) | |
{ | |
return _Utils_chr(char.toLocaleLowerCase()); | |
} | |
/**/ | |
function _Json_errorToString(error) | |
{ | |
return $elm$json$Json$Decode$errorToString(error); | |
} | |
//*/ | |
// CORE DECODERS | |
function _Json_succeed(msg) | |
{ | |
return { | |
$: 0, | |
a: msg | |
}; | |
} | |
function _Json_fail(msg) | |
{ | |
return { | |
$: 1, | |
a: msg | |
}; | |
} | |
function _Json_decodePrim(decoder) | |
{ | |
return { $: 2, b: decoder }; | |
} | |
var _Json_decodeInt = _Json_decodePrim(function(value) { | |
return (typeof value !== 'number') | |
? _Json_expecting('an INT', value) | |
: | |
(-2147483647 < value && value < 2147483647 && (value | 0) === value) | |
? $elm$core$Result$Ok(value) | |
: | |
(isFinite(value) && !(value % 1)) | |
? $elm$core$Result$Ok(value) | |
: _Json_expecting('an INT', value); | |
}); | |
var _Json_decodeBool = _Json_decodePrim(function(value) { | |
return (typeof value === 'boolean') | |
? $elm$core$Result$Ok(value) | |
: _Json_expecting('a BOOL', value); | |
}); | |
var _Json_decodeFloat = _Json_decodePrim(function(value) { | |
return (typeof value === 'number') | |
? $elm$core$Result$Ok(value) | |
: _Json_expecting('a FLOAT', value); | |
}); | |
var _Json_decodeValue = _Json_decodePrim(function(value) { | |
return $elm$core$Result$Ok(_Json_wrap(value)); | |
}); | |
var _Json_decodeString = _Json_decodePrim(function(value) { | |
return (typeof value === 'string') | |
? $elm$core$Result$Ok(value) | |
: (value instanceof String) | |
? $elm$core$Result$Ok(value + '') | |
: _Json_expecting('a STRING', value); | |
}); | |
function _Json_decodeList(decoder) { return { $: 3, b: decoder }; } | |
function _Json_decodeArray(decoder) { return { $: 4, b: decoder }; } | |
function _Json_decodeNull(value) { return { $: 5, c: value }; } | |
var _Json_decodeField = F2(function(field, decoder) | |
{ | |
return { | |
$: 6, | |
d: field, | |
b: decoder | |
}; | |
}); | |
var _Json_decodeIndex = F2(function(index, decoder) | |
{ | |
return { | |
$: 7, | |
e: index, | |
b: decoder | |
}; | |
}); | |
function _Json_decodeKeyValuePairs(decoder) | |
{ | |
return { | |
$: 8, | |
b: decoder | |
}; | |
} | |
function _Json_mapMany(f, decoders) | |
{ | |
return { | |
$: 9, | |
f: f, | |
g: decoders | |
}; | |
} | |
var _Json_andThen = F2(function(callback, decoder) | |
{ | |
return { | |
$: 10, | |
b: decoder, | |
h: callback | |
}; | |
}); | |
function _Json_oneOf(decoders) | |
{ | |
return { | |
$: 11, | |
g: decoders | |
}; | |
} | |
// DECODING OBJECTS | |
var _Json_map1 = F2(function(f, d1) | |
{ | |
return _Json_mapMany(f, [d1]); | |
}); | |
var _Json_map2 = F3(function(f, d1, d2) | |
{ | |
return _Json_mapMany(f, [d1, d2]); | |
}); | |
var _Json_map3 = F4(function(f, d1, d2, d3) | |
{ | |
return _Json_mapMany(f, [d1, d2, d3]); | |
}); | |
var _Json_map4 = F5(function(f, d1, d2, d3, d4) | |
{ | |
return _Json_mapMany(f, [d1, d2, d3, d4]); | |
}); | |
var _Json_map5 = F6(function(f, d1, d2, d3, d4, d5) | |
{ | |
return _Json_mapMany(f, [d1, d2, d3, d4, d5]); | |
}); | |
var _Json_map6 = F7(function(f, d1, d2, d3, d4, d5, d6) | |
{ | |
return _Json_mapMany(f, [d1, d2, d3, d4, d5, d6]); | |
}); | |
var _Json_map7 = F8(function(f, d1, d2, d3, d4, d5, d6, d7) | |
{ | |
return _Json_mapMany(f, [d1, d2, d3, d4, d5, d6, d7]); | |
}); | |
var _Json_map8 = F9(function(f, d1, d2, d3, d4, d5, d6, d7, d8) | |
{ | |
return _Json_mapMany(f, [d1, d2, d3, d4, d5, d6, d7, d8]); | |
}); | |
// DECODE | |
var _Json_runOnString = F2(function(decoder, string) | |
{ | |
try | |
{ | |
var value = JSON.parse(string); | |
return _Json_runHelp(decoder, value); | |
} | |
catch (e) | |
{ | |
return $elm$core$Result$Err(A2($elm$json$Json$Decode$Failure, 'This is not valid JSON! ' + e.message, _Json_wrap(string))); | |
} | |
}); | |
var _Json_run = F2(function(decoder, value) | |
{ | |
return _Json_runHelp(decoder, _Json_unwrap(value)); | |
}); | |
function _Json_runHelp(decoder, value) | |
{ | |
switch (decoder.$) | |
{ | |
case 2: | |
return decoder.b(value); | |
case 5: | |
return (value === null) | |
? $elm$core$Result$Ok(decoder.c) | |
: _Json_expecting('null', value); | |
case 3: | |
if (!_Json_isArray(value)) | |
{ | |
return _Json_expecting('a LIST', value); | |
} | |
return _Json_runArrayDecoder(decoder.b, value, _List_fromArray); | |
case 4: | |
if (!_Json_isArray(value)) | |
{ | |
return _Json_expecting('an ARRAY', value); | |
} | |
return _Json_runArrayDecoder(decoder.b, value, _Json_toElmArray); | |
case 6: | |
var field = decoder.d; | |
if (typeof value !== 'object' || value === null || !(field in value)) | |
{ | |
return _Json_expecting('an OBJECT with a field named `' + field + '`', value); | |
} | |
var result = _Json_runHelp(decoder.b, value[field]); | |
return ($elm$core$Result$isOk(result)) ? result : $elm$core$Result$Err(A2($elm$json$Json$Decode$Field, field, result.a)); | |
case 7: | |
var index = decoder.e; | |
if (!_Json_isArray(value)) | |
{ | |
return _Json_expecting('an ARRAY', value); | |
} | |
if (index >= value.length) | |
{ | |
return _Json_expecting('a LONGER array. Need index ' + index + ' but only see ' + value.length + ' entries', value); | |
} | |
var result = _Json_runHelp(decoder.b, value[index]); | |
return ($elm$core$Result$isOk(result)) ? result : $elm$core$Result$Err(A2($elm$json$Json$Decode$Index, index, result.a)); | |
case 8: | |
if (typeof value !== 'object' || value === null || _Json_isArray(value)) | |
{ | |
return _Json_expecting('an OBJECT', value); | |
} | |
var keyValuePairs = _List_Nil; | |
// TODO test perf of Object.keys and switch when support is good enough | |
for (var key in value) | |
{ | |
if (value.hasOwnProperty(key)) | |
{ | |
var result = _Json_runHelp(decoder.b, value[key]); | |
if (!$elm$core$Result$isOk(result)) | |
{ | |
return $elm$core$Result$Err(A2($elm$json$Json$Decode$Field, key, result.a)); | |
} | |
keyValuePairs = _List_Cons(_Utils_Tuple2(key, result.a), keyValuePairs); | |
} | |
} | |
return $elm$core$Result$Ok($elm$core$List$reverse(keyValuePairs)); | |
case 9: | |
var answer = decoder.f; | |
var decoders = decoder.g; | |
for (var i = 0; i < decoders.length; i++) | |
{ | |
var result = _Json_runHelp(decoders[i], value); | |
if (!$elm$core$Result$isOk(result)) | |
{ | |
return result; | |
} | |
answer = answer(result.a); | |
} | |
return $elm$core$Result$Ok(answer); | |
case 10: | |
var result = _Json_runHelp(decoder.b, value); | |
return (!$elm$core$Result$isOk(result)) | |
? result | |
: _Json_runHelp(decoder.h(result.a), value); | |
case 11: | |
var errors = _List_Nil; | |
for (var temp = decoder.g; temp.b; temp = temp.b) // WHILE_CONS | |
{ | |
var result = _Json_runHelp(temp.a, value); | |
if ($elm$core$Result$isOk(result)) | |
{ | |
return result; | |
} | |
errors = _List_Cons(result.a, errors); | |
} | |
return $elm$core$Result$Err($elm$json$Json$Decode$OneOf($elm$core$List$reverse(errors))); | |
case 1: | |
return $elm$core$Result$Err(A2($elm$json$Json$Decode$Failure, decoder.a, _Json_wrap(value))); | |
case 0: | |
return $elm$core$Result$Ok(decoder.a); | |
} | |
} | |
function _Json_runArrayDecoder(decoder, value, toElmValue) | |
{ | |
var len = value.length; | |
var array = new Array(len); | |
for (var i = 0; i < len; i++) | |
{ | |
var result = _Json_runHelp(decoder, value[i]); | |
if (!$elm$core$Result$isOk(result)) | |
{ | |
return $elm$core$Result$Err(A2($elm$json$Json$Decode$Index, i, result.a)); | |
} | |
array[i] = result.a; | |
} | |
return $elm$core$Result$Ok(toElmValue(array)); | |
} | |
function _Json_isArray(value) | |
{ | |
return Array.isArray(value) || (typeof FileList !== 'undefined' && value instanceof FileList); | |
} | |
function _Json_toElmArray(array) | |
{ | |
return A2($elm$core$Array$initialize, array.length, function(i) { return array[i]; }); | |
} | |
function _Json_expecting(type, value) | |
{ | |
return $elm$core$Result$Err(A2($elm$json$Json$Decode$Failure, 'Expecting ' + type, _Json_wrap(value))); | |
} | |
// EQUALITY | |
function _Json_equality(x, y) | |
{ | |
if (x === y) | |
{ | |
return true; | |
} | |
if (x.$ !== y.$) | |
{ | |
return false; | |
} | |
switch (x.$) | |
{ | |
case 0: | |
case 1: | |
return x.a === y.a; | |
case 2: | |
return x.b === y.b; | |
case 5: | |
return x.c === y.c; | |
case 3: | |
case 4: | |
case 8: | |
return _Json_equality(x.b, y.b); | |
case 6: | |
return x.d === y.d && _Json_equality(x.b, y.b); | |
case 7: | |
return x.e === y.e && _Json_equality(x.b, y.b); | |
case 9: | |
return x.f === y.f && _Json_listEquality(x.g, y.g); | |
case 10: | |
return x.h === y.h && _Json_equality(x.b, y.b); | |
case 11: | |
return _Json_listEquality(x.g, y.g); | |
} | |
} | |
function _Json_listEquality(aDecoders, bDecoders) | |
{ | |
var len = aDecoders.length; | |
if (len !== bDecoders.length) | |
{ | |
return false; | |
} | |
for (var i = 0; i < len; i++) | |
{ | |
if (!_Json_equality(aDecoders[i], bDecoders[i])) | |
{ | |
return false; | |
} | |
} | |
return true; | |
} | |
// ENCODE | |
var _Json_encode = F2(function(indentLevel, value) | |
{ | |
return JSON.stringify(_Json_unwrap(value), null, indentLevel) + ''; | |
}); | |
function _Json_wrap(value) { return { $: 0, a: value }; } | |
function _Json_unwrap(value) { return value.a; } | |
function _Json_wrap_UNUSED(value) { return value; } | |
function _Json_unwrap_UNUSED(value) { return value; } | |
function _Json_emptyArray() { return []; } | |
function _Json_emptyObject() { return {}; } | |
var _Json_addField = F3(function(key, value, object) | |
{ | |
object[key] = _Json_unwrap(value); | |
return object; | |
}); | |
function _Json_addEntry(func) | |
{ | |
return F2(function(entry, array) | |
{ | |
array.push(_Json_unwrap(func(entry))); | |
return array; | |
}); | |
} | |
var _Json_encodeNull = _Json_wrap(null); | |
// TASKS | |
function _Scheduler_succeed(value) | |
{ | |
return { | |
$: 0, | |
a: value | |
}; | |
} | |
function _Scheduler_fail(error) | |
{ | |
return { | |
$: 1, | |
a: error | |
}; | |
} | |
function _Scheduler_binding(callback) | |
{ | |
return { | |
$: 2, | |
b: callback, | |
c: null | |
}; | |
} | |
var _Scheduler_andThen = F2(function(callback, task) | |
{ | |
return { | |
$: 3, | |
b: callback, | |
d: task | |
}; | |
}); | |
var _Scheduler_onError = F2(function(callback, task) | |
{ | |
return { | |
$: 4, | |
b: callback, | |
d: task | |
}; | |
}); | |
function _Scheduler_receive(callback) | |
{ | |
return { | |
$: 5, | |
b: callback | |
}; | |
} | |
// PROCESSES | |
var _Scheduler_guid = 0; | |
function _Scheduler_rawSpawn(task) | |
{ | |
var proc = { | |
$: 0, | |
e: _Scheduler_guid++, | |
f: task, | |
g: null, | |
h: [] | |
}; | |
_Scheduler_enqueue(proc); | |
return proc; | |
} | |
function _Scheduler_spawn(task) | |
{ | |
return _Scheduler_binding(function(callback) { | |
callback(_Scheduler_succeed(_Scheduler_rawSpawn(task))); | |
}); | |
} | |
function _Scheduler_rawSend(proc, msg) | |
{ | |
proc.h.push(msg); | |
_Scheduler_enqueue(proc); | |
} | |
var _Scheduler_send = F2(function(proc, msg) | |
{ | |
return _Scheduler_binding(function(callback) { | |
_Scheduler_rawSend(proc, msg); | |
callback(_Scheduler_succeed(_Utils_Tuple0)); | |
}); | |
}); | |
function _Scheduler_kill(proc) | |
{ | |
return _Scheduler_binding(function(callback) { | |
var task = proc.f; | |
if (task.$ === 2 && task.c) | |
{ | |
task.c(); | |
} | |
proc.f = null; | |
callback(_Scheduler_succeed(_Utils_Tuple0)); | |
}); | |
} | |
/* STEP PROCESSES | |
type alias Process = | |
{ $ : tag | |
, id : unique_id | |
, root : Task | |
, stack : null | { $: SUCCEED | FAIL, a: callback, b: stack } | |
, mailbox : [msg] | |
} | |
*/ | |
var _Scheduler_working = false; | |
var _Scheduler_queue = []; | |
function _Scheduler_enqueue(proc) | |
{ | |
_Scheduler_queue.push(proc); | |
if (_Scheduler_working) | |
{ | |
return; | |
} | |
_Scheduler_working = true; | |
while (proc = _Scheduler_queue.shift()) | |
{ | |
_Scheduler_step(proc); | |
} | |
_Scheduler_working = false; | |
} | |
function _Scheduler_step(proc) | |
{ | |
while (proc.f) | |
{ | |
var rootTag = proc.f.$; | |
if (rootTag === 0 || rootTag === 1) | |
{ | |
while (proc.g && proc.g.$ !== rootTag) | |
{ | |
proc.g = proc.g.i; | |
} | |
if (!proc.g) | |
{ | |
return; | |
} | |
proc.f = proc.g.b(proc.f.a); | |
proc.g = proc.g.i; | |
} | |
else if (rootTag === 2) | |
{ | |
proc.f.c = proc.f.b(function(newRoot) { | |
proc.f = newRoot; | |
_Scheduler_enqueue(proc); | |
}); | |
return; | |
} | |
else if (rootTag === 5) | |
{ | |