@@ -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