src/Model/Export.elm (284 lines of code) (raw):
module Model.Export exposing
( encode
, decode
, encodeToString
, decodeFromString
, encodeForPort
, decodeFromPort
, adaptModelDecodeErrors
)
import Array
import Tuple
import Time
import Browser.Navigation as Nav
import Math.Vector2 as Vec2
import Json.Decode as D exposing (bool, int, string, float, Decoder, Value)
import Json.Decode.Extra as D exposing (andMap)
import Json.Encode as E exposing (encode, Value, string, int, float, bool, list, object)
import Algorithm.Gaussian as Gaussian
import Model.Layer.Blend.Html as HtmlBlend
import Model.Layer.Blend.WebGL as WGLBlend
import Model.Util exposing (resultToDecoder, intPairDecoder)
import Gradient
import Model.Core as M
import Model.AppMode as Mode
import Model.Layer.Layer as Layer
import Model.Layer.Export as Layer
import Model.Layer.Context exposing (Context)
import Model.SizeRule as SizeRule
import Model.Error as M
import Model.Product as Product exposing (Product, encode, decode, encodeGradient, decodeGradient)
import Model.Range exposing (..)
import Model.Version exposing (Version)
import Model.Version as Version
import TronGui as GUI
type ModelDecodeError
= LayerDecodeErrors (List Layer.DecodeError)
| SizeRuleDecodeError String
-- | ModeDecodeError String
| ProductDecodeError String
encodeIntPair : ( Int, Int ) -> E.Value
encodeIntPair ( v1, v2 ) =
E.object
[ ( "v1", E.int v1 )
, ( "v2", E.int v2 )
]
-- encodePairAsArray : (a -> E.Value) -> ( a, a ) -> E.Value
-- encodePairAsArray f ( v1, v2 ) =
-- E.list f [ v1, v2 ]
encodeColor : { r: Float, g: Float, b: Float } -> E.Value
encodeColor { r, g, b } =
E.list
E.float
[ r
, g
, b
]
encode : M.Model -> E.Value
encode model =
E.object
[ ( "background", E.string model.background )
, ( "mode", E.string <| Mode.encode model.mode )
, ( "theta", E.float model.theta )
, ( "omega", E.float model.omega )
, ( "layers", E.list (Layer.encode <| M.getContext model) model.layers )
-- , ( "layers", E.list (List.filterMap
-- (\layer -> Maybe.map encodeLayer layer) model.layers) )
-- for b/w compatibility, we also encode size as numbers, but sizeRule is what should matter
-- when it is defined/known on import
, ( "size", encodeIntPair <| SizeRule.getRuleSizeOrZeroes model.size )
, ( "sizeRule", E.string <| SizeRule.encode model.size )
, ( "origin", encodeIntPair model.origin )
, ( "mouse", encodeIntPair model.mouse )
, ( "now", E.float model.now )
, ( "palette",
model.product
|> Product.getPalette
|> Product.encodePalette
|> E.list E.string
)
, ( "product", model.product |> Product.encode |> E.string )
, ( "version", model.version |> Maybe.map Version.encode |> Maybe.withDefault E.null )
]
encodeToString : M.Model -> String
encodeToString model = model |> encode |> E.encode 2
encodeForPort : M.Model -> M.PortModel
encodeForPort model =
{ background = model.background
, mode = Mode.encode model.mode
, now = model.now
, theta = model.theta
, omega = model.omega
, layers = List.map (Layer.encodeForPort <| M.getContext model) model.layers
, size = SizeRule.getRuleSize model.size |> Maybe.withDefault ( -1, -1 )
, sizeRule = SizeRule.encode model.size |> Just
, origin = model.origin
, mouse = model.mouse
, palette = model.product |> Product.getPalette |> Product.encodePalette
, product = model.product |> Product.encode
, version = model.version
|> Maybe.map (Version.encode >> E.encode 0)
|> Maybe.withDefault ""
}
decodeFromPort
: Nav.Key
-> M.ServerUrl
-> Context
-> M.PortModel
-> Result (List ModelDecodeError) M.Model
decodeFromPort navKey serverUrl ctx portModel =
let
couldBeDecodedLayers =
List.map (Layer.decodeFromPort ctx) portModel.layers
extractLayerDecodeErrors res =
case res of
Ok layer -> Nothing
Err errors -> Just <| LayerDecodeErrors errors
layerDecodeErrors =
couldBeDecodedLayers
|> List.filterMap extractLayerDecodeErrors
tryToDecodeSize maybeSizeRule =
case maybeSizeRule of
Just sizeRuleStr ->
SizeRule.decode sizeRuleStr
|> Result.mapError (List.singleton << SizeRuleDecodeError)
Nothing -> case portModel.size of
( w, h ) -> Ok <| SizeRule.Custom w h
applyDecoded decodedLayers decodedSize decodedProduct decodedVersion =
let
modeResult = Mode.decode portModel.mode
mode =
modeResult
|> Result.withDefault Mode.Production
initialModel = M.init navKey serverUrl mode
decodedModel =
{ initialModel
| background = portModel.background
, mode = mode
, now = portModel.now
, theta = portModel.theta
, omega = portModel.omega
, layers = decodedLayers
, size = decodedSize
, origin = portModel.origin
, mouse = portModel.mouse
, product = decodedProduct -- Debug.log "decoded product" decodedProduct
}
in
{ decodedModel
| gui = case mode of
Mode.TronUi _ -> Just <| GUI.gui decodedModel
_ -> Nothing
}
in
Result.map4 -- TODO: join in a list of all failures
applyDecoded
(if List.isEmpty layerDecodeErrors
then couldBeDecodedLayers
|> List.filterMap Result.toMaybe
|> Ok
else Err <| layerDecodeErrors)
(tryToDecodeSize portModel.sizeRule)
(portModel.product
|> Product.decode
|> Result.mapError (List.singleton << ProductDecodeError))
(portModel.version
|> D.decodeString Version.decode
|> Result.mapError (List.singleton << ProductDecodeError << D.errorToString))
decode : Nav.Key -> M.ServerUrl -> Context -> M.CreateGui -> D.Decoder M.Model
decode navKey serverUrl ctx createGui =
let
createModel
background
theta
omega
layers
maybeSize
maybeSizeRule
origin
mouse
now
product
maybeVersion =
let
initialModel =
M.init navKey serverUrl ctx.mode
sizeResult =
case maybeSizeRule of
Just sizeRuleStr -> SizeRule.decode sizeRuleStr
Nothing -> case maybeSize of
Just (w, h) -> Ok <| SizeRule.Custom w h
Nothing -> Err "Unknown Size"
in
sizeResult
|> resultToDecoder
|> D.map
(\size ->
{ initialModel
| background = background
, theta = theta
, omega = omega
, layers = layers
, size = size
, origin = origin
, mouse = mouse
, now = now
, product = product -- Debug.log "product decoded" product
--, palette = Product.getPalette product
}
)
|> D.map
(\newModel ->
{ newModel
| gui = case ctx.mode of
Mode.TronUi innerAppMode ->
Just <| createGui { newModel | mode = innerAppMode }
_ -> Nothing
}
)
in
-- case maybeSizeRule of
-- Just sizeRuleStr -> M.decodeSizeRule sizeRuleStr
-- Nothing -> case maybeSize of
-- Just (w, h) -> M.Custom w h
-- Nothing -> SizeRule.default
D.field "product" D.string
|> D.map (Product.decode >> resultToDecoder)
|> D.andThen identity
|> D.andThen
(\product ->
D.succeed createModel
|> D.andMap (D.field "background" D.string)
|> D.andMap (D.field "theta" D.float)
|> D.andMap (D.field "omega" D.float)
|> D.andMap (D.field "layers" (Layer.decode ctx |> D.list))
|> D.andMap (D.maybe (D.field "size" intPairDecoder))
|> D.andMap (D.maybe (D.field "sizeRule" D.string))
|> D.andMap (D.field "origin" intPairDecoder)
|> D.andMap (D.field "mouse" intPairDecoder)
|> D.andMap (D.field "now" D.float)
|> D.andMap (D.succeed product)
|> D.andMap (D.maybe (D.field "version" Version.decode))
|> D.andThen identity
)
decodeFromString
: Nav.Key
-> M.ServerUrl
-> Context
-> M.CreateGui
-> String
-> Result String M.Model
decodeFromString navKey serverUrl ctx createGui modelStr =
D.decodeString (decode navKey serverUrl ctx createGui) modelStr
|> Result.mapError D.errorToString
adaptModelDecodeErrors : List ModelDecodeError -> M.Errors
adaptModelDecodeErrors modelDecodeErrors =
let
layerDecodeErrorToString index layerDecodeError =
"(" ++ String.fromInt index ++ ") " ++
case layerDecodeError of
Layer.UnknownDefId whyKindDecodeFailed ->
"Failed to decode kind: " ++ whyKindDecodeFailed
Layer.UnknownBlend whyBlendDecodeFailed ->
"Failed to decode blend: " ++ whyBlendDecodeFailed
-- LayerCreationFailed whyLayerCreationFailed ->
-- "Failed to create layer: " ++ whyLayerCreationFailed
Layer.LayerModelDecodeFailed whyLayerModelDecodeFailed ->
"Failed to decode layer model: "
++ D.errorToString whyLayerModelDecodeFailed
modelDecodeErrorToString index modelDecodeError =
"(" ++ String.fromInt index ++ ") " ++
case modelDecodeError of
LayerDecodeErrors layerDecodeErrors ->
"Layers failed to decode: " ++
(layerDecodeErrors
|> List.indexedMap layerDecodeErrorToString
|> String.join "; ")
SizeRuleDecodeError whySizeRuleDecodeFailed ->
"Failed to decode sizeRule: " ++ whySizeRuleDecodeFailed
ProductDecodeError whyProductDecodeFailed ->
"Failed to decode product: " ++ whyProductDecodeFailed
in
modelDecodeErrors
|> List.indexedMap modelDecodeErrorToString
|> M.Errors