{-# OPTIONS_GHC -fno-warn-incomplete-patterns #-} {-# OPTIONS_GHC -fno-warn-incomplete-uni-patterns #-} module Schema ( Schema, viewSchema, schemaTable, schemaForm, ) where import Data.Aeson qualified as A import Data.Aeson.Key qualified as AK import Data.Aeson.KeyMap qualified as AM import Data.List import Data.Map qualified as M import Data.Maybe import Data.Text qualified as T import Form qualified as F import Miso import Miso.String (MisoString, fromMisoString, toMisoString) import Route data Schema = Schema { id :: String, schema :: String, title :: String, type_ :: SchemaType } deriving (Show, Eq) instance A.FromJSON Schema where parseJSON = A.withObject "Schema" ( \v -> Schema <$> v A..: "$id" <*> v A..: "$schema" <*> v A..: "title" <*> A.parseJSON (A.Object v) ) data SchemaType = Object (M.Map String String) deriving (Show, Eq) instance A.FromJSON SchemaType where parseJSON = A.withObject "SchemaType" ( \v -> v A..: "type" >>= \case ("object" :: String) -> Object <$> v A..: "properties" ) viewSchema :: Schema -> View action viewSchema schema = case schema.type_ of Object properties -> ol_ [] $ ( \(k, v) -> li_ [] $ [ text (toMisoString k), text ":", text (toMisoString v) ] ) <$> (M.toList properties) schemaTable :: MisoString -> Schema -> [A.Object] -> View action schemaTable collection schema values = table_ [] [thead, tbody] where thead = thead_ [] $ case schema.type_ of Object properties -> [ tr_ [] $ [ th_ [] [text (toMisoString k)] | k <- M.keys properties ] ] tbody = tbody_ [] $ case schema.type_ of Object properties -> [ tr_ [] [ td_ [] $ [ case (k, getO (AK.fromString k) value) of ("$fileName", A.String fn) -> a_ [ href_ (routeToMisoString (EditValue collection (toMisoString fn))) ] [ text (toMisoString fn) ] (_, v) -> text $ case v of A.String s -> toMisoString s _ -> toMisoString (A.encode v) ] | k <- M.keys properties ] | value <- values ] schemaForm :: Schema -> F.Form A.Object A.Object schemaForm schema = fmap mergeJson . sequence $ case schema.type_ of Object properties -> ( \(AK.fromString -> k, v) -> case v of "string" -> AM.singleton k <$> ( F.mapValues (getO k) (setO k) $ fmap (A.String . fromMisoString) . F.mapValues fromJson toJson $ F.input (toMisoString (AK.toString k)) ) "string?" -> AM.singleton k <$> ( F.mapValues (getO k) (setO k) $ fmap (maybe A.Null (A.String . fromMisoString)) . F.mapValues fromJson toJson $ F.optional (F.input (toMisoString (AK.toString k))) ) ) <$> (M.toList properties) mergeJson :: [A.Object] -> A.Object mergeJson = foldl' mergeObject AM.empty mergeObject :: A.Object -> A.Object -> A.Object mergeObject kvs kvs' = AM.union kvs kvs' fromJson :: A.Value -> MisoString fromJson (A.String x) = toMisoString x fromJson _ = "" toJson :: MisoString -> A.Value -> A.Value toJson x _ = A.String (fromMisoString x) getO :: AK.Key -> A.Object -> A.Value getO k kvs = fromMaybe A.Null (AM.lookup k kvs) setO :: AK.Key -> A.Value -> A.Object -> A.Object setO k v kvs = AM.insert k v kvs