1module FillInTheGaps where
5import Data.Array (foldMap, intercalate)
7import Data.Either (Either(..))
8import Data.Foldable (foldl)
9import Data.Function.Uncurried (mkFn2)
10import Data.Lens ((%~))
11import Data.Lens.Index (ix)
12import Data.Maybe (Maybe(..), fromMaybe', isJust, isNothing)
13import Data.Monoid (guard)
14import Data.String (Pattern(..), split)
15import Data.String as S
16import Data.String.Regex (Regex)
17import Data.String.Regex as Regex
18import Data.String.Regex.Flags as RegexFlags
19import Data.String.Regex.Unsafe (unsafeRegex)
20import Data.Tuple.Nested ((/\))
21import Debug.Trace (spy)
23import Effect.Aff (launchAff_)
24import Effect.Class (liftEffect)
25import Justifill (justifill)
27import Milkis.Impl (FetchImpl)
28import Partial.Unsafe (unsafeCrashWith)
29import React.Basic (JSX, ReactComponent, element, fragment)
30import React.Basic.DOM (css)
31import React.Basic.DOM as R
32import React.Basic.DOM.Events (preventDefault)
33import React.Basic.Events (handler, handler_)
34import React.Basic.Helpers (jsx)
35import React.Basic.Hooks (component, useState)
36import React.Basic.Hooks as React
37import React.Basic.Hooks.Spring (animatedDiv, useTransition)
38import Shared.Models.Body (CompileResult, RunResult)
39import Yoga.Button.Component (ButtonType(..), mkButton)
40import Yoga.ClickAway.Component as ClickAway
41import Yoga.CloseIcon.Component as CloseIcon
42import Yoga.Cluster.Component as Cluster
43import Yoga.CompileEditor.Component (compileAndRun)
44import Yoga.Helpers ((?||))
45import Yoga.InlineCode.Component as InlineCode
46import Yoga.Modal.Component as Modal
49 = ExpectedResult String
55derive instance eqSegment ∷ Eq Segment
56getResult ∷ Segment -> Maybe String
58 ExpectedResult r -> Just r
61findResult ∷ Array Segment -> String
62findResult = fromMaybe' (\_ -> unsafeCrashWith "Even teachers make mistakes") <<< A.findMap getResult
64toCode ∷ (Array (Array Segment)) -> String
65toCode lines = intercalate "\n" mapped
67 mapped = lines <#> (intercalate "" <<< map segmentToCode)
68 segmentToCode = case _ of
73complete ∷ (Array (Array Segment)) -> Boolean
74complete arr = foldl f true (join arr)
82visibleRange ∷ Array (Array Segment) -> { end ∷ Int, start ∷ Int }
83visibleRange arr = { start, end }
85 start = A.findIndex (_ == [ Start ]) arr ?|| 0
86 end = A.findIndex (_ == [ End ]) arr ?|| A.length arr
88renderSegments ∷ ReactComponent InlineCode.Props -> ((Array (Array Segment) -> Array (Array Segment)) -> Effect Unit) -> Array (Array Segment) -> JSX
89renderSegments ic update arrs = R.div_ (A.mapWithIndex renderLine arrs)
91 { start, end } = visibleRange arrs
92 renderLine i l = R.div_ (A.mapWithIndex (renderSegment i) l)
94 if between start end i then case s of
95 Filler s' -> R.code_ [ R.text s' ]
100 , onSubmit: update <<< updateSegments i j
106updateSegments ∷ Int -> Int -> String -> Array (Array Segment) -> Array (Array Segment)
107updateSegments i j v = (ix i <<< ix j) %~ f
111 _ -> unsafeCrashWith "Updated a non-hole"
114holeRegex = unsafeRegex "({-.*?-})" RegexFlags.global
117resultRegex = unsafeRegex "--result" RegexFlags.global
120startRegex = unsafeRegex "--start here" RegexFlags.global
123endRegex = unsafeRegex "--end here" RegexFlags.global
125rawSegments ∷ String -> Array String
126rawSegments = Regex.split holeRegex
128toSegment ∷ String -> Segment
131 | Regex.test holeRegex x -> Hole (S.length x - 4) ""
133 | S.indexOf (Pattern "--result ") x == Just 0 -> ExpectedResult ((S.stripPrefix (Pattern "--result ") x) ?|| "")
135 | Regex.test startRegex x -> Start
137 | Regex.test endRegex x -> End
138 other -> Filler other
140mkFillInTheGaps ∷ FetchImpl -> Effect (ReactComponent { code ∷ String })
141mkFillInTheGaps fetch = do
142 ic <- InlineCode.makeComponent
143 modal <- Modal.makeComponent
144 closeIcon <- CloseIcon.makeComponent
145 cluster <- Cluster.makeComponent
146 clickAway <- ClickAway.makeComponent
148 component "FillInTheGaps" \{ code } -> React.do
150 lines = split (Pattern "\n") code
152 initialSegments = lines <#> \line -> rawSegments line <#> toSegment
153 segments /\ modifySegments <- useState initialSegments
154 result /\ modifyResult <- useState Nothing
156 useTransition [ result ] (Just show)
158 { from: { opacity: 0.0, transform: "translate3d(-50%, -50%, 0) scale3d(0.3, 0.3, 1.0)" }
159 , enter: { opacity: 1.0, transform: "translate3d(-50%, -50%, 0) scale3d(1.0, 1.0, 1.0)" }
160 , leave: { opacity: 0.0, transform: "translate3d(-50%, -50%, 0) scale3d(0.3, 0.3, 1.0)" }
161 , config: mkFn2 \_ state ->
162 case spy "state" state of
163 "leave" -> { mass: 1.0, tension: 140, friction: 15 }
164 _ -> { mass: 1.0, tension: 170, friction: 20 }
166 clickAwayTransitions <-
167 useTransition [ result ] (Just show)
169 { from: { opacity: 0.0 }
170 , enter: { opacity: 1.0 }
171 , leave: { opacity: 0.0 }
172 , config: mkFn2 \_ state ->
173 case spy "state" state of
174 "leave" -> { mass: 1.0, tension: 140, friction: 15 }
175 _ -> { mass: 1.0, tension: 170, friction: 20 }
178 expectedResult = findResult (join segments)
183 res <- compileAndRun (M.fetch fetch) { code: toCode segments }
184 modifyResult (const (Just $ res)) # liftEffect
187 $ [ renderSegments ic (modifySegments) segments
191 { onClick: handler_ onClick
193 if complete segments && isNothing result then
202 $ clickAwayTransitions
203 >>= \{ item, key, props } ->
207 [element clickAway (justifill { allowEscape: true, style: props, onClick: modifyResult (const Nothing) })]
210 >>= \{ item, key, props } ->
218 | S.stripSuffix (S.Pattern "\n") stdout == Just expectedResult -> "Hooray!"
219 (Left l) -> "Does not compile"
220 (Right _) -> "Not " <> (findResult $ join segments)
221 , icon: element closeIcon { onClick: modifyResult (const Nothing), style: Nothing }
224 [ R.text $ compileResultToString result ]
228compileResultToString ∷ Maybe (Either CompileResult RunResult) -> String
229compileResultToString = case _ of
231 Just (Left cr) -> cr.result <#> _.message # intercalate "/n"
232 Just (Right r) -> r.stdout
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list