Skip to content

Commit 0f6ff50

Browse files
committed
Make miso todo-mvc use Miso.Lens.
1 parent f51fd55 commit 0f6ff50

3 files changed

Lines changed: 83 additions & 105 deletions

File tree

flake.lock

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

miso-todomvc.cabal

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ executable app
4242
main-is:
4343
Main.hs
4444
build-depends:
45-
base, miso, mtl
45+
base, miso, mtl, containers
4646
default-language:
4747
GHC2021
4848
default-extensions:

src/Main.hs

Lines changed: 79 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@
22
{-# LANGUAGE CPP #-}
33
{-# LANGUAGE LambdaCase #-}
44
{-# LANGUAGE DeriveGeneric #-}
5-
{-# LANGUAGE RecordWildCards #-}
65
{-# LANGUAGE DeriveAnyClass #-}
6+
{-# LANGUAGE TemplateHaskell #-}
7+
{-# LANGUAGE RecordWildCards #-}
78
{-# LANGUAGE OverloadedStrings #-}
89
{-# LANGUAGE DerivingStrategies #-}
910
-----------------------------------------------------------------------------
@@ -17,61 +18,60 @@
1718
----------------------------------------------------------------------------
1819
module Main where
1920
----------------------------------------------------------------------------
20-
import Control.Monad.State
21+
import Control.Category ((.))
22+
import qualified Data.IntMap as IM
23+
import Data.IntMap (IntMap)
2124
import Data.Bool
2225
import GHC.Generics
26+
import Prelude hiding ((.))
2327
----------------------------------------------------------------------------
24-
import Miso
28+
import Miso hiding (at)
2529
import Miso.Html
26-
import Miso.JSON
30+
import Miso.Lens
31+
import Miso.Lens.TH
2732
import Miso.Html.Property hiding (label_)
2833
import qualified Miso.String as S
2934
import qualified Miso.CSS as CSS
3035
----------------------------------------------------------------------------
3136
default (MisoString)
3237
----------------------------------------------------------------------------
33-
#ifdef WASM
34-
foreign export javascript "hs_start" main :: IO ()
35-
#endif
36-
----------------------------------------------------------------------------
3738
data Model
3839
= Model
39-
{ entries :: [Entry]
40-
, field :: MisoString
41-
, uid :: Int
42-
, visibility :: MisoString
43-
, step :: Bool
40+
{ _entries :: IntMap Entry
41+
, _field :: MisoString
42+
, _uid :: Int
43+
, _visibility :: MisoString
44+
, _step :: Bool
4445
} deriving stock (Show, Generic, Eq)
45-
deriving anyclass (FromJSON, ToJSON)
4646
----------------------------------------------------------------------------
4747
data Entry
4848
= Entry
49-
{ description :: MisoString
50-
, completed :: Bool
51-
, editing :: Bool
52-
, eid :: Int
53-
, focussed :: Bool
49+
{ _description :: MisoString
50+
, _completed :: Bool
51+
, _editing :: Bool
52+
, _focussed :: Bool
5453
} deriving stock (Show, Generic, Eq)
55-
deriving anyclass (FromJSON, ToJSON)
54+
----------------------------------------------------------------------------
55+
$(makeLenses ''Entry)
56+
$(makeLenses ''Model)
5657
----------------------------------------------------------------------------
5758
emptyModel :: Model
5859
emptyModel
5960
= Model
60-
{ entries = []
61-
, visibility = "All"
62-
, field = mempty
63-
, uid = 0
64-
, step = False
61+
{ _entries = mempty
62+
, _visibility = "All"
63+
, _field = mempty
64+
, _uid = 0
65+
, _step = False
6566
}
6667
----------------------------------------------------------------------------
67-
newEntry :: MisoString -> Int -> Entry
68-
newEntry desc eid
68+
newEntry :: MisoString -> Entry
69+
newEntry desc
6970
= Entry
70-
{ description = desc
71-
, completed = False
72-
, editing = False
73-
, eid = eid
74-
, focussed = False
71+
{ _description = desc
72+
, _completed = False
73+
, _editing = False
74+
, _focussed = False
7575
}
7676
----------------------------------------------------------------------------
7777
data Msg
@@ -89,6 +89,12 @@ data Msg
8989
| FocusOnInput
9090
deriving (Show)
9191
----------------------------------------------------------------------------
92+
#ifdef WASM
93+
#ifndef INTERACTIVE
94+
foreign export javascript "hs_start" main :: IO ()
95+
#endif
96+
#endif
97+
----------------------------------------------------------------------------
9298
main :: IO ()
9399
main = startApp (defaultEvents <> keyboardEvents) app
94100
----------------------------------------------------------------------------
@@ -111,72 +117,48 @@ updateModel = \case
111117
FocusOnInput ->
112118
io_ (focus "input-box")
113119
CurrentTime time ->
114-
io_ $ consoleLog $ S.ms (show time)
120+
io_ $ consoleLog (S.ms time)
115121
Add -> do
116-
model@Model{..} <- get
117-
put model
118-
{ uid = uid + 1
119-
, field = mempty
120-
, entries = entries <> [newEntry field uid | not $ S.null field]
121-
}
122-
UpdateField str ->
123-
modify update
124-
where
125-
update m = m { field = str }
126-
EditingEntry id' isEditing ->
127-
modify $ \m ->
128-
m { entries =
129-
filterMap (entries m) (\t -> eid t == id') $ \t ->
130-
t { editing = isEditing
131-
, focussed = isEditing
132-
}
133-
}
134-
UpdateEntry id' task ->
135-
modify $ \m -> m
136-
{ entries = filterMap (entries m) ((== id') . eid) $ \t ->
137-
t { description = task }
138-
}
139-
Delete id' ->
140-
modify $ \m -> m
141-
{ entries = filter (\t -> eid t /= id') (entries m)
142-
}
122+
value <- use field
123+
field .= mempty
124+
uid += 1
125+
nextId <- use uid
126+
entries %= IM.insert nextId (newEntry value)
127+
UpdateField str -> do
128+
field .= str
129+
EditingEntry idx isEditing ->
130+
entries . at idx %?= (\e ->
131+
e & editing .~ isEditing
132+
& focussed .~ isEditing)
133+
UpdateEntry idx task ->
134+
entries . at idx %?= do
135+
description .~ task
136+
Delete idx ->
137+
entries . at idx .= Nothing
143138
DeleteComplete ->
144-
modify $ \m -> m
145-
{ entries = filter (not . completed) (entries m)
146-
}
147-
Check id' isCompleted ->
148-
modify $ \m -> m
149-
{ entries =
150-
filterMap (entries m) (\t -> eid t == id') $ \t ->
151-
t { completed = isCompleted }
152-
}
139+
entries %= IM.filter (\entry -> not (entry ^. completed))
140+
Check idx isCompleted ->
141+
entries . at idx %?= do completed .~ isCompleted
153142
CheckAll isCompleted ->
154-
modify $ \m -> m
155-
{ entries =
156-
filterMap (entries m) (const True) $ \t ->
157-
t { completed = isCompleted }
158-
}
143+
entries %= IM.map (\entry -> entry & completed .~ isCompleted)
159144
ChangeVisibility v ->
160-
modify $ \m -> m { visibility = v }
161-
----------------------------------------------------------------------------
162-
filterMap :: [a] -> (a -> Bool) -> (a -> a) -> [a]
163-
filterMap xs p f = [ if p x then f x else x | x <- xs ]
145+
visibility .= v
164146
----------------------------------------------------------------------------
165147
viewModel :: Model -> View model Msg
166-
viewModel m@Model{..} =
148+
viewModel m =
167149
div_
168150
[ class_ "todomvc-wrapper"
169151
]
170152
[ section_
171153
[class_ "todoapp"]
172-
[ viewInput m field
173-
, viewEntries visibility entries
174-
, viewControls m visibility entries
154+
[ viewInput m (m ^. field)
155+
, viewEntries (m ^. visibility) (IM.toList (m ^. entries))
156+
, viewControls m (m ^. visibility) (IM.toList (m ^. entries))
175157
]
176158
, infoFooter
177159
]
178160
----------------------------------------------------------------------------
179-
viewEntries :: MisoString -> [Entry] -> View model Msg
161+
viewEntries :: MisoString -> [(Int, Entry)] -> View model Msg
180162
viewEntries visibility entries =
181163
section_
182164
[ class_ "main"
@@ -194,40 +176,36 @@ viewEntries visibility entries =
194176
[for_ "toggle-all"]
195177
[text $ S.pack "Mark all as complete"]
196178
, ul_ [class_ "todo-list"] $
197-
flip map (filter isVisible entries) $ \t ->
198-
viewKeyedEntry t
179+
filter isVisible entries <&> viewEntry
199180
]
200181
where
201182
cssVisibility = bool "visible" "hidden" (null entries)
202-
allCompleted = all completed entries
203-
isVisible Entry{..} =
183+
allCompleted = all _completed (snd <$> entries)
184+
isVisible (_, Entry {..}) =
204185
case visibility of
205-
"Completed" -> completed
206-
"Active" -> not completed
186+
"Completed" -> _completed
187+
"Active" -> not _completed
207188
_ -> True
208189
----------------------------------------------------------------------------
209-
viewKeyedEntry :: Entry -> View model Msg
210-
viewKeyedEntry = viewEntry
211-
----------------------------------------------------------------------------
212-
viewEntry :: Entry -> View model Msg
213-
viewEntry Entry{..} =
190+
viewEntry :: (Int, Entry) -> View model Msg
191+
viewEntry (eid, Entry{..}) =
214192
li_
215193
[ class_ $
216194
S.intercalate " " $
217-
["completed" | completed] <> ["editing" | editing]
195+
["completed" | _completed] <> ["editing" | _editing]
218196
, key_ eid
219197
]
220198
[ div_
221199
[class_ "view"]
222200
[ input_
223201
[ class_ "toggle"
224202
, type_ "checkbox"
225-
, checked_ completed
226-
, onClick $ Check eid (not completed)
203+
, checked_ _completed
204+
, onClick $ Check eid (not _completed)
227205
]
228206
, label_
229207
[onDoubleClick (EditingEntry eid True) ]
230-
[text description]
208+
[text _description]
231209
, button_
232210
[ class_ "destroy"
233211
, onClick $ Delete eid
@@ -236,7 +214,7 @@ viewEntry Entry{..} =
236214
]
237215
, input_
238216
[ class_ "edit"
239-
, value_ description
217+
, value_ _description
240218
, name_ "title"
241219
, id_ ("todo-" <> S.ms eid)
242220
, onInput (UpdateEntry eid)
@@ -245,7 +223,7 @@ viewEntry Entry{..} =
245223
]
246224
]
247225
----------------------------------------------------------------------------
248-
viewControls :: Model -> MisoString -> [Entry] -> View model Msg
226+
viewControls :: Model -> MisoString -> [(Int,Entry)] -> View model Msg
249227
viewControls model visibility entries =
250228
footer_
251229
[ class_ "footer"
@@ -256,7 +234,7 @@ viewControls model visibility entries =
256234
, viewControlsClear model entriesCompleted
257235
]
258236
where
259-
entriesCompleted = length . filter completed $ entries
237+
entriesCompleted = length . filter (_completed . snd) $ entries
260238
entriesLeft = length entries - entriesCompleted
261239
----------------------------------------------------------------------------
262240
viewControlsCount :: Int -> View model Msg

0 commit comments

Comments
 (0)