-- SPDX-FileCopyrightText: 2023 Felix Hamann ,Gregor Kleen ,Sarah Vaupel ,Sarah Vaupel ,Steffen Jost ,Steffen Jost ,Wolfgang Witt -- -- SPDX-License-Identifier: AGPL-3.0-or-later {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE UndecidableInstances #-} {-# OPTIONS_GHC -fno-warn-deprecations #-} -- `WidgetT`, `HandlerT` module Utils.Form where import ClassyPrelude.Yesod hiding (addMessage, addMessageI, cons, Proxy(..), identifyForm, addClass, mreq, areq, wreq, urlField) import Yesod.Auth (YesodAuth(maybeAuthId)) import Data.Kind (Type, Constraint) import qualified Yesod.Form as Yesod import Yesod.Core.Instances () import Settings import Utils.Parameters import Utils.Lens import Text.Blaze (Markup) import qualified Text.Blaze.Internal as Blaze (null) import qualified Data.Text as T import qualified Data.Char as C import Data.CaseInsensitive (CI) import qualified Data.CaseInsensitive as CI import Data.Universe import Data.List (nub, (!!)) import Data.Map.Lazy ((!)) import qualified Data.Map.Lazy as Map import qualified Data.Set as Set import Control.Monad.Reader.Class (MonadReader(..)) import Control.Monad.Writer.Class (MonadWriter(..), censor) import Control.Monad.State.Class (MonadState(..)) import qualified Control.Monad.State.Class as State import Control.Monad.Trans.RWS (RWST, execRWST, mapRWST) import Control.Monad.Trans.State (runStateT, evalStateT) import Control.Monad.Trans.Except (ExceptT, runExceptT) import qualified Control.Monad.Trans.Writer.Lazy as Lazy (WriterT) import Control.Monad.Fix (MonadFix) import Control.Monad.Morph (MFunctor(..)) import Control.Monad.Base import Control.Monad.Catch (MonadCatch) import Control.Monad.Random.Class (uniform, uniformMay, getRandom, getRandomR, getRandomRs, weighted) import Web.PathPieces import Data.UUID hiding (toText) import Data.Ratio ((%)) import Data.Fixed import Data.Scientific import Data.Time.Clock (NominalDiffTime, nominalDay) import Utils import Utils.Frontend.Notification -- import Utils.Message -- import Utils.PathPiece -- import Utils.Route import Data.Proxy import Data.Monoid (Endo(..), Sum(..)) import Network.URI (URI, parseURI, uriToString) import Data.Either (fromRight) import qualified Database.Esqueleto.Legacy as E import Handler.Utils.Random import qualified Data.Binary as Binary import qualified Data.ByteString.Base64.URL as Base64 (encodeUnpadded) import qualified Data.ByteString as BS ------------ -- Fields -- ------------ _olOptions :: Traversal' (OptionList a) (Option a) _olOptions f = \case x@OptionList{} -> (\olOptions -> x{olOptions}) <$> traverse f (olOptions x) x@OptionListGrouped{} -> (\olOptionsGrouped -> x{olOptionsGrouped}) <$> traverseOf (traverse . _2 . traverse) f (olOptionsGrouped x) _olOptionsGrouped :: Traversal' (OptionList a) (Text, [Option a]) _olOptionsGrouped f = \case x@OptionList{} -> pure x x@OptionListGrouped{} -> (\olOptionsGrouped -> x{olOptionsGrouped}) <$> traverse f (olOptionsGrouped x) _olReadExternal :: Lens' (OptionList a) (Text -> Maybe a) _olReadExternal f = \case x@OptionList{} -> (\olReadExternal -> x{olReadExternal}) <$> f (olReadExternal x) x@OptionListGrouped{} -> (\olReadExternalGrouped -> x{olReadExternalGrouped}) <$> f (olReadExternalGrouped x) -- if a field is required, but none should be there noField :: Monad m => Field m a noField = Field{..} where fieldParse _ _ = return $ Right Nothing fieldView _ _ _ _ _ = mempty fieldEnctype = UrlEncoded -- | Field to inject comments into forms, also see aformMessage commentField :: (Monad m, RenderMessage (HandlerSite m) a) => a -> Field m () commentField msg = Field {..} where fieldParse _ _ = return $ Right $ Just () fieldView _ _ _ _ _ = msg2widget msg fieldEnctype = UrlEncoded -------------------- -- Field Settings -- -------------------- fsl :: Text -> FieldSettings site fsl lbl = FieldSettings { fsLabel = SomeMessage lbl , fsTooltip = Nothing , fsId = Nothing , fsName = Nothing , fsAttrs = [] } fslI :: RenderMessage site msg => msg -> FieldSettings site fslI lbl = FieldSettings { fsLabel = SomeMessage lbl , fsTooltip = Nothing , fsId = Nothing , fsName = Nothing , fsAttrs = [] } fslp :: Text -> Text -> FieldSettings site fslp lbl placeholder = FieldSettings { fsLabel = SomeMessage lbl , fsTooltip = Nothing , fsId = Nothing , fsName = Nothing , fsAttrs = [("placeholder", placeholder)] } fslpI :: RenderMessage site msg => msg -> Text -> FieldSettings site fslpI lbl placeholder = FieldSettings { fsLabel = SomeMessage lbl , fsTooltip = Nothing , fsId = Nothing , fsName = Nothing , fsAttrs = [("placeholder", placeholder)] } -- NOTE: see Utils.insertAttrs for inserting/merging generic [[(Text,Text)] attributes addAttr :: Text -> Text -> FieldSettings site -> FieldSettings site addAttr attr valu fs = fs { fsAttrs = insertAttr attr valu $ fsAttrs fs } addAttrs :: Text -> [Text] -> FieldSettings site -> FieldSettings site addAttrs attr valus fs = fs { fsAttrs = insertAttr attr valu $ fsAttrs fs } where valu = T.intercalate " " valus data DatepickerPosition = DPLeft | DPRight | DPTop | DPBottom deriving (Eq,Ord,Enum,Bounded,Read,Show) instance Universe DatepickerPosition instance Finite DatepickerPosition nullaryPathPiece ''DatepickerPosition $ camelToPathPiece' 1 addDatepickerPositionAttr :: DatepickerPosition -> FieldSettings site -> FieldSettings site addDatepickerPositionAttr = addAttr "data-datepicker-position" . toPathPiece addPlaceholder :: Text -> FieldSettings site -> FieldSettings site addPlaceholder placeholder fs = fs { fsAttrs = (placeholderAttr, placeholder) : filter ((/= placeholderAttr) . fst) (fsAttrs fs) } where placeholderAttr = "placeholder" addClass :: PathPiece c => c -> FieldSettings site -> FieldSettings site addClass = over _fsAttrs . Yesod.addClass . toPathPiece addClasses :: (MonoFoldable mono, PathPiece (Element mono)) => mono -> FieldSettings site -> FieldSettings site addClasses = appEndo . foldMap (Endo . addClass) addName :: PathPiece p => p -> FieldSettings site -> FieldSettings site addName nm fs = fs { fsName = Just $ toPathPiece nm } addId :: PathPiece p => p -> FieldSettings site -> FieldSettings site addId fid fs = fs { fsId = Just $ toPathPiece fid } setTooltip :: RenderMessage site msg => msg -> FieldSettings site -> FieldSettings site setTooltip msg fs = fs { fsTooltip = Just $ SomeMessage msg } addDatalist :: MonadHandler m => HandlerT (HandlerSite m) IO (OptionList a) -> Field m a -> Field m a addDatalist mkOptions field = field { fieldView = \fId fName fAttrs fRes fReq -> do listId <- newIdent fieldView field fId fName (("list", listId) : fAttrs) fRes fReq options <- liftHandler $ toListOf _olOptions <$> mkOptions [whamlet| $newline never $forall Option{optionDisplay, optionExternalValue} <- options