git.iko.soy/rowtype-yoga/purescript-next-website
on main help sign in
Some improvements
commitc2bfb8e95e41a9c9952820521175b2eef1c03888 authorsigma-andex <77549848+sigma-andex@users.noreply.github.com> dateSun 28 Aug 2022, 17:32 (4 years ago) parentdca75e6
12 files changed+395−74
package-lock.json+25 package.json+2 pages/_app.js+2−1 spago.dhall+2 src/Components/Navigation.js+19 src/Components/Navigation.purs+85−53 src/Pages/App.purs−11 src/Pages/Document.purs−4 src/Pages/Pursuit.purs+101−2 src/React/Hooks/UseRemoteData.purs+153 src/Themes.purs+3−3 styles.css+3
package-lock.json+25
@@ -9,8 +9,10 @@
99 "version": "1.0.0",
1010 "license": "MIT",
1111 "dependencies": {
12+ "@fontsource/manrope": "^4.5.11",
1213 "@heroicons/react": "^2.0.8",
1314 "@nextui-org/react": "^1.0.0-beta.10",
15+ "big-integer": "^1.6.51",
1416 "next": "^12.2.5",
1517 "next-themes": "^0.2.0",
1618 "react": "^18.2.0",
@@ -30,6 +32,11 @@
3032 "node": ">=6.9.0"
3133 }
3234 },
35+ "node_modules/@fontsource/manrope": {
36+ "version": "4.5.11",
37+ "resolved": "https://registry.npmjs.org/@fontsource/manrope/-/manrope-4.5.11.tgz",
38+ "integrity": "sha512-YOdZShn/ply0nKcMYZF4ggrNQD7cDsq6aJ8QkjiA1WVXn6ztD1x7XMd538wEqF+Er+22d00dilEDqpVPFU0FjQ=="
39+ },
3340 "node_modules/@formatjs/ecma402-abstract": {
3441 "version": "1.12.0",
3542 "resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-1.12.0.tgz",
@@ -1462,6 +1469,14 @@
14621469 "tslib": "^2.4.0"
14631470 }
14641471 },
1472+ "node_modules/big-integer": {
1473+ "version": "1.6.51",
1474+ "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.51.tgz",
1475+ "integrity": "sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg==",
1476+ "engines": {
1477+ "node": ">=0.6"
1478+ }
1479+ },
14651480 "node_modules/caniuse-lite": {
14661481 "version": "1.0.30001383",
14671482 "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001383.tgz",
@@ -1715,6 +1730,11 @@
17151730 "regenerator-runtime": "^0.13.4"
17161731 }
17171732 },
1733+ "@fontsource/manrope": {
1734+ "version": "4.5.11",
1735+ "resolved": "https://registry.npmjs.org/@fontsource/manrope/-/manrope-4.5.11.tgz",
1736+ "integrity": "sha512-YOdZShn/ply0nKcMYZF4ggrNQD7cDsq6aJ8QkjiA1WVXn6ztD1x7XMd538wEqF+Er+22d00dilEDqpVPFU0FjQ=="
1737+ },
17181738 "@formatjs/ecma402-abstract": {
17191739 "version": "1.12.0",
17201740 "resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-1.12.0.tgz",
@@ -2815,6 +2835,11 @@
28152835 "tslib": "^2.4.0"
28162836 }
28172837 },
2838+ "big-integer": {
2839+ "version": "1.6.51",
2840+ "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.51.tgz",
2841+ "integrity": "sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg=="
2842+ },
28182843 "caniuse-lite": {
28192844 "version": "1.0.30001383",
28202845 "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001383.tgz",
package.json+2
@@ -13,8 +13,10 @@
1313 "author": "",
1414 "license": "MIT",
1515 "dependencies": {
16+ "@fontsource/manrope": "^4.5.11",
1617 "@heroicons/react": "^2.0.8",
1718 "@nextui-org/react": "^1.0.0-beta.10",
19+ "big-integer": "^1.6.51",
1820 "next": "^12.2.5",
1921 "next-themes": "^0.2.0",
2022 "react": "^18.2.0",
pages/_app.js+2−1
@@ -1,3 +1,4 @@
11import { mkApp } from "../output/Pages.App/index.js";
2−
2+import "@fontsource/manrope"
3+import '../styles.css'
34export default mkApp();
spago.dhall+2
@@ -18,6 +18,7 @@ to generate this file without the comments in this block.
1818 , "console"
1919 , "debug"
2020 , "effect"
21+ , "either"
2122 , "fetch"
2223 , "fetch-yoga-json"
2324 , "foldable-traversable"
@@ -32,6 +33,7 @@ to generate this file without the comments in this block.
3233 , "react-basic-hooks"
3334 , "react-icons"
3435 , "record"
36+ , "remotedata"
3537 , "strings"
3638 , "transformers"
3739 , "tuples"
src/Components/Navigation.jsnew file+19
@@ -0,0 +1,19 @@
1+import { e } from "../../output/Data.Number";
2+
3+export const isDarkDefault_ = () => {
4+ if (window.matchMedia) {
5+ const mediaQuery = "(prefers-color-scheme: dark)";
6+
7+ const mql = window.matchMedia(mediaQuery);
8+
9+ const hasPreference = typeof mql.matches === "boolean";
10+
11+ if (hasPreference) {
12+ return mql.matches ? true : false;
13+ } else {
14+ return false;
15+ }
16+ } else {
17+ return false;
18+ }
19+};
src/Components/Navigation.purs+85−53
@@ -1,11 +1,18 @@
1−module Components.Navigation (navigation) where
1+module Components.Navigation
2+ ( isDarkDefault_
3+ , navigation
4+ )
5+ where
26
37import Prelude
8+import Web.HTML.HTMLElement
49
5−import Data.Maybe (Maybe(..))
6−import Data.Traversable (traverse_)
10+import Control.Monad.Maybe.Trans (MaybeT(..), runMaybeT)
11+import Data.Maybe (Maybe(..), maybe)
12+import Data.Traversable (for_, traverse, traverse_)
713import Data.Tuple.Nested ((/\))
814import Debug (spy)
15+import Effect (Effect)
916import Next.Router (useRouter)
1017import Next.Router as Router
1118import NextUI.NextUI (switch, useTheme)
@@ -13,7 +20,7 @@ import NextUI.NextUI as NextUI
1320import React.Basic.DOM (css)
1421import React.Basic.DOM.Events (targetChecked)
1522import React.Basic.Events (handler)
16−import React.Basic.Hooks (Component, component, useEffect, useState')
23+import React.Basic.Hooks (Component, component, useEffect, useEffectOnce, useState')
1724import React.Basic.Hooks as React
1825import React.Icons (icon)
1926import React.Icons.Si (siDiscord, siDiscourse, siGithub, siPurescript)
@@ -23,56 +30,81 @@ import Web.DOM.Document (documentElement)
2330import Web.DOM.Element as DOMEL
2431import Web.HTML (window)
2532import Web.HTML.HTMLDocument (toDocument)
33+import Web.HTML.HTMLDocument as Doc
2634import Web.HTML.Window (document)
35+-- radial-gradient(20rem 20rem at left 15rem top 20rem, rgba(30, 144, 255,0.5) 0% 10%,#0000 90% 90%),
36+mkBackgroundImage isDark =
37+ if isDark then
38+ """
39+ radial-gradient(25rem 25rem at left 15rem top 20rem, rgba(30, 144, 255,0.55) 0% 10%,#0000 90% 90%),
40+ radial-gradient(45rem 45rem at left 70rem top 40rem, rgba(0, 0, 139, 0.75) 0% 10%, #0000 70% 80%);
41+ """
42+ else
43+ """
44+ radial-gradient(25rem 25rem at left 15rem top 20rem, rgba(32, 178, 170, 0.25) 0% 10%,#0000 90% 90%),
45+ radial-gradient(45rem 45rem at left 70rem top 40rem, rgba(30, 144, 255, 0.25) 0% 10%,#0000 70% 80%);
46+ """
47+foreign import isDarkDefault_ :: Effect Boolean
2748
2849navigation :: Component Unit
29−navigation = component "Navbar" \_props -> React.do
50+navigation = do
51+ component "Navbar" \_props -> React.do
3052
31− { theme } <- useTheme
32− router <- useRouter
33− let
34− dispatchRoute = Router.push router
35− isDark /\ setDark <- useState' false
36− _ <- useEffect [ isDark ] do
37− w <- window
38− maybeElem <- document w <#> toDocument >>= documentElement
39− case maybeElem of
40− Just el -> do
41− (DOMEL.setAttribute "class" if isDark then "light-theme" else "dark-theme") el
42− pure mempty
43− Nothing -> pure mempty
44− pure $ el NextUI.navbar { isBordered: isDark, variant: "static" }
45− [ el NextUI.navbarBrand {}
46− $ el NextUI.link
47− { onClick: dispatchRoute $ "/"
48− }
49− $ icon siPurescript { style: css { color: getColorValue theme "neutral" }, size: "3rem" }
50− , el NextUI.navbarContent { hideIn: "xs" } $ map (mkLink theme)
51− [ { onClick: dispatchRoute "/pursuit", title: "Getting started" }
52− , { onClick: dispatchRoute "/pursuit", title: "Try Purescript" }
53− , { onClick: dispatchRoute "/pursuit", title: "Pursuit" }
54− ]
55− , el NextUI.navbarContent {}
56− [ el NextUI.navbarLink
57− { href: "https://purescript.org/chat"
58− }
59− $ icon siDiscord { style: css { color: spy "color value" $ getColorValue theme "neutral" }, size: "1.5rem" }
60− , el NextUI.navbarLink
61− { href: "https://discourse.purescript.org/"
62− , target: "_blank"
63− }
64− $ icon siDiscourse { style: css { color: spy "color value" $ getColorValue theme "neutral" }, size: "1.5rem" }
65− , el NextUI.navbarLink
66− { href: "https://www.github.com/purescript/purescript"
67− , target: "_blank"
68− }
69− $ icon siGithub { style: css { color: spy "color value" $ getColorValue theme "neutral" }, size: "1.5rem" }
70− , el switch { checked: isDark, onChange: handler targetChecked $ traverse_ setDark } ""
71− ]
72− ]
73− where
74− mkLink theme { onClick, title } =
75− el NextUI.navbarLink
76− { onClick
77− }
78− $ el NextUI.text { size: "$xl", color: getColorValue theme "neutral" } title
53+ { theme } <- useTheme
54+ router <- useRouter
55+ let
56+ dispatchRoute = Router.push router
57+ isDarkPreference /\ setIsDarkPreference <- useState' true
58+ isDark /\ setDark <- useState' isDarkPreference
59+ _ <- useEffectOnce do
60+ isDarkDefault <- isDarkDefault_
61+ setIsDarkPreference $ spy "Setting isDarkDefault " isDarkDefault
62+ pure mempty
63+ _ <- useEffect [ isDark ] do
64+ w <- window
65+ htmlDocument <- document w
66+ maybeElem <- htmlDocument # toDocument # documentElement
67+ case maybeElem of
68+ Just el -> do
69+ (DOMEL.setAttribute "class" if (spy "isDark" $ isDark) then "dark-theme" else "light-theme") el
70+ bodyElem <- runMaybeT do
71+ body <- MaybeT $ Doc.body htmlDocument
72+ pure $ toElement body
73+ maybe (pure unit) (DOMEL.setAttribute "style" ("background-image: " <> mkBackgroundImage isDark)) bodyElem
74+ pure mempty
75+ Nothing -> pure mempty
76+ pure $ el NextUI.navbar { isBordered: isDark, variant: "static" }
77+ [ el NextUI.navbarBrand {}
78+ $ el NextUI.link
79+ { onClick: dispatchRoute $ "/"
80+ }
81+ $ icon siPurescript { style: css { color: getColorValue theme "neutral" }, size: "3rem" }
82+ , el NextUI.navbarContent { hideIn: "xs" } $ map (mkLink theme)
83+ [ { onClick: dispatchRoute "/pursuit", title: "Getting started" }
84+ , { onClick: dispatchRoute "/pursuit", title: "Try Purescript" }
85+ , { onClick: dispatchRoute "/pursuit", title: "Pursuit" }
86+ ]
87+ , el NextUI.navbarContent {}
88+ [ el NextUI.navbarLink
89+ { href: "https://purescript.org/chat"
90+ }
91+ $ icon siDiscord { style: css { color: spy "color value" $ getColorValue theme "neutral" }, size: "1.5rem" }
92+ , el NextUI.navbarLink
93+ { href: "https://discourse.purescript.org/"
94+ , target: "_blank"
95+ }
96+ $ icon siDiscourse { style: css { color: spy "color value" $ getColorValue theme "neutral" }, size: "1.5rem" }
97+ , el NextUI.navbarLink
98+ { href: "https://github.com/purescript/purescript"
99+ , target: "_blank"
100+ }
101+ $ icon siGithub { style: css { color: spy "color value" $ getColorValue theme "neutral" }, size: "1.5rem" }
102+ , el switch { checked: isDark, onChange: handler targetChecked $ traverse_ setDark } ""
103+ ]
104+ ]
105+ where
106+ mkLink theme { onClick, title } =
107+ el NextUI.navbarLink
108+ { onClick
109+ }
110+ $ el NextUI.text { size: "$xl", color: getColorValue theme "neutral" } title
src/Pages/App.purs−11
@@ -23,17 +23,6 @@ type AppProps props =
2323 , pageProps :: props
2424 }
2525
26−backgroundImage isDark =
27− if isDark then
28− """
29− radial-gradient(45rem 45rem at left 70rem top 40rem, rgba(0, 0, 139, 0.75) 0% 10%, #0000 70% 80%),
30− radial-gradient(20rem 20rem at left 15rem top 20rem, rgba(30, 144, 255,0.5) 0% 10%,#0000 90% 90%);
31− """
32− else
33− """
34− radial-gradient(65rem 65rem at left 70rem top 40rem, rgba(0, 0, 139, 0.75) 0% 10%,#0000 70% 80%),
35− radial-gradient(20rem 20rem at left 15rem top 20rem, rgba(0, 191, 255, 0.25) 0% 10%,#0000 90% 90%)
36− """
3726
3827mkApp :: forall props. Effect (EffectFn1 (AppProps props) React.JSX)
3928mkApp = do
src/Pages/Document.purs−4
@@ -24,10 +24,6 @@ mkDocument = React.component "Document" \_ -> do
2424 }
2525 , R.body
2626 { className: "font-sans antialiased leading-normal tracking-wider bg-slate-200"
27− , style: css
28− { height: "100vh"
29− , width: "100vw"
30− }
3127 , children:
3228 [ N.main {}
3329 , N.nextScript {}
src/Pages/Pursuit.purs+101−2
@@ -8,21 +8,112 @@ module Pages.Pursuit
88import Prelude
99
1010import Components.Page as Page
11−import NextUI.NextUI as NextUI
11+import Control.Monad.Except (except)
12+import Data.Either (Either(..))
13+import Data.Eq.Generic (genericEq)
14+import Data.Generic.Rep (class Generic)
15+import Data.Maybe (Maybe)
16+import Data.Show.Generic (genericShow)
17+import Data.Tuple.Nested ((/\))
18+import Effect.Aff (Aff)
19+import Effect.Class (liftEffect)
20+import Effect.Class.Console (log)
21+import Effect.Exception (Error)
22+import Fetch (Method(..), fetch)
23+import Fetch.Yoga.Json (fromJSON)
24+import Foreign (ForeignError(..))
25+import Network.RemoteData (RemoteData)
26+import Network.RemoteData as RD
1227import Next.Router (query, useRouter)
1328import Next.Router as Router
29+import NextUI.NextUI as NextUI
1430import React.Basic.DOM.Simplified.Generated as R
31+import React.Basic.Hooks (type (/\), mkReducer, useEffect)
1532import React.Basic.Hooks as React
33+import React.Hooks.UseRemoteData (useRemoteDataDispatch)
1634import React.Util (el)
35+import Yoga.JSON (class ReadForeign)
36+import Yoga.JSON as YogaJson
1737
1838type Props =
1939 { header :: String
2040 }
2141
42+type DeclarationR =
43+ ( module :: String
44+ , title :: String
45+ , typeOrValue :: String
46+ , typeText :: Maybe String
47+ )
48+
49+type PackageR = (deprecated :: Boolean)
50+
51+data SearchInfo = Declaration { | DeclarationR } | Package { | PackageR }
52+
53+derive instance Generic SearchInfo _
54+instance Eq SearchInfo where
55+ eq = genericEq
56+instance Show SearchInfo where
57+ show = genericShow
58+
59+instance ReadForeign SearchInfo where
60+ readImpl f = do
61+ { "type": t } :: { "type" :: String } <- YogaJson.readImpl f
62+ case t of
63+ "declaration" -> YogaJson.readImpl f <#> Declaration
64+ "package" -> YogaJson.readImpl f <#> Package
65+ other -> except $ Left (pure $ ForeignError $ "Invalid search info type " <> other)
66+
67+type SearchResult =
68+ { info :: SearchInfo
69+ , markup :: String
70+ , package :: String
71+ , text :: String
72+ , url :: String
73+ , version :: String
74+ }
75+
76+data SearchError = SearchError
77+
78+derive instance Generic SearchError _
79+instance Eq SearchError where
80+ eq = genericEq
81+instance Show SearchError where
82+ show SearchError = "Failed to get search results"
83+
84+data Action = UpdateSearchResult (RemoteData SearchError (Array SearchResult))
85+
86+type State =
87+ { searchResult ∷ RemoteData SearchError (Array SearchResult)
88+ }
89+
90+defaultState :: State
91+defaultState = { searchResult: RD.NotAsked }
92+
93+reduce ∷ State → Action → State
94+reduce s (UpdateSearchResult searchResult) =
95+ s { searchResult = searchResult }
96+
97+getSearchResult :: {} -> Aff (Either SearchError (Array SearchResult))
98+getSearchResult {} = do
99+ let url = "http://localhost:3000/search?q=barlow"
100+ { status, text, json } ← fetch url
101+ { method: GET
102+ , headers: { "Accept": "application/json" }
103+ }
104+ case status of
105+ 200 -> do
106+ searchResult :: Array SearchResult <- fromJSON json
107+ pure $ Right searchResult
108+ statusCode -> do
109+ body <- text
110+ log $ "Got invalid response [" <> (show statusCode) <> "]:\n" <> body
111+ pure $ Left SearchError
22112
23113mkPursuit :: Page.Component Props
24114mkPursuit = do
25115
116+ reducer ← mkReducer reduce # liftEffect
26117 Page.component "Pursuit" \env props -> React.do
27118 router <- useRouter
28119 let
@@ -31,10 +122,18 @@ mkPursuit = do
31122
32123 dispatchRoute = Router.push router
33124
125+ state /\ dispatch ← React.useReducer defaultState reducer
126+
127+ remoteSearchResult ← useRemoteDataDispatch (dispatch <<< UpdateSearchResult) getSearchResult
128+
129+ useEffect unit do
130+ remoteSearchResult.load {}
131+ mempty
132+ -- http://localhost:3000/search\?q
34133 pure $ el NextUI.container {} $
35134 [ el NextUI.row {}
36135 [ R.h1' "Pursuit"
37−
136+ , el NextUI.text {} $ show state.searchResult
38137 ]
39138 ]
40139
src/React/Hooks/UseRemoteData.pursnew file+153
@@ -0,0 +1,153 @@
1+module React.Hooks.UseRemoteData
2+ ( UseRemoteData(..)
3+ , UseRemoteDataDispatch(..)
4+ , useRemoteData
5+ , useRemoteDataDispatch
6+ , useRemoteDataDispatchWithDefault
7+ , useRemoteDataWithDefault
8+ ) where
9+
10+import Prelude hiding (top)
11+
12+import Data.Either (Either, either)
13+import Data.Maybe (Maybe(..), maybe)
14+import Data.Newtype (class Newtype)
15+import Data.Traversable (for_)
16+import Data.Tuple.Nested ((/\))
17+import Effect (Effect)
18+import Effect.Aff (Aff, Fiber, error, finally, forkAff, killFiber, launchAff_)
19+import Effect.Class (liftEffect)
20+import Network.RemoteData (RemoteData)
21+import Network.RemoteData as RD
22+import React.Basic.Hooks (Hook, UseEffect, UseRef, UseState, coerceHook, readRef, useEffect, writeRef)
23+import React.Basic.Hooks as React
24+
25+newtype UseRemoteData ∷ ∀ k. k → Type → Type → Type → Type
26+newtype UseRemoteData i err o hooks = UseRemoteData
27+ ( UseRef (Maybe (Fiber Unit))
28+ ( UseState (RemoteData err o)
29+ hooks
30+ )
31+ )
32+
33+derive instance Newtype (UseRemoteData i o err hooks) _
34+
35+useRemoteDataWithDefault
36+ ∷ ∀ i o err
37+ . Maybe o
38+ → (i → Aff (Either err o))
39+ → Hook (UseRemoteData i err o)
40+ ( { data ∷ RemoteData err o
41+ , load ∷ i → Effect Unit
42+ , reset ∷ Effect Unit
43+ }
44+ )
45+useRemoteDataWithDefault startValue loadFn =
46+ coerceHook React.do
47+ let initialValue = maybe RD.NotAsked RD.Success startValue
48+ value /\ setValue ← React.useState' initialValue
49+ fiberRef ← React.useRef Nothing
50+ let
51+ load input =
52+ launchAff_ do
53+ cancel
54+ setValue RD.Loading # liftEffect
55+ fib ←
56+ forkAff
57+ $ finally (writeRef fiberRef Nothing # liftEffect) do
58+ result ∷ Either err o ← loadFn input
59+ setValue (either RD.Failure RD.Success result) # liftEffect
60+ writeRef fiberRef (Just fib) # liftEffect
61+
62+ cancel = do
63+ fiberʔ <- readRef fiberRef # liftEffect
64+ for_ fiberʔ (killFiber (error "Cancelled"))
65+
66+ reset =
67+ launchAff_ do
68+ cancel
69+ setValue RD.NotAsked # liftEffect
70+ pure
71+ { data: value
72+ , load
73+ , reset
74+ }
75+
76+useRemoteData
77+ ∷ ∀ i o err
78+ . (i → Aff (Either err o))
79+ → Hook (UseRemoteData i err o)
80+ ( { data ∷ RemoteData err o
81+ , load ∷ i → Effect Unit
82+ , reset ∷ Effect Unit
83+ }
84+ )
85+useRemoteData = useRemoteDataWithDefault Nothing
86+
87+newtype UseRemoteDataDispatch ∷ ∀ k. k → Type → Type → Type → Type
88+newtype UseRemoteDataDispatch i err o hooks = UseRemoteDataDispatch
89+ ( UseEffect (RemoteData err o)
90+ ( UseState (Maybe (Fiber Unit))
91+ ( UseState (RemoteData err o)
92+ hooks
93+ )
94+ )
95+ )
96+
97+derive instance Newtype (UseRemoteDataDispatch i o err hooks) _
98+
99+useRemoteDataDispatchWithDefault
100+ ∷ ∀ i o err
101+ . Eq o
102+ => Eq err
103+ => Maybe o
104+ → ((RemoteData err o) -> Effect Unit)
105+ → (i → Aff (Either err o))
106+ → Hook (UseRemoteDataDispatch i err o)
107+ ( { load ∷ i → Effect Unit
108+ , cancel ∷ Effect Unit
109+ }
110+ )
111+useRemoteDataDispatchWithDefault startValue dispatch loadFn =
112+ coerceHook React.do
113+ let initialValue = maybe RD.NotAsked RD.Success startValue
114+ value /\ setValue ← React.useState' initialValue
115+ fiber /\ setFiber ← React.useState' Nothing
116+ useEffect value do
117+ dispatch value
118+ mempty
119+ let
120+ load input =
121+ launchAff_ do
122+ cancel
123+ setValue RD.Loading # liftEffect
124+ fib ←
125+ forkAff
126+ $ finally (setFiber Nothing # liftEffect) do
127+ result ∷ Either err o ← loadFn input
128+ setValue (either RD.Failure RD.Success result) # liftEffect
129+ setFiber (Just fib) # liftEffect
130+
131+ cancel = for_ fiber (killFiber (error "Cancelled"))
132+
133+ reset =
134+ launchAff_ do
135+ cancel
136+ setValue RD.NotAsked # liftEffect
137+ pure
138+ { load
139+ , cancel: reset
140+ }
141+
142+useRemoteDataDispatch
143+ :: ∀ i o err
144+ . Eq o
145+ => Eq err
146+ => ((RemoteData err o) -> Effect Unit)
147+ → (i → Aff (Either err o))
148+ → Hook (UseRemoteDataDispatch i err o)
149+ ( { load ∷ i → Effect Unit
150+ , cancel ∷ Effect Unit
151+ }
152+ )
153+useRemoteDataDispatch = useRemoteDataDispatchWithDefault Nothing
src/Themes.purs+3−3
@@ -30,7 +30,7 @@ mkDark = createTheme
3030 }
3131 , space: {}
3232 , fonts:
33− { sans: "-apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;"
33+ { sans: "Manrope, -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;"
3434 , mono: "Menlo, Monaco, 'Lucida Console', 'Liberation Mono', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono'"
3535 }
3636 }
@@ -49,7 +49,7 @@ mkLight = createTheme
4949 , primaryBorder: "$blue300"
5050 , primaryBorderHover: "$blue600"
5151 , primarySolidHover: "$blue400"
52− , primarySolidContrast: "$black"
52+ , primarySolidContrast: "$blue600"
5353 , primaryShadow: "$blue500"
5454 , gradient: "linear-gradient(112deg, $blue100 -25%, $pink500 -10%, $purple500 80%)"
5555 , link: "$black100"
@@ -57,7 +57,7 @@ mkLight = createTheme
5757 }
5858 , space: {}
5959 , fonts:
60− { sans: "-apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;"
60+ { sans: "Manrope, -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;"
6161 , mono: "Menlo, Monaco, 'Lucida Console', 'Liberation Mono', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono'"
6262 }
6363 }
styles.cssnew file+3
@@ -0,0 +1,3 @@
1+body {
2+ height: 100vh
3+}