git.iko.soy/rowtype-yoga/psfp
on master help sign in
Simplified Fill In The Gaps
commitd37fadc1fffa4aac1e4ad95ce86e2e8bd14af43b authorMark Eibes <mark.eibes@gmail.com> dateTue 14 Apr 2020, 21:01 (6 years ago) parent21162af
6 files changed+177−234
blog/.psc-ide-port+1 blog/src/components/PSLayout.purs+27−8 blog/src/pages/optional.mdx+13−1 components/src/Yoga/FillInTheGaps/Component.purs+13−209 components/src/Yoga/FillInTheGaps/Logic.purs+118 components/src/Yoga/FillInTheGaps/Stories.purs+5−16
blog/.psc-ide-portnew file+1
@@ -0,0 +1 @@
1+15613
blog/src/components/PSLayout.purs+27−8
@@ -1,14 +1,17 @@
11module PSLayout where
22
33import Prelude
4+
5+import Data.Array as A
46import Data.Array as Array
57import Data.Array.NonEmpty as NEA
68import Data.Maybe (Maybe(..), fromMaybe)
79import Data.Nullable (Nullable)
810import Data.Nullable as Nullable
911import Data.String as String
12+import Data.Tuple.Nested ((/\))
13+import Debug.Trace (spy)
1014import Effect (Effect)
11−import Effect.Unsafe (unsafePerformEffect)
1215import JSS (jss, jssClasses)
1316import Justifill (justifill)
1417import Milkis.Impl (FetchImpl)
@@ -16,13 +19,15 @@ import React.Basic (JSX, ReactComponent)
1619import React.Basic.DOM (unsafeCreateDOMComponent)
1720import React.Basic.DOM as R
1821import React.Basic.Helpers (jsx)
19−import React.Basic.Hooks (ReactChildren, component, componentWithChildren, element, reactChildrenToArray, useState)
22+import React.Basic.Hooks (ReactChildren, component, componentWithChildren, element, reactChildrenToArray, useEffect, useState)
2023import React.Basic.Hooks as React
24+import Unsafe.Coerce (unsafeCoerce)
2125import Yoga.Box.Component as Box
2226import Yoga.CompileEditor.Component (mkCompileEditor)
2327import Yoga.Compiler.Api (apiCompiler)
2428import Yoga.FillInTheGaps.Component as FillInTheGaps
2529import Yoga.Header.Component (mkHeader)
30+import Yoga.Helpers ((?||))
2631import Yoga.InlineCode.Component as InlineCode
2732import Yoga.Theme (fromTheme)
2833import Yoga.Theme.CSSBaseline (mkCssBaseline)
@@ -78,9 +83,12 @@ mkLayout fetchImpl = do
7883 ]
7984 }
8085
81−mkSecret :: Effect ( ReactComponent { kids ∷ Array JSX , visible ∷ Boolean })
86+mkSecret :: Effect ( ReactComponent { kids ∷ Array JSX , visible ∷ Boolean, register :: Effect Unit })
8287mkSecret = do
83− component "Secret" \{ kids, visible } -> React.do
88+ component "Secret" \{ kids, visible, register } -> React.do
89+ useEffect visible do
90+ unless visible register
91+ pure mempty
8492 pure
8593 $ R.div
8694 { style: R.css { visibility: if visible then "visible" else "hidden" }
@@ -124,16 +132,20 @@ mkMdxProviderComponent fetchImpl = do
124132 }
125133 componentWithChildren "MDXProviderComponent" \{ children, siteInfo } -> React.do
126134 classes <- useStyles {}
127− sections <- useState []
135+ visibleThroughKey /\ updateVisible <- useState ""
128136 let
129137 baseline child = element cssBaseline { kids: child }
138+ kids = reactChildrenToArray children
139+ visibleKids = spy "visibleKids" $ fromMaybe kids do
140+ i <- kids # A.findIndex (\x -> (unsafeCoerce x).key == visibleThroughKey)
141+ pure $ A.take (i+1) kids
130142
131143 siteInfoJSX =
132144 R.div
133145 { children:
134146 [ jsx header { className: "" } [ R.text siteInfo.site.siteMetadata.title ]
135147 , element sidebar { links: siteInfo.site.siteMetadata.menuLinks }
136− , jsx box {} (reactChildrenToArray children)
148+ , jsx box {} visibleKids
137149 ]
138150 }
139151
@@ -152,8 +164,8 @@ mkMdxProviderComponent fetchImpl = do
152164 R.div
153165 { children: [ element p { text: props.children } ]
154166 }
155− , thematicBreak:
156− \props -> R.div_ [ R.text "HOUI" ]
167+ -- , thematicBreak:
168+ -- \props -> jsx secret { register: updateSections (Map.insert ) }
157169 , inlineCode:
158170 \props -> do
159171 R.span { className: classes.code, children: props.children }
@@ -194,6 +206,13 @@ mkMdxProviderComponent fetchImpl = do
194206 }
195207 false, _ -> element (unsafeCreateDOMComponent "pre") props
196208 }
209+
210+ useEffect (A.length kids) do
211+ let
212+ firstGaps = kids # A.find (\x -> (unsafeCoerce x).props.mdxType == "pre" && ((unsafeCoerce x).props.children.props.className == "language-puregaps")) <#> (\x -> (unsafeCoerce x).key)
213+ lastKey = kids # A.last # unsafeCoerce # _.key
214+ updateVisible (const (firstGaps ?|| lastKey))
215+ pure mempty
197216 pure
198217 $ baseline
199218 [ element mdxProvider
blog/src/pages/optional.mdx+13−1
@@ -6,7 +6,7 @@ import SEO from '../components/seo'
66# Optional
77
88```puregaps
9−--result Apero
9+--result Hello World
1010module Main where
1111import Grimoire
1212
@@ -19,4 +19,16 @@ incantation = cast $
1919
2020---
2121
22+```puregaps
23+--result Hello World
24+module Main where
25+import Grimoire
26+
27+incantation :: Effect Unit
28+--start here
29+incantation = cast $
30+ "{-Hello World-}"
31+--end here
32+```
33+
2234## Hans Hölzel
components/src/Yoga/FillInTheGaps/Component.purs+13−209
@@ -1,83 +1,24 @@
11module Yoga.FillInTheGaps.Component where
22
33import Prelude
4−import Data.Array (foldMap, foldr, intercalate)
54import Data.Array as A
6−import Data.Either (Either(..))
7−import Data.Foldable (foldl)
8−import Data.Function.Uncurried (mkFn2)
9−import Data.Lens ((%~))
10−import Data.Lens.Index (ix)
11−import Data.Maybe (Maybe(..), fromMaybe', isJust, isNothing)
12−import Data.Monoid (guard)
13−import Data.String (Pattern(..), split)
14−import Data.String as S
15−import Data.String.Regex (Regex)
16−import Data.String.Regex as Regex
17−import Data.String.Regex.Flags as RegexFlags
18−import Data.String.Regex.Unsafe (unsafeRegex)
195import Data.Tuple.Nested ((/\))
20−import Debug.Trace (spy)
216import Effect (Effect)
22−import Effect.Aff (launchAff_)
7+import Effect.Aff (Milliseconds(..), delay)
238import Effect.Class (liftEffect)
249import Justifill (justifill)
25−import Partial.Unsafe (unsafeCrashWith)
26−import React.Basic (JSX, ReactComponent, element, fragment)
27−import React.Basic.DOM (css)
10+import React.Basic (JSX, ReactComponent, element)
2811import React.Basic.DOM as R
29−import React.Basic.Events (handler_)
30−import React.Basic.Helpers (jsx)
3112import React.Basic.Hooks (component, useState)
3213import React.Basic.Hooks as React
33−import React.Basic.Hooks.Spring (useTransition)
14+import React.Basic.Hooks.Aff (useAff)
3415import React.Basic.SyntaxHighlighter.Component (HighlighterTheme, syntaxHighlighterImpl)
35−import Shared.Models.Body (CompileResult, RunResult)
36−import Yoga.Button.Component (ButtonType(..), mkButton)
37−import Yoga.ClickAway.Component as ClickAway
38−import Yoga.CloseIcon.Component as CloseIcon
39−import Yoga.Cluster.Component as Cluster
40−import Yoga.Compiler.Types (Compiler)
16+import Yoga.FillInTheGaps.Logic (Segment(..), updateSegments)
4117import Yoga.Helpers ((?||))
4218import Yoga.InlineCode.Component as InlineCode
43−import Yoga.Modal.Component as Modal
4419import Yoga.Theme.Styles (useTheme)
4520import Yoga.Theme.Syntax (mkHighlighterTheme)
4621
47−data Segment
48− = ExpectedResult String
49− | Start
50− | End
51− | Filler String
52− | Hole Int String
53−
54−derive instance eqSegment ∷ Eq Segment
55−getResult ∷ Segment -> Maybe String
56−getResult = case _ of
57− ExpectedResult r -> Just r
58− _ -> Nothing
59−
60−findResult ∷ Array Segment -> String
61−findResult = fromMaybe' (\_ -> unsafeCrashWith "Even teachers make mistakes") <<< A.findMap getResult
62−
63−toCode ∷ Array (Array Segment) -> String
64−toCode lines = intercalate "\n" mapped
65− where
66− mapped = lines <#> (intercalate "" <<< map segmentToCode)
67− segmentToCode = case _ of
68− Filler s -> s
69− Hole _ s -> s
70− _ -> ""
71−
72−complete ∷ Array (Array Segment) -> Boolean
73−complete arr = foldl f true (join arr)
74− where
75− f acc seg =
76− acc
77− && case seg of
78− Hole _ s -> s /= ""
79− _ -> true
80−
8122visibleRange ∷ Array (Array Segment) -> { end ∷ Int, start ∷ Int }
8223visibleRange arr = { start, end }
8324 where
@@ -91,7 +32,7 @@ renderSegments highlighterTheme ic update arrs = R.div_ (A.mapWithIndex renderLi
9132 renderLine i l = R.div_ (A.mapWithIndex (renderSegment i) l)
9233 renderSegment i j s =
9334 if between start end i then case s of
94− Filler s' -> element syntaxHighlighterImpl { style: highlighterTheme, language: "purescript", children: spy "ch" s' }
35+ Filler s' -> element syntaxHighlighterImpl { style: highlighterTheme, language: "purescript", children: s' }
9536 Hole width _ ->
9637 element ic
9738 $ justifill
@@ -102,150 +43,13 @@ renderSegments highlighterTheme ic update arrs = R.div_ (A.mapWithIndex renderLi
10243 else
10344 mempty
10445
105−updateSegments ∷ Int -> Int -> String -> Array (Array Segment) -> Array (Array Segment)
106−updateSegments i j v = (ix i <<< ix j) %~ f
107− where
108− f = case _ of
109− Hole h _ -> Hole h v
110− _ -> unsafeCrashWith "Updated a non-hole"
111−
112−holeRegex ∷ Regex
113−holeRegex = unsafeRegex "({-.*?-})" RegexFlags.global
114−
115−resultRegex ∷ Regex
116−resultRegex = unsafeRegex "--result" RegexFlags.global
117−
118−startRegex ∷ Regex
119−startRegex = unsafeRegex "--start here" RegexFlags.global
120−
121−endRegex ∷ Regex
122−endRegex = unsafeRegex "--end here" RegexFlags.global
123−
124−rawSegments ∷ String -> Array String
125−rawSegments = Regex.split holeRegex
126−
127−toSegment ∷ String -> Segment
128−toSegment = case _ of
129− x
130− | Regex.test holeRegex x -> Hole (S.length x - 4) ""
131− x
132− | S.indexOf (Pattern "--result ") x == Just 0 -> ExpectedResult ((S.stripPrefix (Pattern "--result ") x) ?|| "")
133− x
134− | Regex.test startRegex x -> Start
135− x
136− | Regex.test endRegex x -> End
137− other -> Filler other
138−
139−-- Glues together Filler segments
140−-- [[Filler "line1"], [Filler "line2"]] --> [[Filler "line1\nline2"]]
141−-- [[Filler "line1", Filler "line2"]] --> [[Filler "line1\nline2"]]
142−-- [[Filler "line1", Filler "line2"], [Filler "line3"]] --> [[Filler "line1\nline2\line3"]]
143−smooshFillers ∷ Array (Array Segment) -> Array (Array Segment)
144−smooshFillers = foldl smooshOuter []
145− where
146− smooshInner segments segment = case segment, A.unsnoc segments of
147− Filler f, Just { init, last: Filler prev } -> A.snoc init (Filler (prev <> "\n" <> f))
148− _, _ -> A.snoc segments segment
149− smooshOuter acc segs = case foldl smooshInner [] segs, A.unsnoc acc of
150− [ Filler f ], Just { init, last: [ Filler prev ] } -> A.snoc init [ Filler (prev <> "\n" <> f) ]
151− _, _ -> A.snoc acc segs
152−
153−type Ctx r
154− = (Compiler r)
155−
156−makeComponent ∷ { | Ctx () } -> Effect (ReactComponent { code ∷ String })
157−makeComponent { compileAndRun } = do
46+makeComponent ∷ Effect (ReactComponent { initialSegments ∷ Array (Array Segment), update ∷ Array (Array Segment) -> Effect Unit })
47+makeComponent = do
15848 ic <- InlineCode.makeComponent
159− modal <- Modal.makeComponent
160− closeIcon <- CloseIcon.makeComponent
161− cluster <- Cluster.makeComponent
162− clickAway <- ClickAway.makeComponent
163− button <- mkButton
164− component "FillInTheGaps" \{ code } -> React.do
165− let
166− lines = split (Pattern "\n") code
167−
168− initialSegments = smooshFillers (lines <#> \line -> rawSegments line <#> toSegment)
49+ component "FillInTheGaps" \{ initialSegments, update } -> React.do
16950 segments /\ modifySegments <- useState initialSegments
170− result /\ modifyResult <- useState Nothing
171− cssTheme <- useTheme
172− let
173− highlighterTheme = mkHighlighterTheme cssTheme
174− modalTransitions <-
175− useTransition [ result ] (Just show)
176− $ css
177− { from: { opacity: 0.0, transform: "translate3d(-50%, -50%, 0) scale3d(0.3, 0.3, 1.0)" }
178− , enter: { opacity: 1.0, transform: "translate3d(-50%, -50%, 0) scale3d(1.0, 1.0, 1.0)" }
179− , leave: { opacity: 0.0, transform: "translate3d(-50%, -50%, 0) scale3d(0.3, 0.3, 1.0)" }
180− , config:
181− mkFn2 \_ state -> case state of
182− "leave" -> { mass: 1.0, tension: 140, friction: 15 }
183− _ -> { mass: 1.0, tension: 170, friction: 20 }
184− }
185− clickAwayTransitions <-
186− useTransition [ result ] (Just show)
187− $ css
188− { from: { opacity: 0.0 }
189− , enter: { opacity: 1.0 }
190− , leave: { opacity: 0.0 }
191− , config:
192− mkFn2 \_ state -> case state of
193− "leave" -> { mass: 1.0, tension: 140, friction: 15 }
194− _ -> { mass: 1.0, tension: 170, friction: 20 }
195− }
196− let
197− expectedResult = findResult (join segments)
198−
199− onClick =
200− launchAff_ do
201− do
202− res <- compileAndRun { code: toCode segments }
203− modifyResult (const (Just $ res)) # liftEffect
204− pure
205− $ fragment
206− $ [ renderSegments highlighterTheme ic (modifySegments) segments
207− , jsx cluster {}
208− [ R.div_
209− [ jsx button
210− { onClick: handler_ onClick
211− , buttonType:
212− if complete segments && isNothing result then
213− HighlightedButton
214− else
215− DisabledButton
216− }
217− [ R.text "Incantate" ]
218− ]
219− ]
220− , fragment
221− $ clickAwayTransitions
222− >>= \{ item, key, props } ->
223− guard (isJust item)
224− $ join item
225− # foldMap \_ ->
226− [ element clickAway (justifill { allowEscape: true, style: props, onClick: modifyResult (const Nothing) }) ]
227− , fragment
228− $ modalTransitions
229− >>= \{ item, key, props } ->
230− guard (isJust item)
231− $ join item
232− # foldMap \r ->
233− [ jsx modal
234− { title:
235− case r of
236− (Right { stdout })
237− | S.stripSuffix (S.Pattern "\n") stdout == Just expectedResult -> "Hooray!"
238− (Left l) -> "Does not compile"
239− (Right _) -> "Not " <> (findResult $ join segments)
240− , icon: element closeIcon { onClick: modifyResult (const Nothing), style: Nothing }
241− , style: props
242− }
243− [ R.pre_ [ R.text $ compileResultToString result ] ]
244− ]
245− ]
246−
247−compileResultToString ∷ Maybe (Either CompileResult RunResult) -> String
248−compileResultToString = case _ of
249− Nothing -> ""
250− Just (Left cr) -> cr.result <#> _.message # intercalate "\n"
251− Just (Right r) -> r.stdout
51+ useAff segments do
52+ delay (200.0 # Milliseconds)
53+ update segments # liftEffect
54+ highlighterTheme <- useTheme <#> mkHighlighterTheme
55+ pure $ renderSegments highlighterTheme ic (modifySegments) segments
components/src/Yoga/FillInTheGaps/Logic.pursnew file+118
@@ -0,0 +1,118 @@
1+module Yoga.FillInTheGaps.Logic where
2+
3+import Prelude
4+import Data.Array (intercalate)
5+import Data.Array as A
6+import Data.Either (Either(..))
7+import Data.Foldable (foldl)
8+import Data.Lens ((%~))
9+import Data.Lens.Index (ix)
10+import Data.Maybe (Maybe(..), fromMaybe')
11+import Data.String (Pattern(..), split)
12+import Data.String as S
13+import Data.String.Regex (Regex)
14+import Data.String.Regex as Regex
15+import Data.String.Regex.Flags as RegexFlags
16+import Data.String.Regex.Unsafe (unsafeRegex)
17+import Data.Tuple.Nested ((/\))
18+import Debug.Trace (spy)
19+import Effect (Effect)
20+import Justifill (justifill)
21+import Partial.Unsafe (unsafeCrashWith)
22+import React.Basic (JSX, ReactComponent, element)
23+import React.Basic.DOM as R
24+import React.Basic.Hooks (component, useState)
25+import React.Basic.Hooks as React
26+import React.Basic.SyntaxHighlighter.Component (HighlighterTheme, syntaxHighlighterImpl)
27+import Shared.Models.Body (CompileResult, RunResult)
28+import Yoga.Helpers ((?||))
29+import Yoga.InlineCode.Component as InlineCode
30+import Yoga.Theme.Styles (useTheme)
31+import Yoga.Theme.Syntax (mkHighlighterTheme)
32+
33+complete ∷ Array (Array Segment) -> Boolean
34+complete arr = foldl f true (join arr)
35+ where
36+ f acc seg =
37+ acc
38+ && case seg of
39+ Hole _ s -> s /= ""
40+ _ -> true
41+
42+data Segment
43+ = ExpectedResult String
44+ | Start
45+ | End
46+ | Filler String
47+ | Hole Int String
48+
49+derive instance eqSegment ∷ Eq Segment
50+getResult ∷ Segment -> Maybe String
51+getResult = case _ of
52+ ExpectedResult r -> Just r
53+ _ -> Nothing
54+
55+findResult ∷ Array Segment -> String
56+findResult = fromMaybe' (\_ -> unsafeCrashWith "Even teachers make mistakes") <<< A.findMap getResult
57+
58+toCode ∷ Array (Array Segment) -> String
59+toCode lines = intercalate "\n" mapped
60+ where
61+ mapped = lines <#> (intercalate "" <<< map segmentToCode)
62+ segmentToCode = case _ of
63+ Filler s -> s
64+ Hole _ s -> s
65+ _ -> ""
66+
67+holeRegex ∷ Regex
68+holeRegex = unsafeRegex "({-.*?-})" RegexFlags.global
69+
70+resultRegex ∷ Regex
71+resultRegex = unsafeRegex "--result" RegexFlags.global
72+
73+startRegex ∷ Regex
74+startRegex = unsafeRegex "--start here" RegexFlags.global
75+
76+endRegex ∷ Regex
77+endRegex = unsafeRegex "--end here" RegexFlags.global
78+
79+rawSegments ∷ String -> Array String
80+rawSegments = Regex.split holeRegex
81+
82+toSegment ∷ String -> Segment
83+toSegment = case _ of
84+ x
85+ | Regex.test holeRegex x -> Hole (S.length x - 4) ""
86+ x
87+ | S.indexOf (Pattern "--result ") x == Just 0 -> ExpectedResult ((S.stripPrefix (Pattern "--result ") x) ?|| "")
88+ x
89+ | Regex.test startRegex x -> Start
90+ x
91+ | Regex.test endRegex x -> End
92+ other -> Filler other
93+
94+updateSegments ∷ Int -> Int -> String -> Array (Array Segment) -> Array (Array Segment)
95+updateSegments i j v = (ix i <<< ix j) %~ f
96+ where
97+ f = case _ of
98+ Hole h _ -> Hole h v
99+ _ -> unsafeCrashWith "Updated a non-hole"
100+
101+-- Glues together Filler segments
102+-- [[Filler "line1"], [Filler "line2"]] --> [[Filler "line1\nline2"]]
103+-- [[Filler "line1", Filler "line2"]] --> [[Filler "line1\nline2"]]
104+-- [[Filler "line1", Filler "line2"], [Filler "line3"]] --> [[Filler "line1\nline2\line3"]]
105+smooshFillers ∷ Array (Array Segment) -> Array (Array Segment)
106+smooshFillers = foldl smooshOuter []
107+ where
108+ smooshInner segments segment = case segment, A.unsnoc segments of
109+ Filler f, Just { init, last: Filler prev } -> A.snoc init (Filler (prev <> "\n" <> f))
110+ _, _ -> A.snoc segments segment
111+ smooshOuter acc segs = case foldl smooshInner [] segs, A.unsnoc acc of
112+ [ Filler f ], Just { init, last: [ Filler prev ] } -> A.snoc init [ Filler (prev <> "\n" <> f) ]
113+ _, _ -> A.snoc acc segs
114+
115+parseSegments code = segments
116+ where
117+ lines = split (Pattern "\n") code
118+ segments = smooshFillers (lines <#> \line -> rawSegments line <#> toSegment)
components/src/Yoga/FillInTheGaps/Stories.purs+5−16
@@ -1,30 +1,19 @@
11module Yoga.FillInTheGaps.Stories where
22
33import Prelude hiding (add)
4−import Data.Either (Either(..))
5−import Data.Maybe (Maybe(..))
64import Data.String as String
75import Effect (Effect)
86import Storybook.Decorator.FullScreen (fullScreenDecorator)
97import Storybook.React (Storybook, add, addDecorator, storiesOf)
108import Yoga.FillInTheGaps.Component as FillInTheGaps
11−
12−ctx ∷ { | FillInTheGaps.Ctx () }
13−ctx =
14− { compileAndRun
15− }
16− where
17− compileAndRun = case _ of
18− { code }
19− | code == correctCode -> pure (Right { code: Nothing, stdout: "Hello World\n", stderr: "" })
20− other -> pure (Right { code: Nothing, stdout: "Not hello world", stderr: "" })
9+import Yoga.FillInTheGaps.Logic (parseSegments)
2110
2211stories ∷ Effect Storybook
2312stories = do
2413 storiesOf "FillInTheGaps" do
2514 addDecorator fullScreenDecorator
26− add "The FillInTheGaps" (FillInTheGaps.makeComponent ctx)
27− [ { code: codeWithHoles }
15+ add "The FillInTheGaps" (FillInTheGaps.makeComponent)
16+ [ { initialSegments: parseSegments codeWithHoles, update: pure (pure unit) }
2817 ]
2918
3019codeWithHoles =
@@ -33,9 +22,9 @@ codeWithHoles =
3322module Main where
3423import Grimoire
3524
36−main :: Effect Unit
25+incantation :: Effect Unit
3726--start here
38−main = cast
27+incantation = cast
3928 "{-Hello World-}"
4029--end here
4130"""