-- Hoogle documentation, generated by Haddock -- See Hoogle, http://www.haskell.org/hoogle/ -- | Interactive HTML apps using type-safe serverside Haskell -- -- Interactive serverside web framework Inspired by HTMX, Elm, and -- Phoenix LiveView @package hyperbole @version 0.4.2 module Web.Hyperbole.Data.QueryData newtype Param Param :: Text -> Param [text] :: Param -> Text newtype ParamValue ParamValue :: Text -> ParamValue [text] :: ParamValue -> Text -- | Key-value store for query params and sessions newtype QueryData QueryData :: Map Param ParamValue -> QueryData singleton :: ToParam a => Param -> a -> QueryData insert :: ToParam a => Param -> a -> QueryData -> QueryData insertAll :: ToQuery a => a -> QueryData -> QueryData delete :: Param -> QueryData -> QueryData lookup :: FromParam a => Param -> QueryData -> Maybe a require :: FromParam a => Param -> QueryData -> Either Text a filterKey :: (Param -> Bool) -> QueryData -> QueryData member :: Param -> QueryData -> Bool elems :: QueryData -> [ParamValue] render :: QueryData -> ByteString parse :: ByteString -> QueryData queryData :: Query -> QueryData fromList :: [(Param, ParamValue)] -> QueryData toList :: QueryData -> [(Param, ParamValue)] -- | Decode a type from a QueryData. Missing fields are set to -- defaultParam -- --
-- data Filters = Filters
-- { active :: Bool
-- , term :: Text
-- }
-- deriving (Generic, FromQuery, ToQuery)
--
--
-- -- >>> parseQuery $ parse "active=true&search=asdf" -- Right (Filters True "asdf") ---- --
-- >>> parseQuery $ parse "search=asdf" -- Right (Filters False "asdf") --class FromQuery a parseQuery :: FromQuery a => QueryData -> Either Text a ($dmparseQuery) :: (FromQuery a, Generic a, GFromQuery (Rep a)) => QueryData -> Either Text a -- | A page can store state in the browser query string. ToQuery -- and FromQuery control how a datatype is encoded to a full query -- string -- --
-- data Filters = Filters
-- { active :: Bool
-- , term :: Text
-- }
-- deriving (Generic, FromQuery, ToQuery)
--
--
-- -- >>> render $ toQuery $ Filter True "asdf" -- "active=true&search=asdf" ---- -- If the value of a field is the same as DefaultParam, it will be -- omitted from the query string -- --
-- >>> render $ toQuery $ Filter True "" -- "active=true" ---- --
-- >>> render $ toQuery $ Filter False "" -- "" --class ToQuery a toQuery :: ToQuery a => a -> QueryData ($dmtoQuery) :: (ToQuery a, Generic a, GToQuery (Rep a)) => a -> QueryData -- | sessions, forms, and querys all encode data -- as query strings. ToParam and FromParam control how a datatype is -- encoded to a parameter. By default it simply url-encodes the show -- instance. -- --
-- data Todo = Todo
-- { id :: TodoId
-- , task :: Text
-- , completed :: Bool
-- }
-- deriving (Show, Read, ToParam, FromParam)
--
--
-- -- data Tags = Tags [Text] -- -- instance ToParam Tags where -- toParam (Tags ts) = ParamValue $ Text.intercalate "," ts --class ToParam a toParam :: ToParam a => a -> ParamValue ($dmtoParam) :: (ToParam a, Show a) => a -> ParamValue -- | Decode data from a query, session, or form -- parameter value -- --
-- data Todo = Todo
-- { id :: TodoId
-- , task :: Text
-- , completed :: Bool
-- }
-- deriving (Show, Read, ToParam, FromParam)
--
--
-- -- data Tags = Tags [Text] -- -- instance FromParam Tags where -- parseParam (ParamValue t) = -- pure $ Tags $ Text.splitOn "," t --class FromParam a parseParam :: FromParam a => ParamValue -> Either Text a ($dmparseParam) :: (FromParam a, Read a) => ParamValue -> Either Text a -- | Encode a Show as a query param showQueryParam :: Show a => a -> ParamValue -- | Decode a Read as a query param readQueryParam :: Read a => ParamValue -> Either Text a -- | Parse a Traversable (list) of params parseParams :: (Traversable t, FromParam a) => t ParamValue -> Either Text (t a) -- | Generic decoding of records from a Query class GFromQuery (f :: k -> Type) gParseQuery :: forall (p :: k). GFromQuery f => QueryData -> Either Text (f p) -- | Generic encoding of records to a Query class GToQuery (f :: k -> Type) gToQuery :: forall (p :: k). GToQuery f => f p -> QueryData -- | Data.Default doesn't have a Text instance. This class does class DefaultParam a defaultParam :: DefaultParam a => a ($dmdefaultParam) :: (DefaultParam a, Default a) => a instance Web.Hyperbole.Data.QueryData.DefaultParam Data.Text.Internal.Text instance Data.Default.Internal.Default a => Web.Hyperbole.Data.QueryData.DefaultParam a instance GHC.Classes.Eq Web.Hyperbole.Data.QueryData.Param instance GHC.Classes.Eq Web.Hyperbole.Data.QueryData.ParamValue instance Web.Hyperbole.Data.QueryData.FromParam GHC.Types.Bool instance Web.Hyperbole.Data.QueryData.FromParam GHC.Types.Char instance Web.Hyperbole.Data.QueryData.FromParam GHC.Types.Double instance (Web.Hyperbole.Data.QueryData.FromParam a, Web.Hyperbole.Data.QueryData.FromParam b) => Web.Hyperbole.Data.QueryData.FromParam (Data.Either.Either a b) instance Web.Hyperbole.Data.QueryData.FromParam GHC.Types.Float instance Web.Hyperbole.Data.QueryData.FromParam GHC.Types.Int instance Web.Hyperbole.Data.QueryData.FromParam GHC.Num.Integer.Integer instance GHC.Read.Read a => Web.Hyperbole.Data.QueryData.FromParam [a] instance (GHC.Read.Read k, GHC.Read.Read v, GHC.Classes.Ord k) => Web.Hyperbole.Data.QueryData.FromParam (Data.Map.Internal.Map k v) instance Web.Hyperbole.Data.QueryData.FromParam a => Web.Hyperbole.Data.QueryData.FromParam (GHC.Maybe.Maybe a) instance Web.Hyperbole.Data.QueryData.FromParam Data.Text.Internal.Text instance Web.Hyperbole.Data.QueryData.FromParam Data.Time.Clock.Internal.UTCTime.UTCTime instance Web.Hyperbole.Data.QueryData.FromParam GHC.Types.Word instance Web.Hyperbole.Data.QueryData.FromParam GHC.Word.Word16 instance Web.Hyperbole.Data.QueryData.FromParam GHC.Word.Word32 instance Web.Hyperbole.Data.QueryData.FromParam GHC.Word.Word64 instance Web.Hyperbole.Data.QueryData.FromParam GHC.Word.Word8 instance Web.Hyperbole.Data.QueryData.FromQuery Web.Hyperbole.Data.QueryData.QueryData instance forall k (f :: k -> *) (g :: k -> *). (Web.Hyperbole.Data.QueryData.GFromQuery f, Web.Hyperbole.Data.QueryData.GFromQuery g) => Web.Hyperbole.Data.QueryData.GFromQuery (f GHC.Generics.:*: g) instance (GHC.Generics.Selector s, Web.Hyperbole.Data.QueryData.FromParam a, Web.Hyperbole.Data.QueryData.DefaultParam a) => Web.Hyperbole.Data.QueryData.GFromQuery (GHC.Generics.M1 GHC.Generics.S s (GHC.Generics.K1 GHC.Generics.R a)) instance forall k (f :: k -> *) (d :: GHC.Generics.Meta). Web.Hyperbole.Data.QueryData.GFromQuery f => Web.Hyperbole.Data.QueryData.GFromQuery (GHC.Generics.M1 GHC.Generics.D d f) instance forall k (f :: k -> *) (c :: GHC.Generics.Meta). Web.Hyperbole.Data.QueryData.GFromQuery f => Web.Hyperbole.Data.QueryData.GFromQuery (GHC.Generics.M1 GHC.Generics.C c f) instance forall k (f :: k -> *) (g :: k -> *). (Web.Hyperbole.Data.QueryData.GToQuery f, Web.Hyperbole.Data.QueryData.GToQuery g) => Web.Hyperbole.Data.QueryData.GToQuery (f GHC.Generics.:*: g) instance (GHC.Generics.Selector s, Web.Hyperbole.Data.QueryData.ToParam a, GHC.Classes.Eq a, Web.Hyperbole.Data.QueryData.DefaultParam a) => Web.Hyperbole.Data.QueryData.GToQuery (GHC.Generics.M1 GHC.Generics.S s (GHC.Generics.K1 GHC.Generics.R a)) instance forall k (f :: k -> *) (d :: GHC.Generics.Meta). Web.Hyperbole.Data.QueryData.GToQuery f => Web.Hyperbole.Data.QueryData.GToQuery (GHC.Generics.M1 GHC.Generics.D d f) instance forall k (f :: k -> *) (d :: GHC.Generics.Meta). Web.Hyperbole.Data.QueryData.GToQuery f => Web.Hyperbole.Data.QueryData.GToQuery (GHC.Generics.M1 GHC.Generics.C d f) instance Data.String.IsString Web.Hyperbole.Data.QueryData.Param instance Data.String.IsString Web.Hyperbole.Data.QueryData.ParamValue instance GHC.Base.Monoid Web.Hyperbole.Data.QueryData.QueryData instance GHC.Classes.Ord Web.Hyperbole.Data.QueryData.Param instance GHC.Classes.Ord Web.Hyperbole.Data.QueryData.ParamValue instance GHC.Base.Semigroup Web.Hyperbole.Data.QueryData.QueryData instance GHC.Show.Show Web.Hyperbole.Data.QueryData.Param instance GHC.Show.Show Web.Hyperbole.Data.QueryData.ParamValue instance GHC.Show.Show Web.Hyperbole.Data.QueryData.QueryData instance Web.Hyperbole.Data.QueryData.ToParam GHC.Types.Bool instance Web.Hyperbole.Data.QueryData.ToParam GHC.Types.Char instance Web.Hyperbole.Data.QueryData.ToParam GHC.Types.Double instance (Web.Hyperbole.Data.QueryData.ToParam a, Web.Hyperbole.Data.QueryData.ToParam b) => Web.Hyperbole.Data.QueryData.ToParam (Data.Either.Either a b) instance Web.Hyperbole.Data.QueryData.ToParam GHC.Types.Float instance Web.Hyperbole.Data.QueryData.ToParam GHC.Types.Int instance Web.Hyperbole.Data.QueryData.ToParam GHC.Num.Integer.Integer instance GHC.Show.Show a => Web.Hyperbole.Data.QueryData.ToParam [a] instance (GHC.Show.Show k, GHC.Show.Show v) => Web.Hyperbole.Data.QueryData.ToParam (Data.Map.Internal.Map k v) instance Web.Hyperbole.Data.QueryData.ToParam a => Web.Hyperbole.Data.QueryData.ToParam (GHC.Maybe.Maybe a) instance Web.Hyperbole.Data.QueryData.ToParam Data.Text.Internal.Text instance Web.Hyperbole.Data.QueryData.ToParam Data.Time.Clock.Internal.UTCTime.UTCTime instance Web.Hyperbole.Data.QueryData.ToParam GHC.Types.Word instance Web.Hyperbole.Data.QueryData.ToParam GHC.Word.Word16 instance Web.Hyperbole.Data.QueryData.ToParam GHC.Word.Word32 instance Web.Hyperbole.Data.QueryData.ToParam GHC.Word.Word64 instance Web.Hyperbole.Data.QueryData.ToParam GHC.Word.Word8 instance Web.Hyperbole.Data.QueryData.ToQuery Network.HTTP.Types.URI.Query instance Web.Hyperbole.Data.QueryData.ToQuery Web.Hyperbole.Data.QueryData.QueryData module Web.Hyperbole.Data.Session data Cookie Cookie :: Param -> Maybe ParamValue -> Maybe [Segment] -> Cookie [key] :: Cookie -> Param [value] :: Cookie -> Maybe ParamValue [path] :: Cookie -> Maybe [Segment] newtype Cookies Cookies :: Map Param Cookie -> Cookies insert :: Cookie -> Cookies -> Cookies delete :: Param -> Cookies -> Cookies lookup :: Param -> Cookies -> Maybe ParamValue deletedCookie :: Session a => Cookie sessionCookie :: (Session a, ToParam a) => a -> Cookie fromList :: [Cookie] -> Cookies toList :: Cookies -> [Cookie] -- | Configure a data type to persist in the session -- --
-- data Preferences = Preferences
-- { color :: AppColor
-- }
-- deriving (Generic, Show, Read, ToParam, FromParam, Session)
--
-- instance DefaultParam Preferences where
-- defaultParam = Preferences White
--
class Session a
-- | Unique key for the Session. Defaults to the datatypeName
sessionKey :: Session a => Param
($dmsessionKey) :: (Session a, Generic a, GDatatypeName (Rep a)) => Param
-- | By default Sessions are persisted only to the current page. Set this
-- to `Just []` to make an application-wide Session
cookiePath :: Session a => Maybe [Segment]
($dmcookiePath) :: Session a => Maybe [Segment]
-- | generic datatype name
class GDatatypeName (f :: k -> Type)
gDatatypeName :: forall (p :: k). GDatatypeName f => f p -> Text
instance GHC.Classes.Eq Web.Hyperbole.Data.Session.Cookie
instance GHC.Classes.Eq Web.Hyperbole.Data.Session.Cookies
instance forall k (d :: GHC.Generics.Meta) (f :: k -> *). GHC.Generics.Datatype d => Web.Hyperbole.Data.Session.GDatatypeName (GHC.Generics.M1 GHC.Generics.D d f)
instance GHC.Base.Monoid Web.Hyperbole.Data.Session.Cookies
instance GHC.Base.Semigroup Web.Hyperbole.Data.Session.Cookies
instance GHC.Show.Show Web.Hyperbole.Data.Session.Cookie
instance GHC.Show.Show Web.Hyperbole.Data.Session.Cookies
module Web.Hyperbole.Route
-- | Derive this class to use a sum type as a route. Constructors and
-- Selectors map intuitively to url patterns
--
-- -- data AppRoute -- = Main -- | Messages -- | User UserId -- deriving (Eq, Generic) -- -- instance Route AppRoute where -- baseRoute = Just Main ---- --
-- >>> routeUrl Main -- / ---- --
-- >>> routeUrl (User 9) -- /user/9 --class Route a -- | The route to use if attempting to match on empty segments baseRoute :: Route a => Maybe a ($dmbaseRoute) :: (Route a, Generic a, GenRoute (Rep a)) => Maybe a -- | Try to match segments to a route matchRoute :: Route a => [Segment] -> Maybe a ($dmmatchRoute) :: (Route a, Generic a, GenRoute (Rep a)) => [Segment] -> Maybe a -- | Map a route to segments routePath :: Route a => a -> [Segment] ($dmroutePath) :: (Route a, Generic a, Eq a, GenRoute (Rep a)) => a -> [Segment] -- | Try to match a route, use defRoute if it's empty findRoute :: Route a => [Segment] -> Maybe a pathUrl :: [Segment] -> Url -- | Convert a Route to a Url -- --
-- >>> routeUrl (User 100) -- /user/100 --routeUrl :: Route a => a -> Url -- | Automatically derive Route class GenRoute (f :: k -> Type) genRoute :: forall (p :: k). GenRoute f => [Text] -> Maybe (f p) genPaths :: forall (p :: k). GenRoute f => f p -> [Text] genRouteRead :: forall {k} x (a :: k). Read x => [Text] -> Maybe (K1 R x a) data Url instance forall k (a :: k -> *) (b :: k -> *). (Web.Hyperbole.Route.GenRoute a, Web.Hyperbole.Route.GenRoute b) => Web.Hyperbole.Route.GenRoute (a GHC.Generics.:*: b) instance forall k (a :: k -> *) (b :: k -> *). (Web.Hyperbole.Route.GenRoute a, Web.Hyperbole.Route.GenRoute b) => Web.Hyperbole.Route.GenRoute (a GHC.Generics.:+: b) instance Web.Hyperbole.Route.Route sub => Web.Hyperbole.Route.GenRoute (GHC.Generics.K1 GHC.Generics.R sub) instance forall k (f :: k -> *) (c :: GHC.Generics.Meta). Web.Hyperbole.Route.GenRoute f => Web.Hyperbole.Route.GenRoute (GHC.Generics.M1 GHC.Generics.D c f) instance forall k (c :: GHC.Generics.Meta) (f :: k -> *). (GHC.Generics.Constructor c, Web.Hyperbole.Route.GenRoute f) => Web.Hyperbole.Route.GenRoute (GHC.Generics.M1 GHC.Generics.C c f) instance forall k (f :: k -> *) (c :: GHC.Generics.Meta). Web.Hyperbole.Route.GenRoute f => Web.Hyperbole.Route.GenRoute (GHC.Generics.M1 GHC.Generics.S c f) instance Web.Hyperbole.Route.GenRoute GHC.Generics.U1 instance Web.Hyperbole.Route.Route GHC.Types.Int instance Web.Hyperbole.Route.Route GHC.Num.Integer.Integer instance Web.Hyperbole.Route.Route GHC.Base.String instance Web.Hyperbole.Route.Route a => Web.Hyperbole.Route.Route (GHC.Maybe.Maybe a) instance Web.Hyperbole.Route.Route Data.Text.Internal.Text module Web.Hyperbole.Effect.Server -- | Low level effect mapping request/response to either HTTP or WebSockets data Server (a :: Type -> Type) b [LoadRequest] :: forall (a :: Type -> Type). Server a Request [SendResponse] :: forall (a :: Type -> Type). Client -> Response -> Server a () runServerWai :: forall (es :: [Effect]) a. IOE :> es => (ByteString -> ByteString) -> Request -> (Response -> IO ResponseReceived) -> Eff (Server ': es) a -> Eff es (Maybe ResponseReceived) renderCookie :: [Segment] -> Cookie -> ByteString cookiesFromHeader :: [(ByteString, ByteString)] -> Cookies runServerSockets :: forall (es :: [Effect]). IOE :> es => Connection -> Request -> Eff (Server ': es) Response -> Eff es Response errNotHandled :: Event Text Text -> String data Client Client :: Cookies -> QueryData -> Client [session] :: Client -> Cookies [query] :: Client -> QueryData data SocketError InvalidMessage :: Text -> SocketError data ContentType ContentHtml :: ContentType ContentText :: ContentType contentType :: ContentType -> (HeaderName, ByteString) newtype Metadata Metadata :: [(ByteString, Text)] -> Metadata newtype Host Host :: ByteString -> Host [text] :: Host -> ByteString data Request Request :: Host -> [Segment] -> Query -> ByteString -> Method -> Cookies -> Request [host] :: Request -> Host [path] :: Request -> [Segment] [query] :: Request -> Query [body] :: Request -> ByteString [method] :: Request -> Method [cookies] :: Request -> Cookies -- | Valid responses for a Hyperbole effect. Use -- notFound, etc instead. data Response Response :: TargetViewId -> View () () -> Response NotFound :: Response Redirect :: Url -> Response Err :: ResponseError -> Response Empty :: Response data ResponseError ErrParse :: Text -> ResponseError ErrQuery :: Text -> ResponseError ErrSession :: Param -> Text -> ResponseError ErrOther :: Text -> ResponseError ErrNotHandled :: Event Text Text -> ResponseError ErrAuth :: ResponseError -- | Serialized ViewId newtype TargetViewId TargetViewId :: Text -> TargetViewId -- | An action, with its corresponding id data Event id act Event :: id -> act -> Event id act [viewId] :: Event id act -> id [action] :: Event id act -> act instance GHC.Classes.Eq Web.Hyperbole.Effect.Server.SocketError instance GHC.Base.Monoid Web.Hyperbole.Effect.Server.Metadata instance GHC.Base.Semigroup Web.Hyperbole.Effect.Server.Metadata instance (GHC.Show.Show act, GHC.Show.Show id) => GHC.Show.Show (Web.Hyperbole.Effect.Server.Event id act) instance GHC.Show.Show Web.Hyperbole.Effect.Server.Host instance GHC.Show.Show Web.Hyperbole.Effect.Server.Request instance GHC.Show.Show Web.Hyperbole.Effect.Server.ResponseError instance GHC.Show.Show Web.Hyperbole.Effect.Server.SocketError module Web.Hyperbole.Effect.Hyperbole -- | The Hyperbole Effect allows you to access information in -- the Request, manually respondEarly, and manipulate the -- Client session and query. data Hyperbole (a :: Type -> Type) b [GetRequest] :: forall (a :: Type -> Type). Hyperbole a Request [RespondEarly] :: forall (a :: Type -> Type) b. Response -> Hyperbole a b [ModClient] :: forall (a :: Type -> Type). (Client -> Client) -> Hyperbole a () [GetClient] :: forall (a :: Type -> Type). Hyperbole a Client -- | Run the Hyperbole effect to Server runHyperbole :: forall (es :: [Effect]). Server :> es => Eff (Hyperbole ': es) Response -> Eff es Response data HyperState HyperState :: Request -> Client -> HyperState [request] :: HyperState -> Request [client] :: HyperState -> Client module Web.Hyperbole.Effect.Request -- | Return all information about the Request request :: forall (es :: [Effect]). Hyperbole :> es => Eff es Request -- | Return the request path -- --
-- >>> reqPath -- ["users", "100"] --reqPath :: forall (es :: [Effect]). Hyperbole :> es => Eff es [Segment] -- | Return the request body as a Web.FormUrlEncoded.Form -- -- Prefer using Type-Safe Forms when possible formBody :: forall (es :: [Effect]). Hyperbole :> es => Eff es Form module Web.Hyperbole.Effect.Session -- | Persist datatypes in browser cookies. If the session doesn't exist, -- the DefaultParam is used -- --
-- data Preferences = Preferences
-- { color :: AppColor
-- }
-- deriving (Generic, Show, Read, ToParam, FromParam, Session)
--
-- instance DefaultParam Preferences where
-- defaultParam = Preferences White
--
-- page :: (Hyperbole :> es) => Eff es (Page '[Content])
-- page = do
-- prefs <- session @Preferences
-- pure $ el (bg prefs.color) "Custom Background"
--
session :: forall a (es :: [Effect]). (Session a, DefaultParam a, FromParam a, Hyperbole :> es) => Eff es a
-- | Return a session if it exists
lookupSession :: forall a (es :: [Effect]). (Session a, FromParam a, Hyperbole :> es) => Eff es (Maybe a)
-- | Persist datatypes in browser cookies
--
--
-- data Preferences = Preferences
-- { color :: AppColor
-- }
-- deriving (Generic, Show, Read, ToParam, FromParam, Session)
--
-- instance DefaultParam Preferences where
-- defaultParam = Preferences White
--
-- instance HyperView Content es where
-- data Action Content
-- = SetColor AppColor
-- deriving (Show, Read, ViewAction)
--
-- update (SetColor clr) = do
-- let prefs = Preferences clr
-- saveSession prefs
-- pure $ el (bg prefs.color) "Custom Background"
--
saveSession :: forall a (es :: [Effect]). (Session a, ToParam a, Hyperbole :> es) => a -> Eff es ()
modifySession :: forall a (es :: [Effect]). (Session a, DefaultParam a, ToParam a, FromParam a, Hyperbole :> es) => (a -> a) -> Eff es a
modifySession_ :: forall a (es :: [Effect]). (Session a, DefaultParam a, ToParam a, FromParam a, Hyperbole :> es) => (a -> a) -> Eff es ()
-- | Remove a single Session from the browser cookies
deleteSession :: forall a (es :: [Effect]). (Session a, Hyperbole :> es) => Eff es ()
parseSession :: forall a (es :: [Effect]). (FromParam a, Hyperbole :> es) => Param -> ParamValue -> Eff es a
-- | save a single datatype to a specific key in the session
setCookie :: forall a (es :: [Effect]). (ToParam a, Hyperbole :> es) => Cookie -> Eff es ()
-- | Modify the client cookies
modifyCookies :: forall (es :: [Effect]). Hyperbole :> es => (Cookies -> Cookies) -> Eff es ()
-- | Return all the cookies, both those sent in the request and others
-- added by the page
sessionCookies :: forall (es :: [Effect]). Hyperbole :> es => Eff es Cookies
-- | Return the session from the Client cookies
clientSessionCookies :: forall (es :: [Effect]). Hyperbole :> es => Eff es Cookies
-- | Return the session from the Request cookies
requestSessionCookies :: forall (es :: [Effect]). Hyperbole :> es => Eff es Cookies
module Web.Hyperbole.Effect.Query
-- | Parse querystring from the Request into a datatype. See
-- FromQuery
--
--
-- data Filters = Filters
-- { active :: Bool
-- , term :: Text
-- }
-- deriving (Generic, FromQuery, ToQuery)
--
-- page :: (Hyperbole :> es) => Eff es (Page '[Todos])
-- page = do
-- filters <- query @Filters
-- todos <- loadTodos filters
-- pure $ do
-- hyper Todos $ todosView todos
--
query :: forall a (es :: [Effect]). (FromQuery a, Hyperbole :> es) => Eff es a
-- | Update the client's querystring to an encoded datatype. See
-- ToQuery
--
-- -- instance HyperView Todos es where -- data Action Todos -- = SetFilters Filters -- deriving (Show, Read, ViewAction) -- -- update (SetFilters filters) = do -- setQuery filters -- todos <- loadTodos filters -- pure $ todosView todos --setQuery :: forall a (es :: [Effect]). (ToQuery a, Hyperbole :> es) => a -> Eff es () -- | Parse a single query parameter. Return a 400 status if missing or if -- parsing fails. See FromParam -- --
-- page' :: (Hyperbole :> es) => Eff es (Page '[Message]) -- page' = do -- msg <- param "message" -- pure $ do -- hyper Message $ messageView msg --param :: forall a (es :: [Effect]). (FromParam a, Hyperbole :> es) => Param -> Eff es a -- | Parse a single parameter from the query string if available lookupParam :: forall a (es :: [Effect]). (FromParam a, Hyperbole :> es) => Param -> Eff es (Maybe a) -- | Modify the client's querystring to set a single parameter. See -- ToParam -- --
-- instance HyperView Message es where -- data Action Message -- = SetMessage Text -- deriving (Show, Read, ViewAction) -- -- update (SetMessage msg) = do -- setParam "message" msg -- pure $ messageView msg --setParam :: forall a (es :: [Effect]). (ToParam a, Hyperbole :> es) => Param -> a -> Eff es () -- | Delete a single parameter from the query string deleteParam :: forall (es :: [Effect]). Hyperbole :> es => Param -> Eff es () -- | Return the query from Request as a QueryData queryParams :: forall (es :: [Effect]). Hyperbole :> es => Eff es QueryData modifyQuery :: forall (es :: [Effect]). Hyperbole :> es => (QueryData -> QueryData) -> Eff es () module Web.Hyperbole.TypeList type family (xs :: [a]) <++> (ys :: [a]) :: [a] type family Remove (x :: a) (ys :: [a]) :: [a] type family RemoveAll (xs :: [a]) (ys :: [a]) :: [a] type Elem e (es :: [Type]) = ElemOr e es NotElem e es :: Constraint type family ElemOr (e :: a) (es :: [a]) err type family AllElemOr (xs :: [a]) (ys :: [a]) err type NotElem (x :: t) (orig :: [Type]) = TypeError 'ShowType x ':<>: 'Text " not found in " ':<>: 'ShowType orig :: k type family TupleList a :: [Type] module Web.Hyperbole.HyperView -- | HyperViews are interactive subsections of a Page -- -- Create an instance with a unique view id type and a sum type -- describing the actions the HyperView supports. The View Id can contain -- context (a database id, for example) -- --
-- data Message = Message -- deriving (Show, Read, ViewId) -- -- instance HyperView Message es where -- data Action Message -- = SetMessage Text -- deriving (Show, Read, ViewAction) -- -- update (SetMessage msg) = -- pure $ messageView msg --class (ViewId id, ViewAction Action id) => HyperView id (es :: [Effect]) where { -- | Outline all actions that are permitted in this HyperView -- --
-- data Action Message = SetMessage Text | ClearMessage
-- deriving (Show, Read, ViewAction)
--
data Action id;
-- | Include any child hyperviews here. The compiler will make sure that
-- the page knows how to handle them
--
--
-- type Require = '[ChildView]
--
type Require id :: [Type];
type Require id = '[] :: [Type];
}
-- | Specify how the view should be updated for each Action
--
-- -- update (SetMessage msg) = pure $ messageView msg -- update ClearMessage = pure $ messageView "" --update :: HyperView id es => Action id -> Eff (Reader id ': es) (View id ()) -- | The top-level view returned by a Page. It carries a -- type-level list of every HyperView used in our Page so -- the compiler can check our work and wire everything together. data Root (views :: [Type]) Root :: Root (views :: [Type]) type family ValidDescendents x :: [Type] type family NextDescendents (ex :: [Type]) (xs :: [Type]) :: [Type] type NotHandled id (ctx :: t) (views :: [Type]) = TypeError 'Text "HyperView " ':<>: 'ShowType id ':<>: 'Text " not found in (Require " ':<>: 'ShowType ctx ':<>: 'Text ")" ':$$: 'Text " " ':<>: 'ShowType views ':$$: 'Text "Try adding it to the HyperView instance:" ':$$: 'Text " instance HyperView " ':<>: 'ShowType ctx ':<>: 'Text " where" ':$$: 'Text " type Action " ':<>: 'ShowType ctx ':<>: 'Text " = " ':<>: 'ShowType Action id ':<>: 'Text "" ':$$: 'Text " type Require " ':<>: 'ShowType ctx ':<>: 'Text " = [" ':<>: 'ShowType id ':<>: 'Text ", ...]" :: k type NotDesc (id :: t) (ctx :: t1) (x :: t2) (cs :: t3) = TypeError 'Text "" ':<>: 'ShowType x ':<>: 'Text ", a child of HyperView " ':<>: 'ShowType id ':<>: 'Text ", not handled by context " ':<>: 'ShowType ctx ':$$: 'Text " Require = " ':<>: 'ShowType cs :: k type NotInPage (x :: a) (total :: [a]) = TypeError 'Text "" ':<>: 'ShowType x ':<>: 'Text " not included in: " ':$$: 'Text " Page es " ':<>: 'ShowType total ':$$: 'Text "try expanding the page views to:" ':$$: 'Text " Page es " ':<>: 'ShowType x ': total :: k type HyperViewHandled id ctx = (ElemOr id Require ctx NotHandled id ctx Require ctx :: Constraint, CheckDescendents id ctx) type family CheckDescendents id ctx type family AllInPage (ids :: [a]) (total :: [a]) -- | Embed a HyperView into another View -- --
-- page :: Eff es (Page '[Message]) -- page = do -- pure $ do -- col (pad 10 . gap 10) $ do -- el (bold . fontSize 24) "Unchanging Header" -- hyper Message $ messageView "Hello World" --hyper :: (HyperViewHandled id ctx, ViewId id) => id -> View id () -> View ctx () hyperUnsafe :: ViewId id => id -> View id () -> View ctx () class ViewAction a toAction :: ViewAction a => a -> Text ($dmtoAction) :: (ViewAction a, Show a) => a -> Text parseAction :: ViewAction a => Text -> Maybe a ($dmparseAction) :: (ViewAction a, Read a) => Text -> Maybe a class ViewId a toViewId :: ViewId a => a -> Text ($dmtoViewId) :: (ViewId a, Show a) => a -> Text parseViewId :: ViewId a => Text -> Maybe a ($dmparseViewId) :: (ViewId a, Read a) => Text -> Maybe a -- | Access the viewId in a View or update -- --
-- data Contact = Contact UserId -- deriving (Show, Read, ViewId) -- -- instance (Users :> es, Debug :> es) => HyperView Contact es where -- data Action Contact -- = Edit -- | Save -- | View -- deriving (Show, Read, ViewAction) -- -- update action = do -- -- No matter which action we are performing, let's look up the user to make sure it exists -- Contact uid <- viewId -- u <- Users.find uid -- case action of -- View -> do -- pure $ contactView u -- Edit -> do -- pure $ contactEditView u -- Save -> do -- delay 1000 -- unew <- parseUser uid -- Users.save unew -- pure $ contactView unew --class HasViewId (m :: k -> Type) (view :: k) viewId :: HasViewId m view => m view instance Web.Hyperbole.HyperView.HasViewId (Effectful.Internal.Monad.Eff (Effectful.Reader.Dynamic.Reader view : es)) view instance Web.Hyperbole.HyperView.HasViewId (Web.View.View.View ctx) ctx instance Web.Hyperbole.HyperView.HyperView (Web.Hyperbole.HyperView.Root views) es instance GHC.Read.Read (Web.Hyperbole.HyperView.Action (Web.Hyperbole.HyperView.Root views)) instance GHC.Read.Read (Web.Hyperbole.HyperView.Root views) instance GHC.Show.Show (Web.Hyperbole.HyperView.Action (Web.Hyperbole.HyperView.Root views)) instance GHC.Show.Show (Web.Hyperbole.HyperView.Root views) instance Web.Hyperbole.HyperView.ViewAction (Web.Hyperbole.HyperView.Action (Web.Hyperbole.HyperView.Root views)) instance Web.Hyperbole.HyperView.ViewAction () instance Web.Hyperbole.HyperView.ViewId (Web.Hyperbole.HyperView.Root views) module Web.Hyperbole.Effect.Response -- | Respond with the given view, and stop execution respondEarly :: forall (es :: [Effect]) id. (Hyperbole :> es, HyperView id es) => id -> View id () -> Eff es () -- | Respond immediately with 404 Not Found -- --
-- findUser :: (Hyperbole :> es, Users :> es) => Int -> Eff es User -- findUser uid = do -- mu <- send (LoadUser uid) -- maybe notFound pure mu -- -- userPage :: (Hyperbole :> es, Users :> es) => Eff es (Page '[]) -- userPage = do -- user <- findUser 100 -- -- -- skipped if user not found -- pure $ userView user --notFound :: forall (es :: [Effect]) a. Hyperbole :> es => Eff es a -- | Respond immediately with a parse error parseError :: forall (es :: [Effect]) a. Hyperbole :> es => Text -> Eff es a -- | Redirect immediately to the Url redirect :: forall (es :: [Effect]) a. Hyperbole :> es => Url -> Eff es a -- | Manually set the response to the given view. Normally you would return -- a View from runPage instead view :: forall (es :: [Effect]). Hyperbole :> es => View () () -> Eff es Response module Web.Hyperbole.Effect.Event getEvent :: forall id (es :: [Effect]). (HyperView id es, Hyperbole :> es) => Eff es (Maybe (Event id (Action id))) lookupEvent :: Query -> Maybe (Event Text Text) -- | Lower-level lookup straight from the request lookupParam :: ByteString -> Query -> Maybe Text module Web.Hyperbole.Effect.Handler class RunHandlers (views :: [Type]) (es :: [Effect]) runHandlers :: RunHandlers views es => Eff es () runHandler :: forall id (es :: [Effect]). (HyperView id es, Hyperbole :> es) => (Action id -> Eff (Reader id ': es) (View id ())) -> Eff es () runLoad :: forall (views :: [Type]) (es :: [Effect]). (Hyperbole :> es, RunHandlers views es) => Eff es (View (Root views) ()) -> Eff es Response guardNoEvent :: forall (es :: [Effect]). Hyperbole :> es => Eff es () loadToResponse :: forall (es :: [Effect]) (total :: [Type]). Eff es (View (Root total) ()) -> Eff es Response instance (Web.Hyperbole.HyperView.HyperView view es, Web.Hyperbole.Effect.Handler.RunHandlers views es) => Web.Hyperbole.Effect.Handler.RunHandlers (view : views) es instance Web.Hyperbole.Effect.Handler.RunHandlers '[] es module Web.Hyperbole.Page -- | Conceptually, an application is dividied up into multiple -- Pages. Each page module should have a function that returns a -- Page. The Page itself is a View with a type-level -- list of HyperViews used on the page. -- --
-- page :: Eff es (Page [Message, Count]) -- page = do -- pure $ do -- row id $ do -- hyper Message $ messageView "Hello" -- hyper Count $ countView 0 --type Page (views :: [Type]) = View Root views () -- | Run a Page and return a Response -- --
-- main :: IO () -- main = do -- run 3000 $ do -- liveApp (basicDocument "Example") (runPage page) -- -- page :: Eff es (Page '[]) -- page = do -- pure $ do -- col (pad 10) $ do -- el bold "Hello World" --runPage :: forall (es :: [Effect]) (views :: [Type]). (Hyperbole :> es, RunHandlers views es) => Eff es (Page views) -> Eff es Response module Web.Hyperbole.View.Embed -- | Default CSS to remove unintuitive default styles. This or -- cssResetLink is required. -- --
-- import Data.String.Interpolate (i)
--
-- toDocument :: ByteString -> ByteString
-- toDocument cnt =
-- [i|<html>
-- <head>
-- <style type="text/css">#{cssResetEmbed}</style>
-- </head>
-- <body>#{cnt}</body>
-- </html>|]
--
cssResetEmbed :: ByteString
-- | Alternatively, the reset is available on a CDN
--
--
-- import Data.String.Interpolate (i)
--
-- toDocument :: ByteString -> ByteString
-- toDocument cnt =
-- [i|<html>
-- <head>
-- <link rel="stylesheet" href="#{cssResetLink}">
-- </head>
-- <body>#{cnt}</body>
-- </html>|]
--
cssResetLink :: Text
scriptEmbed :: ByteString
module Web.Hyperbole.Application
waiApp :: (ByteString -> ByteString) -> Eff '[Hyperbole, Server, Concurrent, IOE] Response -> Application
-- | Upgrade a websockets ServerApp to a wai
-- Application. Uses the given backup Application to handle
-- Requests that are not WebSocket requests.
--
-- -- websocketsOr opts ws_app backup_app = \req respond -> -- case websocketsApp opts ws_app req of -- Nothing -> backup_app req send_response -- Just res -> respond res ---- -- For example, below is an Application that sends "Hello, -- client!" to each connected client. -- --
-- app :: Application
-- app = websocketsOr defaultConnectionOptions wsApp backupApp
-- where
-- wsApp :: ServerApp
-- wsApp pending_conn = do
-- conn <- acceptRequest pending_conn
-- sendTextData conn ("Hello, client!" :: Text)
--
-- backupApp :: Application
-- backupApp _ respond = respond $ responseLBS status400 [] "Not a WebSocket request"
--
websocketsOr :: ConnectionOptions -> ServerApp -> Application -> Application
-- | The default connection options:
--
-- -- main :: IO () -- main = do -- run 3000 $ do -- liveApp (basicDocument "Example") (runPage page) --liveApp :: (ByteString -> ByteString) -> Eff '[Hyperbole, Server, Concurrent, IOE] Response -> Application socketApp :: forall (es :: [Effect]). (IOE :> es, Concurrent :> es) => Eff (Hyperbole ': (Server ': es)) Response -> PendingConnection -> Eff es () -- | wrap HTML fragments in a simple document with a custom title and -- include required embeds -- --
-- liveApp (basicDocument "App Title") (routeRequest router) ---- -- You may want to specify a custom document function to import custom -- javascript, css, or add other information to the <head> -- --
-- import Data.String.Interpolate (i)
-- import Web.Hyperbole (scriptEmbed, cssResetEmbed)
--
-- customDocument :: ByteString -> ByteString
-- customDocument content =
-- [i|<html>
-- <head>
-- <title>My Website</title>
-- <script type="text/javascript">#{scriptEmbed}</script>
-- <style type="text/css">#{cssResetEmbed}</style>
-- <script type="text/javascript" src="custom.js"></script>
-- </head>
-- <body>#{content}</body>
-- </html>|]
--
basicDocument :: Text -> ByteString -> ByteString
-- | Route URL patterns to different pages
--
-- -- import Example.Docs.Page.Messages qualified as Messages -- import Example.Docs.Page.Users qualified as Users -- -- type UserId = Int -- -- data AppRoute -- = Main -- | Messages -- | User UserId -- deriving (Eq, Generic) -- -- instance Route AppRoute where -- baseRoute = Just Main -- -- router :: (Hyperbole :> es) => AppRoute -> Eff es Response -- router Messages = runPage Messages.page -- router (User cid) = runPage $ Users.page cid -- router Main = do -- view $ do -- el_ "click a link below to visit a page" -- route Messages id "Messages" -- route (User 1) id "User 1" -- route (User 2) id "User 2" --routeRequest :: forall (es :: [Effect]) route. (Hyperbole :> es, Route route) => (route -> Eff es Response) -> Eff es Response module Web.Hyperbole.View.Event type DelayMs = Int -- | Send the action after N milliseconds. Can be used to implement lazy -- loading or polling. See Example.Page.Concurrent -- --
-- viewUpdating :: Int -> View Progress () -- viewUpdating prg = do -- let pct = fromIntegral prg / 100 -- Progress taskId _ <- viewId -- col (onLoad (CheckProgress prg) 0) $ do -- progressBar pct $ do -- el grow $ text $ "Task" <> pack (show taskId) --onLoad :: ViewAction (Action id) => Action id -> DelayMs -> Mod id onClick :: ViewAction (Action id) => Action id -> Mod id onDblClick :: ViewAction (Action id) => Action id -> Mod id -- | Run an action when the user types into an input or -- textarea. -- -- WARNING: a short delay can result in poor performance. It is not -- recommended to set the value of the input -- --
-- input (onInput OnSearch) 250 id --onInput :: ViewAction (Action id) => (Text -> Action id) -> DelayMs -> Mod id onSubmit :: ViewAction (Action id) => Action id -> Mod id onKeyDown :: ViewAction (Action id) => Key -> Action id -> Mod id onKeyUp :: ViewAction (Action id) => Key -> Action id -> Mod id keyDataAttribute :: Key -> Text data Key ArrowDown :: Key ArrowUp :: Key ArrowLeft :: Key ArrowRight :: Key Enter :: Key Space :: Key Escape :: Key Alt :: Key CapsLock :: Key Control :: Key Fn :: Key Meta :: Key Shift :: Key OtherKey :: Text -> Key -- | Serialize a constructor that expects a single Text, like `data -- MyAction = GoSearch Text` toActionInput :: ViewAction a => (Text -> a) -> Text -- | Apply a Mod only when a request is in flight. See -- Example.Page.Contact -- --
-- contactEditView :: User -> View Contact () -- contactEditView u = do -- el (hide . onRequest flexCol) contactLoading -- el (onRequest hide) $ contactEdit View Save u --onRequest :: Mod id -> Mod id -- | Internal dataTarget :: ViewId a => a -> Mod x -- | Allow inputs to trigger actions for a different view target :: (HyperViewHandled id ctx, ViewId id) => id -> View id () -> View ctx () instance GHC.Read.Read Web.Hyperbole.View.Event.Key instance GHC.Show.Show Web.Hyperbole.View.Event.Key module Web.Hyperbole.View.Element -- | <button> HTML tag which sends the action when pressed -- --
-- button SomeAction (border 1) "Click Me" --button :: ViewAction (Action id) => Action id -> Mod id -> View id () -> View id () -- | Type-safe dropdown. Sends (opt -> Action id) when selected. The -- selection predicate (opt -> Bool) controls which option is -- selected. See Example.Page.Filter -- --
-- familyDropdown :: Filters -> View Languages () -- familyDropdown filters = -- dropdown SetFamily (== filters.family) (border 1 . pad 10) $ do -- option Nothing "Any" -- option (Just ObjectOriented) "Object Oriented" -- option (Just Functional) "Functional" --dropdown :: ViewAction (Action id) => (opt -> Action id) -> (opt -> Bool) -> Mod id -> View (Option opt id (Action id)) () -> View id () -- | An option for a dropdown. First argument is passed to (opt -- -> Action id) in the dropdown, and to the selected predicate option :: (ViewAction (Action id), Eq opt) => opt -> View (Option opt id (Action id)) () -> View (Option opt id (Action id)) () -- | sets selected = true if the dropdown predicate returns True selected :: Bool -> Mod id -- | The view context for an option data Option opt (id :: k) action Option :: (opt -> action) -> (opt -> Bool) -> Option opt (id :: k) action [toAction] :: Option opt (id :: k) action -> opt -> action [selected] :: Option opt (id :: k) action -> opt -> Bool -- | A live search field search :: ViewAction (Action id) => (Text -> Action id) -> DelayMs -> Mod id -> View id () -- | A hyperlink to another route -- --
-- >>> route (User 100) id "View User" -- <a href="/user/100">View User</a> --route :: Route a => a -> Mod c -> View c () -> View c () module Web.Hyperbole.View -- | Embed a HyperView into another View -- --
-- page :: Eff es (Page '[Message]) -- page = do -- pure $ do -- col (pad 10 . gap 10) $ do -- el (bold . fontSize 24) "Unchanging Header" -- hyper Message $ messageView "Hello World" --hyper :: (HyperViewHandled id ctx, ViewId id) => id -> View id () -> View ctx () -- | Add text to a view. Not required for string literals -- --
-- el_ $ do -- "Hello: " -- text user.name --text :: Text -> View c () data Url Url :: Text -> Text -> [Segment] -> Query -> Url [scheme] :: Url -> Text [domain] :: Url -> Text [path] :: Url -> [Segment] [query] :: Url -> Query -- | Apply to even-numbered children even :: Mod c -> Mod c -- | Options for styles that support specifying various sides. This has a -- "fake" Num instance to support literals -- --
-- border 5 -- border (X 2) -- border (TRBL 0 5 0 0) --data Sides a All :: a -> Sides a TRBL :: a -> a -> a -> a -> Sides a X :: a -> Sides a Y :: a -> Sides a XY :: a -> a -> Sides a T :: a -> Sides a R :: a -> Sides a B :: a -> Sides a L :: a -> Sides a TR :: a -> a -> Sides a TL :: a -> a -> Sides a BR :: a -> a -> Sides a BL :: a -> a -> Sides a -- | Element functions expect a modifier function as their first argument. -- These can add attributes and classes. Combine multiple Mods -- with (.) -- --
-- userEmail :: User -> View c () -- userEmail user = input (fontSize 16 . active) (text user.email) -- where -- active = isActive user then bold else id ---- -- If you don't want to specify any attributes, you can use id -- --
-- plainView :: View c () -- plainView = el id "No styles" --type Mod context = Attributes context -> Attributes context -- | Cut off the contents of the element truncate :: Mod c -- | Apply to odd-numbered children odd :: Mod c -> Mod c -- | Set the list style of an item -- --
-- ol id $ do -- li (list Decimal) "First" -- li (list Decimal) "Second" -- li (list Decimal) "Third" --list :: (ToClassName a, Style ListType a) => a -> Mod c data Position Absolute :: Position Fixed :: Position Sticky :: Position Relative :: Position -- | A hyperlink to the given url link :: Url -> Mod c -> View c () -> View c () value :: Text -> Mod c data Display Block :: Display -- | Set top, bottom, right, and left. See stack and popup offset :: Sides Length -> Mod c -- | Space surrounding the children of the element -- -- To create even spacing around and between all elements: -- --
-- col (pad 10 . gap 10) $ do -- el_ "one" -- el_ "two" -- el_ "three" --pad :: Sides Length -> Mod c -- | Views are HTML fragments that carry all CSS used by any child -- element. -- --
-- view :: View c () -- view = col (pad 10 . gap 10) $ do -- el bold "Hello" -- el_ "World" ---- -- They can also have a context which can be used to create type-safe or -- context-aware elements. See context or table for an -- example data View context a style :: Text -> View c () -- | We can intuitively create layouts with combinations of row, -- col, stack, grow, and space -- -- Wrap main content in layout to allow the view to consume -- vertical screen space -- --
-- holygrail :: View c () -- holygrail = layout id $ do -- row section "Top Bar" -- row grow $ do -- col section "Left Sidebar" -- col (section . grow) "Main Content" -- col section "Right Sidebar" -- row section "Bottom Bar" -- where section = border 1 --layout :: Mod c -> View c () -> View c () name :: Text -> Mod c data None None :: None -- | position:absolute, relative, etc. See stack and popup position :: Position -> Mod c -- | Space that fills the available space in the parent row or -- col. -- --
-- row id $ do -- space -- el_ "Right" ---- -- This is equivalent to an empty element with grow -- --
-- space = el grow none --space :: View c () -- | ToColor allows you to create a type containing your application's -- colors: -- --
-- data AppColor -- = White -- | Primary -- | Dark -- -- instance ToColor AppColor where -- colorValue White = "#FFF" -- colorValue Dark = "#333" -- colorValue Primary = "#00F" -- -- hello :: View c () -- hello = el (bg Primary . color White) "Hello" --class ToColor a colorValue :: ToColor a => a -> HexColor colorName :: ToColor a => a -> Text ($dmcolorName) :: (ToColor a, Show a) => a -> Text -- | Apply when hovering over an element -- --
-- el (bg Primary . hover (bg PrimaryLight)) "Hover" --hover :: Mod c -> Mod c -- | Embed static, unescaped HTML or SVG. Take care not to use raw -- with user-generated content. -- --
-- spinner = raw "<svg>...</svg>" --raw :: Text -> View c () -- | Stack children on top of each other. Each child has the full width. -- See popup -- --
-- stack id $ do -- layer id "Background" -- layer (bg Black . opacity 0.5) "Overlay" --stack :: Mod c -> Layer c () -> View c () -- | This layer is not included in the stack size, and covers -- content outside of it. If used outside of stack, the popup is offset -- from the entire page. -- --
-- stack id $ do -- layer id $ input (value "Autocomplete Box") -- layer (popup (TRBL 50 0 0 0)) $ do -- el_ "Item 1" -- el_ "Item 2" -- el_ "Item 3" -- el_ "This is covered by the menu" --popup :: Sides Length -> Mod c -- | Create a type safe data table by specifying columns -- --
-- usersTable :: [User] -> View c () -- usersTable us = do -- table id us $ do -- tcol (th hd "Name") $ \u -> td cell $ text u.name -- tcol (th hd "Email") $ \u -> td cell $ text u.email -- where -- hd = cell . bold -- cell = pad 4 . border 1 --table :: Mod c -> [dt] -> Eff '[Writer [TableColumn c dt]] () -> View c () -- | The Attributes for an Element. Classes are merged and managed -- separately from the other attributes. data Attributes (c :: k) -- | Lay out children in a row -- --
-- row id $ do -- el_ "Left" -- space -- el_ "Right" --row :: Mod c -> View c () -> View c () -- | Lay out children in a column. -- --
-- col grow $ do -- el_ "Top" -- space -- el_ "Bottom" --col :: Mod c -> View c () -> View c () -- | Alternatively, the reset is available on a CDN -- --
-- import Data.String.Interpolate (i)
--
-- toDocument :: ByteString -> ByteString
-- toDocument cnt =
-- [i|<html>
-- <head>
-- <link rel="stylesheet" href="#{cssResetLink}">
-- </head>
-- <body>#{cnt}</body>
-- </html>|]
--
cssResetLink :: Text
data Align
AlignCenter :: Align
AlignLeft :: Align
AlignRight :: Align
AlignJustify :: Align
-- | Hexidecimal Color. Can be specified with or without the leading
-- #. Recommended to use an AppColor type instead of manually
-- using hex colors. See ToColor
newtype HexColor
HexColor :: Text -> HexColor
-- | Media allows for responsive designs that change based on
-- characteristics of the window. See Layout Example
data Media
MinWidth :: Int -> Media
MaxWidth :: Int -> Media
-- | Milliseconds, used for transitions
data Ms
-- | Px, converted to Rem. Allows for the user to change the document font
-- size and have the app scale accordingly. But allows the programmer to
-- code in pixels to match a design
data PxRem
data Length
PxRem :: PxRem -> Length
Pct :: Float -> Length
-- | Apply when the Media matches the current window. This allows for
-- responsive designs
--
-- -- el (width 100 . media (MinWidth 800) (width 400)) -- "Big if window > 800" --media :: Media -> Mod c -> Mod c data TransitionProperty Width :: PxRem -> TransitionProperty Height :: PxRem -> TransitionProperty BgColor :: HexColor -> TransitionProperty Color :: HexColor -> TransitionProperty data ListType Decimal :: ListType Disc :: ListType data Inner Inner :: Inner data Shadow -- | Set to a specific width width :: Length -> Mod c -- | Set to a specific height height :: Length -> Mod c -- | Allow width to grow to contents but not shrink any smaller than value minWidth :: Length -> Mod c -- | Allow height to grow to contents but not shrink any smaller than value minHeight :: Length -> Mod c -- | The space between child elements. See pad gap :: Length -> Mod c fontSize :: Length -> Mod c -- | Add a drop shadow to an element -- --
-- input (shadow Inner) "Inset Shadow" -- button (shadow ()) "Click Me" --shadow :: (Style Shadow a, ToClassName a) => a -> Mod c -- | Round the corners of the element rounded :: Length -> Mod c -- | Set the background color. See ToColor bg :: ToColor clr => clr -> Mod ctx -- | Set the text color. See ToColor color :: ToColor clr => clr -> Mod ctx bold :: Mod c italic :: Mod c underline :: Mod c opacity :: Float -> Mod c -- | Set a border around the element -- --
-- el (border 1) "all sides" -- el (border (X 1)) "only left and right" --border :: Sides PxRem -> Mod c -- | Set a border color. See ToColor borderColor :: ToColor clr => clr -> Mod ctx -- | Use a button-like cursor when hovering over the element -- -- Button-like elements: -- --
-- btn = pointer . bg Primary . hover (bg PrimaryLight) -- -- options = row id $ do -- el btn "Login" -- el btn "Sign Up" --pointer :: Mod c -- | Animate changes to the given property -- --
-- el (transition 100 (Height 400)) "Tall" -- el (transition 100 (Height 100)) "Small" --transition :: Ms -> TransitionProperty -> Mod c textAlign :: Align -> Mod c zIndex :: Int -> Mod c -- | Set container display -- -- el (display None) HIDDEN display :: (Style Display a, ToClassName a) => a -> Mod c -- | Apply when the mouse is pressed down on an element active :: Mod c -> Mod c -- | Apply when the element is somewhere inside an anscestor. -- -- For example, the HTMX library applies an "htmx-request" class to the -- body when a request is pending. We can use this to create a loading -- indicator -- --
-- el (pad 10) $ do -- el (parent "htmx-request" flexRow . hide) "Loading..." -- el (parent "htmx-request" hide . flexRow) "Normal Content" --parent :: Text -> Mod c -> Mod c pathUrl :: [Segment] -> Url cleanSegment :: Segment -> Segment pathSegments :: Text -> [Segment] url :: Text -> Url renderUrl :: Url -> Text renderPath :: [Segment] -> Text -- | Views have a Reader built-in for convienient access to static -- data, and to add type-safety to view functions. See -- 'Web.View.Element.ListItem and -- https://hackage.haskell.org/package/hyperbole/docs/Web-Hyperbole.html -- --
-- numberView :: View Int () -- numberView = do -- num <- context -- el_ $ do -- "Number: " -- text (pack $ show num) --context :: View context context -- | Run a view with a specific context in a parent View with -- a different context. -- --
-- parentView :: View c () -- parentView = do -- addContext 1 numberView -- addContext 2 numberView -- addContext 3 numberView --addContext :: context -> View context () -> View c () -- | Create a new element constructor with the given tag name -- --
-- aside :: Mod c -> View c () -> View c () -- aside = tag "aside" --tag :: Text -> Mod c -> View c () -> View c () -- | Set an attribute, replacing existing value -- --
-- hlink :: Text -> View c () -> View c () -- hlink url content = tag "a" (att "href" url) content --att :: Name -> AttValue -> Mod c -- | Renders a View as HTML with embedded CSS class definitions -- --
-- >>> renderText $ el bold "Hello"
-- <style type='text/css'>.bold { font-weight:bold }</style>
-- <div class='bold'>Hello</div>
--
renderText :: View () () -> Text
renderLazyText :: View () () -> Text
renderLazyByteString :: View () () -> ByteString
data TableColumn c dt
data TableHead a
-- | A basic element
--
-- -- el (bold . pad 10) "Hello" --el :: Mod c -> View c () -> View c () -- | A basic element, with no modifiers -- --
-- el_ "Hello" --el_ :: View c () -> View c () -- | Do not show any content -- --
-- if isVisible -- then content -- else none --none :: View c () pre :: Mod c -> Text -> View c () code :: Mod c -> Text -> View c () script :: Text -> View c () stylesheet :: Text -> View c () tcol :: forall dt c. View (TableHead c) () -> (dt -> View dt ()) -> Eff '[Writer [TableColumn c dt]] () th :: Mod c -> View c () -> View (TableHead c) () td :: Mod () -> View () () -> View dt () -- | List elements do not include any inherent styling but are useful for -- accessibility. See list. -- --
-- ol id $ do -- let nums = list Decimal -- li nums "one" -- li nums "two" -- li nums "three" --ol :: Mod c -> ListItem c () -> View c () ul :: Mod c -> ListItem c () -> View c () li :: Mod c -> View c () -> ListItem c () data Layer c a -- | As layout but as a Mod -- --
-- holygrail = col root $ do -- ... --root :: Mod c -- | Grow to fill the available space in the parent row or -- col -- --
-- row id $ do -- el grow none -- el_ "Right" --grow :: Mod c -- | Make a fixed layout by putting scroll on a child-element -- --
-- document = row root $ do -- nav (width 300) "Sidebar" -- col (grow . scroll) "Main Content" --scroll :: Mod c -- | A Nav element nav :: Mod c -> View c () -> View c () -- | A normal layer contributes to the size of the parent. See stack layer :: Mod c -> View c () -> Layer c () -- | Hide an element. See display hide :: Mod c -- | Set container to be a row. Favor row when possible flexRow :: Mod c -- | Set container to be a column. Favor col when possible flexCol :: Mod c module Web.Hyperbole.View.Forms -- | The only time we can use Fields is inside a form data FormFields id FormFields :: id -> FormFields id -- | Choose one for inputs to give the browser autocomplete hints data InputType NewPassword :: InputType CurrentPassword :: InputType Username :: InputType Email :: InputType Number :: InputType TextInput :: InputType Name :: InputType OneTimeCode :: InputType Organization :: InputType StreetAddress :: InputType Country :: InputType CountryName :: InputType PostalCode :: InputType Search :: InputType data FieldName (a :: k) data Invalid (a :: k) data Input id (valid :: Type -> Type) a Input :: FieldName a -> valid a -> Input id (valid :: Type -> Type) a [inputName] :: Input id (valid :: Type -> Type) a -> FieldName a [valid] :: Input id (valid :: Type -> Type) a -> valid a -- | Display a FormField. See form and Form field :: forall id v a. FormField v a -> (v a -> Mod (FormFields id)) -> View (Input id v a) () -> View (FormFields id) () -- | label for a field label :: forall id (v :: Type -> Type) a. Text -> View (Input id v a) () -- | input for a field input :: forall id (v :: Type -> Type) a. InputType -> Mod (Input id v a) -> View (Input id v a) () -- | Type-safe <form>. Calls (Action id) on submit -- --
-- formView :: View FormView () -- formView = do -- -- create formfields for our form -- let f = formFields ContactForm -- form ContactForm Submit (gap 10 . pad 10) $ do -- el Style.h1 "Add Contact" -- -- -- pass the form field into the field function -- field f.name (const id) $ do -- label "Contact Name" -- input Username (inp . placeholder "contact name") -- -- field f.age (const id) $ do -- label "Age" -- input Number (inp . placeholder "age" . value "0") -- -- submit Style.btn "Submit" -- where -- inp = Style.input --form :: forall (form :: (Type -> Type) -> Type) (v :: Type -> Type) id. (Form form v, ViewAction (Action id)) => Action id -> Mod id -> View (FormFields id) () -> View id () -- | textarea for a field textarea :: forall id (v :: Type -> Type) a. Mod (Input id v a) -> Maybe Text -> View (Input id v a) () placeholder :: Text -> Mod id -- | Button that submits the form. Use button to specify -- actions other than submit submit :: Mod (FormFields id) -> View (FormFields id) () -> View (FormFields id) () formData :: forall form (val :: Type -> Type) (es :: [Effect]). (Form form val, Hyperbole :> es) => Eff es (form Identity) -- | A Form is a Higher Kinded record listing each Field. -- ContactForm Identity behaves like a normal record, -- while ContactForm Maybe would be maybe values for each -- field -- -- From Example.Page.FormSimple -- --
-- data ContactForm f = ExampleForm
-- { name :: Field f Text
-- , age :: Field f Int
-- }
-- deriving (Generic)
-- instance Form ContactForm Maybe
--
class Form (form :: Type -> Type -> Type) (val :: Type -> Type) | form -> val
formParse :: Form form val => Form -> Either Text (form Identity)
($dmformParse) :: (Form form val, Generic (form Identity), GFormParse (Rep (form Identity))) => Form -> Either Text (form Identity)
collectValids :: Form form val => form val -> [val ()]
($dmcollectValids) :: (Form form val, Generic (form val), GCollect (Rep (form val)) val) => form val -> [val ()]
genForm :: Form form val => form val
($dmgenForm) :: (Form form val, Generic (form val), GenFields (Rep (form val))) => form val
genFieldsWith :: Form form val => form val -> form (FormField val)
($dmgenFieldsWith) :: (Form form val, Generic (form val), Generic (form (FormField val)), GConvert (Rep (form val)) (Rep (form (FormField val)))) => form val -> form (FormField val)
formParseParam :: FromParam a => Param -> Form -> Either Text a
formLookupParam :: FromParam a => Param -> Form -> Either Text (Maybe a)
-- | Generate FormFields for the given instance of Form, with no
-- validation information. See Example.Page.FormSimple
--
--
-- data ContactForm f = ExampleForm
-- { name :: Field f Text
-- , age :: Field f Int
-- }
-- deriving (Generic)
--
-- formView :: View FormView ()
-- formView = do
-- -- create formfields for our form
-- let f = formFields @ContactForm
-- form @ContactForm Submit (gap 10 . pad 10) $ do
-- el Style.h1 "Add Contact"
--
-- -- pass the form field into the 'field' function
-- field f.name (const id) $ do
-- label "Contact Name"
-- input Username (inp . placeholder "contact name")
--
-- field f.age (const id) $ do
-- label "Age"
-- input Number (inp . placeholder "age" . value "0")
--
-- submit Style.btn "Submit"
-- where
-- inp = Style.input
--
formFields :: forall form (val :: Type -> Type). Form form val => form (FormField val)
-- | Generate FormFields for the given instance of Form from
-- validation data. See Example.Page.FormValidation
--
--
-- data UserForm f = UserForm
-- { user :: Field f User
-- , age :: Field f Int
-- , pass1 :: Field f Text
-- , pass2 :: Field f Text
-- }
-- deriving (Generic)
-- instance Form UserForm Validated
--
-- formView :: UserForm Validated -> View FormView ()
-- formView v = do
-- let f = formFieldsWith v
-- form @UserForm Submit (gap 10 . pad 10) $ do
-- el Style.h1 "Sign Up"
--
-- field f.user valStyle $ do
-- label "Username"
-- input Username (inp . placeholder "username")
--
-- fv <- fieldValid
-- case fv of
-- Invalid t -> el_ (text t)
-- Valid -> el_ "Username is available"
-- _ -> none
--
-- field f.age valStyle $ do
-- label "Age"
-- input Number (inp . placeholder "age" . value "0")
-- el_ invalidText
--
-- field f.pass1 valStyle $ do
-- label "Password"
-- input NewPassword (inp . placeholder "password")
-- el_ invalidText
--
-- field f.pass2 (const id) $ do
-- label "Repeat Password"
-- input NewPassword (inp . placeholder "repeat password")
--
-- submit Style.btn "Submit"
-- where
-- inp = Style.input
-- valStyle (Invalid _) = Style.invalid
-- valStyle Valid = Style.success
-- valStyle _ = id
--
formFieldsWith :: forall form (val :: Type -> Type). Form form val => form val -> form (FormField val)
-- | Field allows a Higher Kinded Form to reuse the same selectors
-- for form parsing, generating html forms, and validation
--
-- -- Field Identity Text ~ Text -- Field Maybe Text ~ Maybe Text --type family Field (context :: Type -> Type) a -- | Default encoding FormOptions. -- --
-- FormOptions
-- { fieldLabelModifier = id
-- }
--
defaultFormOptions :: FormOptions
-- | Generic-based deriving options for ToForm and
-- FromForm.
--
-- A common use case for non-default FormOptions is to strip a
-- prefix off of field labels:
--
--
-- data Project = Project
-- { projectName :: String
-- , projectSize :: Int
-- } deriving (Generic, Show)
--
-- myOptions :: FormOptions
-- myOptions = FormOptions
-- { fieldLabelModifier = map toLower . drop (length "project") }
--
-- instance ToForm Project where
-- toForm = genericToForm myOptions
--
-- instance FromForm Project where
-- fromForm = genericFromForm myOptions
--
--
--
-- >>> urlEncodeAsFormStable Project { projectName = "http-api-data", projectSize = 172 }
-- "name=http-api-data&size=172"
--
-- >>> urlDecodeAsForm "name=http-api-data&size=172" :: Either Text Project
-- Right (Project {projectName = "http-api-data", projectSize = 172})
--
data FormOptions
FormOptions :: (String -> String) -> FormOptions
-- | Function applied to field labels. Handy for removing common record
-- prefixes for example.
[fieldLabelModifier] :: FormOptions -> String -> String
-- | Validation results for a Form. See validate
--
--
-- data UserForm f = UserForm
-- { user :: Field f User
-- , age :: Field f Int
-- , pass1 :: Field f Text
-- , pass2 :: Field f Text
-- }
-- deriving (Generic)
-- instance Form UserForm Validated
--
-- validateForm :: UserForm Identity -> UserForm Validated
-- validateForm u =
-- UserForm
-- { user = validateUser u.user
-- , age = validateAge u.age
-- , pass1 = validatePass u.pass1 u.pass2
-- , pass2 = NotInvalid
-- }
--
-- validateAge :: Int -> Validated Int
-- validateAge a =
-- validate (a < 20) "User must be at least 20 years old"
--
data Validated (a :: k)
Invalid :: Text -> Validated (a :: k)
NotInvalid :: Validated (a :: k)
Valid :: Validated (a :: k)
data FormField (v :: k -> Type) (a :: k)
FormField :: FieldName a -> v a -> FormField (v :: k -> Type) (a :: k)
[fieldName] :: FormField (v :: k -> Type) (a :: k) -> FieldName a
[validated] :: FormField (v :: k -> Type) (a :: k) -> v a
-- | Returns the Validated for the field. See
-- formFieldsWith
fieldValid :: View (Input id v a) (v a)
anyInvalid :: forall form (val :: Type -> Type). (Form form val, ValidationState val) => form val -> Bool
invalidText :: forall a id. View (Input id (Validated :: Type -> Type) a) ()
-- | specify a check for a Validation
--
-- -- validateAge :: Int -> Validated Int -- validateAge a = -- validate (a < 20) "User must be at least 20 years old" --validate :: forall {k} (a :: k). Bool -> Text -> Validated a -- | Identity functor and monad. (a non-strict monad) data Identity a -- | Decode data from a query, session, or form -- parameter value -- --
-- data Todo = Todo
-- { id :: TodoId
-- , task :: Text
-- , completed :: Bool
-- }
-- deriving (Show, Read, ToParam, FromParam)
--
--
-- -- data Tags = Tags [Text] -- -- instance FromParam Tags where -- parseParam (ParamValue t) = -- pure $ Tags $ Text.splitOn "," t --class FromParam a -- | Representable types of kind *. This class is derivable in GHC -- with the DeriveGeneric flag on. -- -- A Generic instance must satisfy the following laws: -- --
-- from . to ≡ id -- to . from ≡ id --class Generic a class GenFields (f :: k -> Type) gGenFields :: forall (p :: k). GenFields f => f p class GenField (f :: Type -> Type) a genField :: GenField f a => String -> Field f a instance forall k (f :: k -> *) (v :: * -> *) (g :: k -> *). (Web.Hyperbole.View.Forms.GCollect f v, Web.Hyperbole.View.Forms.GCollect g v) => Web.Hyperbole.View.Forms.GCollect (f GHC.Generics.:*: g) v instance (GHC.Generics.Selector s, Web.Hyperbole.View.Forms.ValidationState v) => Web.Hyperbole.View.Forms.GCollect (GHC.Generics.M1 GHC.Generics.S s (GHC.Generics.K1 GHC.Generics.R (v a))) v instance forall k (f :: k -> *) (v :: * -> *) (d :: GHC.Generics.Meta). Web.Hyperbole.View.Forms.GCollect f v => Web.Hyperbole.View.Forms.GCollect (GHC.Generics.M1 GHC.Generics.D d f) v instance forall k (f :: k -> *) (v :: * -> *) (c :: GHC.Generics.Meta). Web.Hyperbole.View.Forms.GCollect f v => Web.Hyperbole.View.Forms.GCollect (GHC.Generics.M1 GHC.Generics.C c f) v instance Web.Hyperbole.View.Forms.GCollect GHC.Generics.U1 v instance forall k (ra0 :: k -> *) (rc0 :: k -> *) (ra1 :: k -> *) (rc1 :: k -> *). (Web.Hyperbole.View.Forms.GConvert ra0 rc0, Web.Hyperbole.View.Forms.GConvert ra1 rc1) => Web.Hyperbole.View.Forms.GConvert (ra0 GHC.Generics.:*: ra1) (rc0 GHC.Generics.:*: rc1) instance (GHC.Generics.Selector s, Web.Hyperbole.View.Forms.GenFieldFrom f g a, Web.Hyperbole.View.Forms.Field g a GHC.Types.~ g a) => Web.Hyperbole.View.Forms.GConvert (GHC.Generics.M1 GHC.Generics.S s (GHC.Generics.K1 GHC.Generics.R (f a))) (GHC.Generics.M1 GHC.Generics.S s (GHC.Generics.K1 GHC.Generics.R (g a))) instance forall k (ra :: k -> *) (rc :: k -> *) (d :: GHC.Generics.Meta). Web.Hyperbole.View.Forms.GConvert ra rc => Web.Hyperbole.View.Forms.GConvert (GHC.Generics.M1 GHC.Generics.D d ra) (GHC.Generics.M1 GHC.Generics.D d rc) instance forall k (ra :: k -> *) (rc :: k -> *) (d :: GHC.Generics.Meta). Web.Hyperbole.View.Forms.GConvert ra rc => Web.Hyperbole.View.Forms.GConvert (GHC.Generics.M1 GHC.Generics.C d ra) (GHC.Generics.M1 GHC.Generics.C d rc) instance forall k (f :: k -> *) (g :: k -> *). (Web.Hyperbole.View.Forms.GFormParse f, Web.Hyperbole.View.Forms.GFormParse g) => Web.Hyperbole.View.Forms.GFormParse (f GHC.Generics.:*: g) instance forall k (f :: k -> *) (d :: GHC.Generics.Meta). Web.Hyperbole.View.Forms.GFormParse f => Web.Hyperbole.View.Forms.GFormParse (GHC.Generics.M1 GHC.Generics.D d f) instance forall k (f :: k -> *) (c :: GHC.Generics.Meta). Web.Hyperbole.View.Forms.GFormParse f => Web.Hyperbole.View.Forms.GFormParse (GHC.Generics.M1 GHC.Generics.C c f) instance (GHC.Generics.Selector s, Web.Hyperbole.Data.QueryData.FromParam a) => Web.Hyperbole.View.Forms.GFormParse (GHC.Generics.M1 GHC.Generics.S s (GHC.Generics.K1 GHC.Generics.R a)) instance forall k (ra0 :: k -> *) (rb0 :: k -> *) (rc0 :: k -> *) (ra1 :: k -> *) (rb1 :: k -> *) (rc1 :: k -> *). (Web.Hyperbole.View.Forms.GMerge ra0 rb0 rc0, Web.Hyperbole.View.Forms.GMerge ra1 rb1 rc1) => Web.Hyperbole.View.Forms.GMerge (ra0 GHC.Generics.:*: ra1) (rb0 GHC.Generics.:*: rb1) (rc0 GHC.Generics.:*: rc1) instance (GHC.Generics.Selector s, Web.Hyperbole.View.Forms.MergeField a b c) => Web.Hyperbole.View.Forms.GMerge (GHC.Generics.M1 GHC.Generics.S s (GHC.Generics.K1 GHC.Generics.R a)) (GHC.Generics.M1 GHC.Generics.S s (GHC.Generics.K1 GHC.Generics.R b)) (GHC.Generics.M1 GHC.Generics.S s (GHC.Generics.K1 GHC.Generics.R c)) instance forall k (ra :: k -> *) (rb :: k -> *) (rc :: k -> *) (d :: GHC.Generics.Meta). Web.Hyperbole.View.Forms.GMerge ra rb rc => Web.Hyperbole.View.Forms.GMerge (GHC.Generics.M1 GHC.Generics.D d ra) (GHC.Generics.M1 GHC.Generics.D d rb) (GHC.Generics.M1 GHC.Generics.D d rc) instance forall k (ra :: k -> *) (rb :: k -> *) (rc :: k -> *) (d :: GHC.Generics.Meta). Web.Hyperbole.View.Forms.GMerge ra rb rc => Web.Hyperbole.View.Forms.GMerge (GHC.Generics.M1 GHC.Generics.C d ra) (GHC.Generics.M1 GHC.Generics.C d rb) (GHC.Generics.M1 GHC.Generics.C d rc) instance Web.Hyperbole.View.Forms.GenFieldFrom val (Web.Hyperbole.View.Forms.FormField val) a instance Web.Hyperbole.View.Forms.GenField Web.Hyperbole.View.Forms.FieldName a instance Web.Hyperbole.View.Forms.GenField (Web.Hyperbole.View.Forms.FormField Web.Hyperbole.View.Forms.Validated) a instance Web.Hyperbole.View.Forms.GenField (Web.Hyperbole.View.Forms.FormField GHC.Maybe.Maybe) a instance Web.Hyperbole.View.Forms.GenField GHC.Maybe.Maybe a instance Web.Hyperbole.View.Forms.GenField Web.Hyperbole.View.Forms.Validated a instance forall k (f :: k -> *) (g :: k -> *). (Web.Hyperbole.View.Forms.GenFields f, Web.Hyperbole.View.Forms.GenFields g) => Web.Hyperbole.View.Forms.GenFields (f GHC.Generics.:*: g) instance (GHC.Generics.Selector s, Web.Hyperbole.View.Forms.GenField f a, Web.Hyperbole.View.Forms.Field f a GHC.Types.~ f a) => Web.Hyperbole.View.Forms.GenFields (GHC.Generics.M1 GHC.Generics.S s (GHC.Generics.K1 GHC.Generics.R (f a))) instance forall k (f :: k -> *) (d :: GHC.Generics.Meta). Web.Hyperbole.View.Forms.GenFields f => Web.Hyperbole.View.Forms.GenFields (GHC.Generics.M1 GHC.Generics.D d f) instance forall k (f :: k -> *) (c :: GHC.Generics.Meta). Web.Hyperbole.View.Forms.GenFields f => Web.Hyperbole.View.Forms.GenFields (GHC.Generics.M1 GHC.Generics.C c f) instance Web.Hyperbole.View.Forms.GenFields GHC.Generics.U1 instance forall k (a :: k). Web.Hyperbole.View.Forms.MergeField (Web.Hyperbole.View.Forms.FieldName a) (Web.Hyperbole.View.Forms.Validated a) (Web.Hyperbole.View.Forms.FormField Web.Hyperbole.View.Forms.Validated a) instance forall k (a :: k). GHC.Base.Monoid (Web.Hyperbole.View.Forms.Validated a) instance forall k (a :: k). GHC.Base.Semigroup (Web.Hyperbole.View.Forms.Validated a) instance forall k (a :: k). GHC.Show.Show (Web.Hyperbole.View.Forms.FieldName a) instance forall k (v :: k -> *) (a :: k). GHC.Show.Show (v a) => GHC.Show.Show (Web.Hyperbole.View.Forms.FormField v a) instance GHC.Show.Show Web.Hyperbole.View.Forms.InputType instance forall k (a :: k). GHC.Show.Show (Web.Hyperbole.View.Forms.Validated a) instance Web.Hyperbole.View.Forms.ValidationState Web.Hyperbole.View.Forms.Validated -- | Create fully interactive HTML applications with type-safe serverside -- Haskell. Inspired by HTMX, Elm, and Phoenix -- LiveView module Web.Hyperbole -- | Turn one or more Pages into a Wai Application. Respond using -- both HTTP and WebSockets -- --
-- main :: IO () -- main = do -- run 3000 $ do -- liveApp (basicDocument "Example") (runPage page) --liveApp :: (ByteString -> ByteString) -> Eff '[Hyperbole, Server, Concurrent, IOE] Response -> Application -- | Run an Application on the given port. This calls -- runSettings with defaultSettings. run :: Port -> Application -> IO () -- | wrap HTML fragments in a simple document with a custom title and -- include required embeds -- --
-- liveApp (basicDocument "App Title") (routeRequest router) ---- -- You may want to specify a custom document function to import custom -- javascript, css, or add other information to the <head> -- --
-- import Data.String.Interpolate (i)
-- import Web.Hyperbole (scriptEmbed, cssResetEmbed)
--
-- customDocument :: ByteString -> ByteString
-- customDocument content =
-- [i|<html>
-- <head>
-- <title>My Website</title>
-- <script type="text/javascript">#{scriptEmbed}</script>
-- <style type="text/css">#{cssResetEmbed}</style>
-- <script type="text/javascript" src="custom.js"></script>
-- </head>
-- <body>#{content}</body>
-- </html>|]
--
basicDocument :: Text -> ByteString -> ByteString
-- | Conceptually, an application is dividied up into multiple
-- Pages. Each page module should have a function that returns a
-- Page. The Page itself is a View with a type-level
-- list of HyperViews used on the page.
--
-- -- page :: Eff es (Page [Message, Count]) -- page = do -- pure $ do -- row id $ do -- hyper Message $ messageView "Hello" -- hyper Count $ countView 0 --type Page (views :: [Type]) = View Root views () -- | Run a Page and return a Response -- --
-- main :: IO () -- main = do -- run 3000 $ do -- liveApp (basicDocument "Example") (runPage page) -- -- page :: Eff es (Page '[]) -- page = do -- pure $ do -- col (pad 10) $ do -- el bold "Hello World" --runPage :: forall (es :: [Effect]) (views :: [Type]). (Hyperbole :> es, RunHandlers views es) => Eff es (Page views) -> Eff es Response -- | Route URL patterns to different pages -- --
-- import Example.Docs.Page.Messages qualified as Messages -- import Example.Docs.Page.Users qualified as Users -- -- type UserId = Int -- -- data AppRoute -- = Main -- | Messages -- | User UserId -- deriving (Eq, Generic) -- -- instance Route AppRoute where -- baseRoute = Just Main -- -- router :: (Hyperbole :> es) => AppRoute -> Eff es Response -- router Messages = runPage Messages.page -- router (User cid) = runPage $ Users.page cid -- router Main = do -- view $ do -- el_ "click a link below to visit a page" -- route Messages id "Messages" -- route (User 1) id "User 1" -- route (User 2) id "User 2" --routeRequest :: forall (es :: [Effect]) route. (Hyperbole :> es, Route route) => (route -> Eff es Response) -> Eff es Response -- | Derive this class to use a sum type as a route. Constructors and -- Selectors map intuitively to url patterns -- --
-- data AppRoute -- = Main -- | Messages -- | User UserId -- deriving (Eq, Generic) -- -- instance Route AppRoute where -- baseRoute = Just Main ---- --
-- >>> routeUrl Main -- / ---- --
-- >>> routeUrl (User 9) -- /user/9 --class Route a -- | The route to use if attempting to match on empty segments baseRoute :: Route a => Maybe a ($dmbaseRoute) :: (Route a, Generic a, GenRoute (Rep a)) => Maybe a -- | Try to match segments to a route matchRoute :: Route a => [Segment] -> Maybe a ($dmmatchRoute) :: (Route a, Generic a, GenRoute (Rep a)) => [Segment] -> Maybe a -- | Map a route to segments routePath :: Route a => a -> [Segment] ($dmroutePath) :: (Route a, Generic a, Eq a, GenRoute (Rep a)) => a -> [Segment] -- | Convert a Route to a Url -- --
-- >>> routeUrl (User 100) -- /user/100 --routeUrl :: Route a => a -> Url -- | A hyperlink to another route -- --
-- >>> route (User 100) id "View User" -- <a href="/user/100">View User</a> --route :: Route a => a -> Mod c -> View c () -> View c () -- | The Hyperbole Effect allows you to access information in -- the Request, manually respondEarly, and manipulate the -- Client session and query. data Hyperbole (a :: Type -> Type) b -- | Respond with the given view, and stop execution respondEarly :: forall (es :: [Effect]) id. (Hyperbole :> es, HyperView id es) => id -> View id () -> Eff es () -- | Respond immediately with 404 Not Found -- --
-- findUser :: (Hyperbole :> es, Users :> es) => Int -> Eff es User -- findUser uid = do -- mu <- send (LoadUser uid) -- maybe notFound pure mu -- -- userPage :: (Hyperbole :> es, Users :> es) => Eff es (Page '[]) -- userPage = do -- user <- findUser 100 -- -- -- skipped if user not found -- pure $ userView user --notFound :: forall (es :: [Effect]) a. Hyperbole :> es => Eff es a -- | Redirect immediately to the Url redirect :: forall (es :: [Effect]) a. Hyperbole :> es => Url -> Eff es a -- | Return all information about the Request request :: forall (es :: [Effect]). Hyperbole :> es => Eff es Request data Request Request :: Host -> [Segment] -> Query -> ByteString -> Method -> Cookies -> Request [host] :: Request -> Host [path] :: Request -> [Segment] [query] :: Request -> Query [body] :: Request -> ByteString [method] :: Request -> Method [cookies] :: Request -> Cookies -- | Parse querystring from the Request into a datatype. See -- FromQuery -- --
-- data Filters = Filters
-- { active :: Bool
-- , term :: Text
-- }
-- deriving (Generic, FromQuery, ToQuery)
--
-- page :: (Hyperbole :> es) => Eff es (Page '[Todos])
-- page = do
-- filters <- query @Filters
-- todos <- loadTodos filters
-- pure $ do
-- hyper Todos $ todosView todos
--
query :: forall a (es :: [Effect]). (FromQuery a, Hyperbole :> es) => Eff es a
-- | Update the client's querystring to an encoded datatype. See
-- ToQuery
--
-- -- instance HyperView Todos es where -- data Action Todos -- = SetFilters Filters -- deriving (Show, Read, ViewAction) -- -- update (SetFilters filters) = do -- setQuery filters -- todos <- loadTodos filters -- pure $ todosView todos --setQuery :: forall a (es :: [Effect]). (ToQuery a, Hyperbole :> es) => a -> Eff es () -- | Parse a single query parameter. Return a 400 status if missing or if -- parsing fails. See FromParam -- --
-- page' :: (Hyperbole :> es) => Eff es (Page '[Message]) -- page' = do -- msg <- param "message" -- pure $ do -- hyper Message $ messageView msg --param :: forall a (es :: [Effect]). (FromParam a, Hyperbole :> es) => Param -> Eff es a -- | Parse a single parameter from the query string if available lookupParam :: forall a (es :: [Effect]). (FromParam a, Hyperbole :> es) => Param -> Eff es (Maybe a) -- | Modify the client's querystring to set a single parameter. See -- ToParam -- --
-- instance HyperView Message es where -- data Action Message -- = SetMessage Text -- deriving (Show, Read, ViewAction) -- -- update (SetMessage msg) = do -- setParam "message" msg -- pure $ messageView msg --setParam :: forall a (es :: [Effect]). (ToParam a, Hyperbole :> es) => Param -> a -> Eff es () -- | Delete a single parameter from the query string deleteParam :: forall (es :: [Effect]). Hyperbole :> es => Param -> Eff es () -- | Return the query from Request as a QueryData queryParams :: forall (es :: [Effect]). Hyperbole :> es => Eff es QueryData -- | Persist datatypes in browser cookies. If the session doesn't exist, -- the DefaultParam is used -- --
-- data Preferences = Preferences
-- { color :: AppColor
-- }
-- deriving (Generic, Show, Read, ToParam, FromParam, Session)
--
-- instance DefaultParam Preferences where
-- defaultParam = Preferences White
--
-- page :: (Hyperbole :> es) => Eff es (Page '[Content])
-- page = do
-- prefs <- session @Preferences
-- pure $ el (bg prefs.color) "Custom Background"
--
session :: forall a (es :: [Effect]). (Session a, DefaultParam a, FromParam a, Hyperbole :> es) => Eff es a
-- | Persist datatypes in browser cookies
--
--
-- data Preferences = Preferences
-- { color :: AppColor
-- }
-- deriving (Generic, Show, Read, ToParam, FromParam, Session)
--
-- instance DefaultParam Preferences where
-- defaultParam = Preferences White
--
-- instance HyperView Content es where
-- data Action Content
-- = SetColor AppColor
-- deriving (Show, Read, ViewAction)
--
-- update (SetColor clr) = do
-- let prefs = Preferences clr
-- saveSession prefs
-- pure $ el (bg prefs.color) "Custom Background"
--
saveSession :: forall a (es :: [Effect]). (Session a, ToParam a, Hyperbole :> es) => a -> Eff es ()
-- | Return a session if it exists
lookupSession :: forall a (es :: [Effect]). (Session a, FromParam a, Hyperbole :> es) => Eff es (Maybe a)
modifySession :: forall a (es :: [Effect]). (Session a, DefaultParam a, ToParam a, FromParam a, Hyperbole :> es) => (a -> a) -> Eff es a
modifySession_ :: forall a (es :: [Effect]). (Session a, DefaultParam a, ToParam a, FromParam a, Hyperbole :> es) => (a -> a) -> Eff es ()
-- | Remove a single Session from the browser cookies
deleteSession :: forall a (es :: [Effect]). (Session a, Hyperbole :> es) => Eff es ()
-- | Configure a data type to persist in the session
--
--
-- data Preferences = Preferences
-- { color :: AppColor
-- }
-- deriving (Generic, Show, Read, ToParam, FromParam, Session)
--
-- instance DefaultParam Preferences where
-- defaultParam = Preferences White
--
class Session a
-- | Unique key for the Session. Defaults to the datatypeName
sessionKey :: Session a => Param
($dmsessionKey) :: (Session a, Generic a, GDatatypeName (Rep a)) => Param
-- | By default Sessions are persisted only to the current page. Set this
-- to `Just []` to make an application-wide Session
cookiePath :: Session a => Maybe [Segment]
($dmcookiePath) :: Session a => Maybe [Segment]
-- | HyperViews are interactive subsections of a Page
--
-- Create an instance with a unique view id type and a sum type
-- describing the actions the HyperView supports. The View Id can contain
-- context (a database id, for example)
--
-- -- data Message = Message -- deriving (Show, Read, ViewId) -- -- instance HyperView Message es where -- data Action Message -- = SetMessage Text -- deriving (Show, Read, ViewAction) -- -- update (SetMessage msg) = -- pure $ messageView msg --class (ViewId id, ViewAction Action id) => HyperView id (es :: [Effect]) where { -- | Outline all actions that are permitted in this HyperView -- --
-- data Action Message = SetMessage Text | ClearMessage
-- deriving (Show, Read, ViewAction)
--
data Action id;
-- | Include any child hyperviews here. The compiler will make sure that
-- the page knows how to handle them
--
--
-- type Require = '[ChildView]
--
type Require id :: [Type];
type Require id = '[] :: [Type];
}
-- | Specify how the view should be updated for each Action
--
-- -- update (SetMessage msg) = pure $ messageView msg -- update ClearMessage = pure $ messageView "" --update :: HyperView id es => Action id -> Eff (Reader id ': es) (View id ()) -- | Embed a HyperView into another View -- --
-- page :: Eff es (Page '[Message]) -- page = do -- pure $ do -- col (pad 10 . gap 10) $ do -- el (bold . fontSize 24) "Unchanging Header" -- hyper Message $ messageView "Hello World" --hyper :: (HyperViewHandled id ctx, ViewId id) => id -> View id () -> View ctx () -- | Access the viewId in a View or update -- --
-- data Contact = Contact UserId -- deriving (Show, Read, ViewId) -- -- instance (Users :> es, Debug :> es) => HyperView Contact es where -- data Action Contact -- = Edit -- | Save -- | View -- deriving (Show, Read, ViewAction) -- -- update action = do -- -- No matter which action we are performing, let's look up the user to make sure it exists -- Contact uid <- viewId -- u <- Users.find uid -- case action of -- View -> do -- pure $ contactView u -- Edit -> do -- pure $ contactEditView u -- Save -> do -- delay 1000 -- unew <- parseUser uid -- Users.save unew -- pure $ contactView unew --class HasViewId (m :: k -> Type) (view :: k) viewId :: HasViewId m view => m view -- | <button> HTML tag which sends the action when pressed -- --
-- button SomeAction (border 1) "Click Me" --button :: ViewAction (Action id) => Action id -> Mod id -> View id () -> View id () -- | A live search field search :: ViewAction (Action id) => (Text -> Action id) -> DelayMs -> Mod id -> View id () -- | Type-safe dropdown. Sends (opt -> Action id) when selected. The -- selection predicate (opt -> Bool) controls which option is -- selected. See Example.Page.Filter -- --
-- familyDropdown :: Filters -> View Languages () -- familyDropdown filters = -- dropdown SetFamily (== filters.family) (border 1 . pad 10) $ do -- option Nothing "Any" -- option (Just ObjectOriented) "Object Oriented" -- option (Just Functional) "Functional" --dropdown :: ViewAction (Action id) => (opt -> Action id) -> (opt -> Bool) -> Mod id -> View (Option opt id (Action id)) () -> View id () -- | An option for a dropdown. First argument is passed to (opt -- -> Action id) in the dropdown, and to the selected predicate option :: (ViewAction (Action id), Eq opt) => opt -> View (Option opt id (Action id)) () -> View (Option opt id (Action id)) () -- | The view context for an option data Option opt (id :: k) action onClick :: ViewAction (Action id) => Action id -> Mod id onDblClick :: ViewAction (Action id) => Action id -> Mod id -- | Run an action when the user types into an input or -- textarea. -- -- WARNING: a short delay can result in poor performance. It is not -- recommended to set the value of the input -- --
-- input (onInput OnSearch) 250 id --onInput :: ViewAction (Action id) => (Text -> Action id) -> DelayMs -> Mod id onKeyDown :: ViewAction (Action id) => Key -> Action id -> Mod id onKeyUp :: ViewAction (Action id) => Key -> Action id -> Mod id -- | Send the action after N milliseconds. Can be used to implement lazy -- loading or polling. See Example.Page.Concurrent -- --
-- viewUpdating :: Int -> View Progress () -- viewUpdating prg = do -- let pct = fromIntegral prg / 100 -- Progress taskId _ <- viewId -- col (onLoad (CheckProgress prg) 0) $ do -- progressBar pct $ do -- el grow $ text $ "Task" <> pack (show taskId) --onLoad :: ViewAction (Action id) => Action id -> DelayMs -> Mod id -- | Apply a Mod only when a request is in flight. See -- Example.Page.Contact -- --
-- contactEditView :: User -> View Contact () -- contactEditView u = do -- el (hide . onRequest flexCol) contactLoading -- el (onRequest hide) $ contactEdit View Save u --onRequest :: Mod id -> Mod id data Key ArrowDown :: Key ArrowUp :: Key ArrowLeft :: Key ArrowRight :: Key Enter :: Key Space :: Key Escape :: Key Alt :: Key CapsLock :: Key Control :: Key Fn :: Key Meta :: Key Shift :: Key OtherKey :: Text -> Key type DelayMs = Int formData :: forall form (val :: Type -> Type) (es :: [Effect]). (Form form val, Hyperbole :> es) => Eff es (form Identity) -- | A Form is a Higher Kinded record listing each Field. -- ContactForm Identity behaves like a normal record, -- while ContactForm Maybe would be maybe values for each -- field -- -- From Example.Page.FormSimple -- --
-- data ContactForm f = ExampleForm
-- { name :: Field f Text
-- , age :: Field f Int
-- }
-- deriving (Generic)
-- instance Form ContactForm Maybe
--
class Form (form :: Type -> Type -> Type) (val :: Type -> Type) | form -> val
formParse :: Form form val => Form -> Either Text (form Identity)
($dmformParse) :: (Form form val, Generic (form Identity), GFormParse (Rep (form Identity))) => Form -> Either Text (form Identity)
collectValids :: Form form val => form val -> [val ()]
($dmcollectValids) :: (Form form val, Generic (form val), GCollect (Rep (form val)) val) => form val -> [val ()]
genForm :: Form form val => form val
($dmgenForm) :: (Form form val, Generic (form val), GenFields (Rep (form val))) => form val
genFieldsWith :: Form form val => form val -> form (FormField val)
($dmgenFieldsWith) :: (Form form val, Generic (form val), Generic (form (FormField val)), GConvert (Rep (form val)) (Rep (form (FormField val)))) => form val -> form (FormField val)
-- | Generate FormFields for the given instance of Form, with no
-- validation information. See Example.Page.FormSimple
--
--
-- data ContactForm f = ExampleForm
-- { name :: Field f Text
-- , age :: Field f Int
-- }
-- deriving (Generic)
--
-- formView :: View FormView ()
-- formView = do
-- -- create formfields for our form
-- let f = formFields @ContactForm
-- form @ContactForm Submit (gap 10 . pad 10) $ do
-- el Style.h1 "Add Contact"
--
-- -- pass the form field into the 'field' function
-- field f.name (const id) $ do
-- label "Contact Name"
-- input Username (inp . placeholder "contact name")
--
-- field f.age (const id) $ do
-- label "Age"
-- input Number (inp . placeholder "age" . value "0")
--
-- submit Style.btn "Submit"
-- where
-- inp = Style.input
--
formFields :: forall form (val :: Type -> Type). Form form val => form (FormField val)
-- | Generate FormFields for the given instance of Form from
-- validation data. See Example.Page.FormValidation
--
--
-- data UserForm f = UserForm
-- { user :: Field f User
-- , age :: Field f Int
-- , pass1 :: Field f Text
-- , pass2 :: Field f Text
-- }
-- deriving (Generic)
-- instance Form UserForm Validated
--
-- formView :: UserForm Validated -> View FormView ()
-- formView v = do
-- let f = formFieldsWith v
-- form @UserForm Submit (gap 10 . pad 10) $ do
-- el Style.h1 "Sign Up"
--
-- field f.user valStyle $ do
-- label "Username"
-- input Username (inp . placeholder "username")
--
-- fv <- fieldValid
-- case fv of
-- Invalid t -> el_ (text t)
-- Valid -> el_ "Username is available"
-- _ -> none
--
-- field f.age valStyle $ do
-- label "Age"
-- input Number (inp . placeholder "age" . value "0")
-- el_ invalidText
--
-- field f.pass1 valStyle $ do
-- label "Password"
-- input NewPassword (inp . placeholder "password")
-- el_ invalidText
--
-- field f.pass2 (const id) $ do
-- label "Repeat Password"
-- input NewPassword (inp . placeholder "repeat password")
--
-- submit Style.btn "Submit"
-- where
-- inp = Style.input
-- valStyle (Invalid _) = Style.invalid
-- valStyle Valid = Style.success
-- valStyle _ = id
--
formFieldsWith :: forall form (val :: Type -> Type). Form form val => form val -> form (FormField val)
data FormField (v :: k -> Type) (a :: k)
-- | Field allows a Higher Kinded Form to reuse the same selectors
-- for form parsing, generating html forms, and validation
--
-- -- Field Identity Text ~ Text -- Field Maybe Text ~ Maybe Text --type family Field (context :: Type -> Type) a -- | Identity functor and monad. (a non-strict monad) data Identity a -- | Type-safe <form>. Calls (Action id) on submit -- --
-- formView :: View FormView () -- formView = do -- -- create formfields for our form -- let f = formFields ContactForm -- form ContactForm Submit (gap 10 . pad 10) $ do -- el Style.h1 "Add Contact" -- -- -- pass the form field into the field function -- field f.name (const id) $ do -- label "Contact Name" -- input Username (inp . placeholder "contact name") -- -- field f.age (const id) $ do -- label "Age" -- input Number (inp . placeholder "age" . value "0") -- -- submit Style.btn "Submit" -- where -- inp = Style.input --form :: forall (form :: (Type -> Type) -> Type) (v :: Type -> Type) id. (Form form v, ViewAction (Action id)) => Action id -> Mod id -> View (FormFields id) () -> View id () -- | Display a FormField. See form and Form field :: forall id v a. FormField v a -> (v a -> Mod (FormFields id)) -> View (Input id v a) () -> View (FormFields id) () -- | label for a field label :: forall id (v :: Type -> Type) a. Text -> View (Input id v a) () -- | input for a field input :: forall id (v :: Type -> Type) a. InputType -> Mod (Input id v a) -> View (Input id v a) () -- | textarea for a field textarea :: forall id (v :: Type -> Type) a. Mod (Input id v a) -> Maybe Text -> View (Input id v a) () -- | Button that submits the form. Use button to specify -- actions other than submit submit :: Mod (FormFields id) -> View (FormFields id) () -> View (FormFields id) () placeholder :: Text -> Mod id -- | Choose one for inputs to give the browser autocomplete hints data InputType NewPassword :: InputType CurrentPassword :: InputType Username :: InputType Email :: InputType Number :: InputType TextInput :: InputType Name :: InputType OneTimeCode :: InputType Organization :: InputType StreetAddress :: InputType Country :: InputType CountryName :: InputType PostalCode :: InputType Search :: InputType -- | Validation results for a Form. See validate -- --
-- data UserForm f = UserForm
-- { user :: Field f User
-- , age :: Field f Int
-- , pass1 :: Field f Text
-- , pass2 :: Field f Text
-- }
-- deriving (Generic)
-- instance Form UserForm Validated
--
-- validateForm :: UserForm Identity -> UserForm Validated
-- validateForm u =
-- UserForm
-- { user = validateUser u.user
-- , age = validateAge u.age
-- , pass1 = validatePass u.pass1 u.pass2
-- , pass2 = NotInvalid
-- }
--
-- validateAge :: Int -> Validated Int
-- validateAge a =
-- validate (a < 20) "User must be at least 20 years old"
--
data Validated (a :: k)
Invalid :: Text -> Validated (a :: k)
NotInvalid :: Validated (a :: k)
Valid :: Validated (a :: k)
-- | specify a check for a Validation
--
-- -- validateAge :: Int -> Validated Int -- validateAge a = -- validate (a < 20) "User must be at least 20 years old" --validate :: forall {k} (a :: k). Bool -> Text -> Validated a -- | Returns the Validated for the field. See -- formFieldsWith fieldValid :: View (Input id v a) (v a) invalidText :: forall a id. View (Input id (Validated :: Type -> Type) a) () anyInvalid :: forall form (val :: Type -> Type). (Form form val, ValidationState val) => form val -> Bool -- | A page can store state in the browser query string. ToQuery -- and FromQuery control how a datatype is encoded to a full query -- string -- --
-- data Filters = Filters
-- { active :: Bool
-- , term :: Text
-- }
-- deriving (Generic, FromQuery, ToQuery)
--
--
-- -- >>> render $ toQuery $ Filter True "asdf" -- "active=true&search=asdf" ---- -- If the value of a field is the same as DefaultParam, it will be -- omitted from the query string -- --
-- >>> render $ toQuery $ Filter True "" -- "active=true" ---- --
-- >>> render $ toQuery $ Filter False "" -- "" --class ToQuery a toQuery :: ToQuery a => a -> QueryData ($dmtoQuery) :: (ToQuery a, Generic a, GToQuery (Rep a)) => a -> QueryData -- | Decode a type from a QueryData. Missing fields are set to -- defaultParam -- --
-- data Filters = Filters
-- { active :: Bool
-- , term :: Text
-- }
-- deriving (Generic, FromQuery, ToQuery)
--
--
-- -- >>> parseQuery $ parse "active=true&search=asdf" -- Right (Filters True "asdf") ---- --
-- >>> parseQuery $ parse "search=asdf" -- Right (Filters False "asdf") --class FromQuery a parseQuery :: FromQuery a => QueryData -> Either Text a ($dmparseQuery) :: (FromQuery a, Generic a, GFromQuery (Rep a)) => QueryData -> Either Text a -- | sessions, forms, and querys all encode data -- as query strings. ToParam and FromParam control how a datatype is -- encoded to a parameter. By default it simply url-encodes the show -- instance. -- --
-- data Todo = Todo
-- { id :: TodoId
-- , task :: Text
-- , completed :: Bool
-- }
-- deriving (Show, Read, ToParam, FromParam)
--
--
-- -- data Tags = Tags [Text] -- -- instance ToParam Tags where -- toParam (Tags ts) = ParamValue $ Text.intercalate "," ts --class ToParam a toParam :: ToParam a => a -> ParamValue ($dmtoParam) :: (ToParam a, Show a) => a -> ParamValue -- | Decode data from a query, session, or form -- parameter value -- --
-- data Todo = Todo
-- { id :: TodoId
-- , task :: Text
-- , completed :: Bool
-- }
-- deriving (Show, Read, ToParam, FromParam)
--
--
-- -- data Tags = Tags [Text] -- -- instance FromParam Tags where -- parseParam (ParamValue t) = -- pure $ Tags $ Text.splitOn "," t --class FromParam a parseParam :: FromParam a => ParamValue -> Either Text a ($dmparseParam) :: (FromParam a, Read a) => ParamValue -> Either Text a -- | Key-value store for query params and sessions data QueryData -- | Data.Default doesn't have a Text instance. This class does class DefaultParam a defaultParam :: DefaultParam a => a ($dmdefaultParam) :: (DefaultParam a, Default a) => a -- | Allow inputs to trigger actions for a different view target :: (HyperViewHandled id ctx, ViewId id) => id -> View id () -> View ctx () -- | Manually set the response to the given view. Normally you would return -- a View from runPage instead view :: forall (es :: [Effect]). Hyperbole :> es => View () () -> Eff es Response -- | Valid responses for a Hyperbole effect. Use -- notFound, etc instead. data Response class ViewId a class ViewAction a -- | The top-level view returned by a Page. It carries a -- type-level list of every HyperView used in our Page so -- the compiler can check our work and wire everything together. data Root (views :: [Type]) -- | Add text to a view. Not required for string literals -- --
-- el_ $ do -- "Hello: " -- text user.name --text :: Text -> View c () data Url Url :: Text -> Text -> [Segment] -> Query -> Url [scheme] :: Url -> Text [domain] :: Url -> Text [path] :: Url -> [Segment] [query] :: Url -> Query -- | Apply to even-numbered children even :: Mod c -> Mod c -- | Options for styles that support specifying various sides. This has a -- "fake" Num instance to support literals -- --
-- border 5 -- border (X 2) -- border (TRBL 0 5 0 0) --data Sides a All :: a -> Sides a TRBL :: a -> a -> a -> a -> Sides a X :: a -> Sides a Y :: a -> Sides a XY :: a -> a -> Sides a T :: a -> Sides a R :: a -> Sides a B :: a -> Sides a L :: a -> Sides a TR :: a -> a -> Sides a TL :: a -> a -> Sides a BR :: a -> a -> Sides a BL :: a -> a -> Sides a -- | Element functions expect a modifier function as their first argument. -- These can add attributes and classes. Combine multiple Mods -- with (.) -- --
-- userEmail :: User -> View c () -- userEmail user = input (fontSize 16 . active) (text user.email) -- where -- active = isActive user then bold else id ---- -- If you don't want to specify any attributes, you can use id -- --
-- plainView :: View c () -- plainView = el id "No styles" --type Mod context = Attributes context -> Attributes context -- | Cut off the contents of the element truncate :: Mod c -- | Apply to odd-numbered children odd :: Mod c -> Mod c -- | Set the list style of an item -- --
-- ol id $ do -- li (list Decimal) "First" -- li (list Decimal) "Second" -- li (list Decimal) "Third" --list :: (ToClassName a, Style ListType a) => a -> Mod c data Position Absolute :: Position Fixed :: Position Sticky :: Position Relative :: Position -- | A hyperlink to the given url link :: Url -> Mod c -> View c () -> View c () value :: Text -> Mod c data Display Block :: Display -- | Set top, bottom, right, and left. See stack and popup offset :: Sides Length -> Mod c -- | Space surrounding the children of the element -- -- To create even spacing around and between all elements: -- --
-- col (pad 10 . gap 10) $ do -- el_ "one" -- el_ "two" -- el_ "three" --pad :: Sides Length -> Mod c -- | Views are HTML fragments that carry all CSS used by any child -- element. -- --
-- view :: View c () -- view = col (pad 10 . gap 10) $ do -- el bold "Hello" -- el_ "World" ---- -- They can also have a context which can be used to create type-safe or -- context-aware elements. See context or table for an -- example data View context a style :: Text -> View c () -- | We can intuitively create layouts with combinations of row, -- col, stack, grow, and space -- -- Wrap main content in layout to allow the view to consume -- vertical screen space -- --
-- holygrail :: View c () -- holygrail = layout id $ do -- row section "Top Bar" -- row grow $ do -- col section "Left Sidebar" -- col (section . grow) "Main Content" -- col section "Right Sidebar" -- row section "Bottom Bar" -- where section = border 1 --layout :: Mod c -> View c () -> View c () name :: Text -> Mod c data None None :: None -- | position:absolute, relative, etc. See stack and popup position :: Position -> Mod c -- | Space that fills the available space in the parent row or -- col. -- --
-- row id $ do -- space -- el_ "Right" ---- -- This is equivalent to an empty element with grow -- --
-- space = el grow none --space :: View c () -- | ToColor allows you to create a type containing your application's -- colors: -- --
-- data AppColor -- = White -- | Primary -- | Dark -- -- instance ToColor AppColor where -- colorValue White = "#FFF" -- colorValue Dark = "#333" -- colorValue Primary = "#00F" -- -- hello :: View c () -- hello = el (bg Primary . color White) "Hello" --class ToColor a colorValue :: ToColor a => a -> HexColor colorName :: ToColor a => a -> Text ($dmcolorName) :: (ToColor a, Show a) => a -> Text -- | Apply when hovering over an element -- --
-- el (bg Primary . hover (bg PrimaryLight)) "Hover" --hover :: Mod c -> Mod c -- | Embed static, unescaped HTML or SVG. Take care not to use raw -- with user-generated content. -- --
-- spinner = raw "<svg>...</svg>" --raw :: Text -> View c () -- | Stack children on top of each other. Each child has the full width. -- See popup -- --
-- stack id $ do -- layer id "Background" -- layer (bg Black . opacity 0.5) "Overlay" --stack :: Mod c -> Layer c () -> View c () -- | This layer is not included in the stack size, and covers -- content outside of it. If used outside of stack, the popup is offset -- from the entire page. -- --
-- stack id $ do -- layer id $ input (value "Autocomplete Box") -- layer (popup (TRBL 50 0 0 0)) $ do -- el_ "Item 1" -- el_ "Item 2" -- el_ "Item 3" -- el_ "This is covered by the menu" --popup :: Sides Length -> Mod c -- | Create a type safe data table by specifying columns -- --
-- usersTable :: [User] -> View c () -- usersTable us = do -- table id us $ do -- tcol (th hd "Name") $ \u -> td cell $ text u.name -- tcol (th hd "Email") $ \u -> td cell $ text u.email -- where -- hd = cell . bold -- cell = pad 4 . border 1 --table :: Mod c -> [dt] -> Eff '[Writer [TableColumn c dt]] () -> View c () -- | The Attributes for an Element. Classes are merged and managed -- separately from the other attributes. data Attributes (c :: k) -- | Lay out children in a row -- --
-- row id $ do -- el_ "Left" -- space -- el_ "Right" --row :: Mod c -> View c () -> View c () -- | Lay out children in a column. -- --
-- col grow $ do -- el_ "Top" -- space -- el_ "Bottom" --col :: Mod c -> View c () -> View c () -- | Alternatively, the reset is available on a CDN -- --
-- import Data.String.Interpolate (i)
--
-- toDocument :: ByteString -> ByteString
-- toDocument cnt =
-- [i|<html>
-- <head>
-- <link rel="stylesheet" href="#{cssResetLink}">
-- </head>
-- <body>#{cnt}</body>
-- </html>|]
--
cssResetLink :: Text
data Align
AlignCenter :: Align
AlignLeft :: Align
AlignRight :: Align
AlignJustify :: Align
-- | Hexidecimal Color. Can be specified with or without the leading
-- #. Recommended to use an AppColor type instead of manually
-- using hex colors. See ToColor
newtype HexColor
HexColor :: Text -> HexColor
-- | Media allows for responsive designs that change based on
-- characteristics of the window. See Layout Example
data Media
MinWidth :: Int -> Media
MaxWidth :: Int -> Media
-- | Milliseconds, used for transitions
data Ms
-- | Px, converted to Rem. Allows for the user to change the document font
-- size and have the app scale accordingly. But allows the programmer to
-- code in pixels to match a design
data PxRem
data Length
PxRem :: PxRem -> Length
Pct :: Float -> Length
-- | Apply when the Media matches the current window. This allows for
-- responsive designs
--
-- -- el (width 100 . media (MinWidth 800) (width 400)) -- "Big if window > 800" --media :: Media -> Mod c -> Mod c data TransitionProperty Width :: PxRem -> TransitionProperty Height :: PxRem -> TransitionProperty BgColor :: HexColor -> TransitionProperty Color :: HexColor -> TransitionProperty data ListType Decimal :: ListType Disc :: ListType data Inner Inner :: Inner data Shadow -- | Set to a specific width width :: Length -> Mod c -- | Set to a specific height height :: Length -> Mod c -- | Allow width to grow to contents but not shrink any smaller than value minWidth :: Length -> Mod c -- | Allow height to grow to contents but not shrink any smaller than value minHeight :: Length -> Mod c -- | The space between child elements. See pad gap :: Length -> Mod c fontSize :: Length -> Mod c -- | Add a drop shadow to an element -- --
-- input (shadow Inner) "Inset Shadow" -- button (shadow ()) "Click Me" --shadow :: (Style Shadow a, ToClassName a) => a -> Mod c -- | Round the corners of the element rounded :: Length -> Mod c -- | Set the background color. See ToColor bg :: ToColor clr => clr -> Mod ctx -- | Set the text color. See ToColor color :: ToColor clr => clr -> Mod ctx bold :: Mod c italic :: Mod c underline :: Mod c opacity :: Float -> Mod c -- | Set a border around the element -- --
-- el (border 1) "all sides" -- el (border (X 1)) "only left and right" --border :: Sides PxRem -> Mod c -- | Set a border color. See ToColor borderColor :: ToColor clr => clr -> Mod ctx -- | Use a button-like cursor when hovering over the element -- -- Button-like elements: -- --
-- btn = pointer . bg Primary . hover (bg PrimaryLight) -- -- options = row id $ do -- el btn "Login" -- el btn "Sign Up" --pointer :: Mod c -- | Animate changes to the given property -- --
-- el (transition 100 (Height 400)) "Tall" -- el (transition 100 (Height 100)) "Small" --transition :: Ms -> TransitionProperty -> Mod c textAlign :: Align -> Mod c zIndex :: Int -> Mod c -- | Set container display -- -- el (display None) HIDDEN display :: (Style Display a, ToClassName a) => a -> Mod c -- | Apply when the mouse is pressed down on an element active :: Mod c -> Mod c -- | Apply when the element is somewhere inside an anscestor. -- -- For example, the HTMX library applies an "htmx-request" class to the -- body when a request is pending. We can use this to create a loading -- indicator -- --
-- el (pad 10) $ do -- el (parent "htmx-request" flexRow . hide) "Loading..." -- el (parent "htmx-request" hide . flexRow) "Normal Content" --parent :: Text -> Mod c -> Mod c pathUrl :: [Segment] -> Url cleanSegment :: Segment -> Segment pathSegments :: Text -> [Segment] url :: Text -> Url renderUrl :: Url -> Text renderPath :: [Segment] -> Text -- | Views have a Reader built-in for convienient access to static -- data, and to add type-safety to view functions. See -- 'Web.View.Element.ListItem and -- https://hackage.haskell.org/package/hyperbole/docs/Web-Hyperbole.html -- --
-- numberView :: View Int () -- numberView = do -- num <- context -- el_ $ do -- "Number: " -- text (pack $ show num) --context :: View context context -- | Run a view with a specific context in a parent View with -- a different context. -- --
-- parentView :: View c () -- parentView = do -- addContext 1 numberView -- addContext 2 numberView -- addContext 3 numberView --addContext :: context -> View context () -> View c () -- | Create a new element constructor with the given tag name -- --
-- aside :: Mod c -> View c () -> View c () -- aside = tag "aside" --tag :: Text -> Mod c -> View c () -> View c () -- | Set an attribute, replacing existing value -- --
-- hlink :: Text -> View c () -> View c () -- hlink url content = tag "a" (att "href" url) content --att :: Name -> AttValue -> Mod c -- | Renders a View as HTML with embedded CSS class definitions -- --
-- >>> renderText $ el bold "Hello"
-- <style type='text/css'>.bold { font-weight:bold }</style>
-- <div class='bold'>Hello</div>
--
renderText :: View () () -> Text
renderLazyText :: View () () -> Text
renderLazyByteString :: View () () -> ByteString
data TableColumn c dt
data TableHead a
-- | A basic element
--
-- -- el (bold . pad 10) "Hello" --el :: Mod c -> View c () -> View c () -- | A basic element, with no modifiers -- --
-- el_ "Hello" --el_ :: View c () -> View c () -- | Do not show any content -- --
-- if isVisible -- then content -- else none --none :: View c () pre :: Mod c -> Text -> View c () code :: Mod c -> Text -> View c () script :: Text -> View c () stylesheet :: Text -> View c () tcol :: forall dt c. View (TableHead c) () -> (dt -> View dt ()) -> Eff '[Writer [TableColumn c dt]] () th :: Mod c -> View c () -> View (TableHead c) () td :: Mod () -> View () () -> View dt () -- | List elements do not include any inherent styling but are useful for -- accessibility. See list. -- --
-- ol id $ do -- let nums = list Decimal -- li nums "one" -- li nums "two" -- li nums "three" --ol :: Mod c -> ListItem c () -> View c () ul :: Mod c -> ListItem c () -> View c () li :: Mod c -> View c () -> ListItem c () data Layer c a -- | As layout but as a Mod -- --
-- holygrail = col root $ do -- ... --root :: Mod c -- | Grow to fill the available space in the parent row or -- col -- --
-- row id $ do -- el grow none -- el_ "Right" --grow :: Mod c -- | Make a fixed layout by putting scroll on a child-element -- --
-- document = row root $ do -- nav (width 300) "Sidebar" -- col (grow . scroll) "Main Content" --scroll :: Mod c -- | A Nav element nav :: Mod c -> View c () -> View c () -- | A normal layer contributes to the size of the parent. See stack layer :: Mod c -> View c () -> Layer c () -- | Hide an element. See display hide :: Mod c -- | Set container to be a row. Favor row when possible flexRow :: Mod c -- | Set container to be a column. Favor col when possible flexCol :: Mod c -- | A constraint that requires that a particular effect e is a -- member of the type-level list es. This is used to -- parameterize an Eff computation over an arbitrary list of -- effects, so long as e is somewhere in the list. -- -- For example, a computation that only needs access to a mutable value -- of type Integer would have the following type: -- --
-- State Integer :> es => Eff es () --class (e :: Effect) :> (es :: [Effect]) -- | The Eff monad provides the implementation of a computation that -- performs an arbitrary set of effects. In Eff es a, -- es is a type-level list that contains all the effects that -- the computation may perform. For example, a computation that produces -- an Integer by consuming a String from the global -- environment and acting upon a single mutable value of type Bool -- would have the following type: -- --
-- (Reader String :> es, State Bool :> es) => Eff es Integer ---- -- Abstracting over the list of effects with (:>): -- --
-- app :: Application -- app req respond = bracket_ -- (putStrLn "Allocating scarce resource") -- (putStrLn "Cleaning up") -- (respond $ responseLBS status200 [] "Hello World") --type Application = Request -> Response -> IO ResponseReceived -> IO ResponseReceived -- | Representable types of kind *. This class is derivable in GHC -- with the DeriveGeneric flag on. -- -- A Generic instance must satisfy the following laws: -- --
-- from . to ≡ id -- to . from ≡ id --class Generic a