module Lexicons exposing ( blobRefDecoder , documentDecoder , emptyPublication , hexToRgb , publicationListItemDecoder , publicationRecordEncoder , rgbToHex ) import Json.Decode as Decode exposing (Decoder) import Json.Encode as Encode import Types exposing (..) -- Publication publicationListItemDecoder : Decoder Publication publicationListItemDecoder = Decode.map8 Publication (Decode.field "uri" rkeyFromUri) (Decode.field "value" (Decode.field "url" Decode.string)) (Decode.field "value" (Decode.field "name" Decode.string)) (Decode.field "value" (Decode.maybe (Decode.field "description" Decode.string))) (Decode.field "value" (Decode.maybe (Decode.field "icon" blobRefDecoder))) (Decode.field "value" (Decode.maybe (Decode.field "basicTheme" basicThemeDecoder))) (Decode.field "value" (Decode.maybe (Decode.field "preferences" preferencesDecoder))) (Decode.field "value" (Decode.maybe (Decode.field "createdAt" Decode.string))) rkeyFromUri : Decoder String rkeyFromUri = Decode.string |> Decode.map (\uri -> uri |> String.split "/" |> List.reverse |> List.head |> Maybe.withDefault "" ) basicThemeDecoder : Decoder BasicTheme basicThemeDecoder = Decode.map4 BasicTheme (Decode.field "background" rgbDecoder) (Decode.field "foreground" rgbDecoder) (Decode.field "accent" rgbDecoder) (Decode.field "accentForeground" rgbDecoder) rgbDecoder : Decoder RgbColor rgbDecoder = Decode.map3 RgbColor (Decode.field "r" Decode.int) (Decode.field "g" Decode.int) (Decode.field "b" Decode.int) blobRefDecoder : Decoder BlobRef blobRefDecoder = Decode.map3 BlobRef (Decode.at [ "ref", "$link" ] Decode.string) (Decode.field "mimeType" Decode.string) (Decode.field "size" Decode.int) preferencesDecoder : Decoder Preferences preferencesDecoder = Decode.map Preferences (Decode.maybe (Decode.field "showInDiscover" Decode.bool)) -- Publication record encoder (only includes fields that are Just) publicationRecordEncoder : Publication -> Encode.Value publicationRecordEncoder pub = let required : List ( String, Encode.Value ) required = [ ( "$type", Encode.string "site.standard.publication" ) , ( "url", Encode.string pub.url ) , ( "name", Encode.string pub.name ) ] optionals : List ( String, Encode.Value ) optionals = List.filterMap identity [ Maybe.map (\d -> ( "description", Encode.string d )) pub.description , Maybe.map (\i -> ( "icon", blobRefEncoder i )) pub.icon , Maybe.map (\t -> ( "basicTheme", basicThemeEncoder t )) pub.basicTheme , Maybe.map (\p -> ( "preferences", preferencesEncoder p )) pub.preferences , Maybe.map (\c -> ( "createdAt", Encode.string c )) pub.createdAt ] in Encode.object (required ++ optionals) basicThemeEncoder : BasicTheme -> Encode.Value basicThemeEncoder theme = Encode.object [ ( "$type", Encode.string "site.standard.theme.basic" ) , ( "background", rgbEncoder theme.background ) , ( "foreground", rgbEncoder theme.foreground ) , ( "accent", rgbEncoder theme.accent ) , ( "accentForeground", rgbEncoder theme.accentForeground ) ] rgbEncoder : RgbColor -> Encode.Value rgbEncoder c = Encode.object [ ( "$type", Encode.string "site.standard.theme.color#rgb" ) , ( "r", Encode.int c.r ) , ( "g", Encode.int c.g ) , ( "b", Encode.int c.b ) ] blobRefEncoder : BlobRef -> Encode.Value blobRefEncoder blob = Encode.object [ ( "$type", Encode.string "blob" ) , ( "ref", Encode.object [ ( "$link", Encode.string blob.link ) ] ) , ( "mimeType", Encode.string blob.mimeType ) , ( "size", Encode.int blob.size ) ] preferencesEncoder : Preferences -> Encode.Value preferencesEncoder prefs = Encode.object (List.filterMap identity [ Maybe.map (\b -> ( "showInDiscover", Encode.bool b )) prefs.showInDiscover ] ) -- Document documentDecoder : Decoder Document documentDecoder = Decode.map6 Document (Decode.field "title" Decode.string) (Decode.maybe (Decode.field "path" Decode.string)) (Decode.field "site" Decode.string) (Decode.field "publishedAt" Decode.string) (Decode.maybe (Decode.field "url" Decode.string)) (Decode.maybe (Decode.field "description" Decode.string)) -- Hex ↔ RGB helpers hexToRgb : String -> Maybe RgbColor hexToRgb raw = let cleaned : String cleaned = if String.startsWith "#" raw then String.dropLeft 1 raw else raw in if String.length cleaned == 6 then Maybe.map3 RgbColor (parseHexByte (String.slice 0 2 cleaned)) (parseHexByte (String.slice 2 4 cleaned)) (parseHexByte (String.slice 4 6 cleaned)) else Nothing rgbToHex : RgbColor -> String rgbToHex c = "#" ++ byteToHex c.r ++ byteToHex c.g ++ byteToHex c.b parseHexByte : String -> Maybe Int parseHexByte s = case String.toList (String.toLower s) of [ a, b ] -> Maybe.map2 (\x y -> x * 16 + y) (hexDigit a) (hexDigit b) _ -> Nothing hexDigit : Char -> Maybe Int hexDigit c = if c >= '0' && c <= '9' then Just (Char.toCode c - Char.toCode '0') else if c >= 'a' && c <= 'f' then Just (Char.toCode c - Char.toCode 'a' + 10) else Nothing byteToHex : Int -> String byteToHex n = let clamped : Int clamped = clamp 0 255 n hi : Int hi = clamped // 16 lo : Int lo = modBy 16 clamped in hexDigitToChar hi ++ hexDigitToChar lo hexDigitToChar : Int -> String hexDigitToChar n = if n < 10 then String.fromInt n else String.fromChar (Char.fromCode (Char.toCode 'a' + n - 10)) -- Empty publication (for Create flow) emptyPublication : Publication emptyPublication = { rkey = "" , url = "" , name = "" , description = Nothing , icon = Nothing , basicTheme = Nothing , preferences = Nothing , createdAt = Nothing }