-
Notifications
You must be signed in to change notification settings - Fork 21
/
Copy pathTodos.hs
179 lines (154 loc) · 6.81 KB
/
Todos.hs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE OverloadedStrings #-}
module Main where
import Control.Monad (forever, void)
import Control.Monad.State (StateT, execStateT, get, lift, put)
import qualified Data.JSString as JSS
import Data.List (intersperse)
import Data.String (fromString)
import Data.Void
import qualified GHCJS.VDOM.Attribute as A
import qualified GHCJS.VDOM.Element as E
import qualified GHCJS.VDOM.Event as Ev
import Concur.Core (Widget, orr, never)
import Concur.VDOM (HTML, classList, clickEl, el,
elEvent, el_, initConcur,
inputEnter, runWidgetInBody,
text)
main :: IO ()
main = do
-- This needs to be called once at the very beginning
initConcur
-- Run widget
runWidgetInBody $ void $ flip execStateT startEntries widgetTodos
-----------------------------------------------------
-- Data Structures, and associated pure operations --
-----------------------------------------------------
data Entry = Entry
{ entryDescription :: String
, entryCompleted :: Bool
}
data EntriesVisibility = All | Active | Completed
deriving (Show, Eq)
data EntriesList = EntriesList
{ entriesVisibility :: EntriesVisibility
, entriesList :: [Entry]
}
startEntries :: EntriesList
startEntries = EntriesList All []
entriesCompleted :: EntriesList -> Int
entriesCompleted = length . filter entryCompleted . entriesList
entriesLeft :: EntriesList -> Int
entriesLeft elist = length (entriesList elist) - entriesCompleted elist
-------------
-- Widgets --
-------------
type EntriesWidget a = StateT EntriesList (Widget HTML) a
widgetTodos :: EntriesWidget a
widgetTodos = forever $ el E.div [ A.class_ "todomvc-wrapper" ]
[ el E.section [A.class_ "todoapp"] [widgetInput, widgetEntries, widgetControls]
, lift $ el E.footer [ A.class_ "info" ]
[ el E.p [] [text "Double-click to edit a todo"]
, el E.p []
[ text "Written by "
, el E.a [ A.href "https://github.com/ajnsit" ] [ text "Anupam Jain" ]
]
, el E.p []
[ text "Part of "
, el E.a [ A.href "https://todomvc.com" ] [ text "TodoMVC" ]
]
]
]
widgetInput :: EntriesWidget ()
widgetInput = el E.header
[ A.class_ "header" ]
[ el E.h1 [] [lift $ text "todos"]
, do
elist <- get
s <- lift $ inputEnter [A.class_ "new-todo", A.placeholder "What needs to be done?", A.autofocus "autofocus", A.name "newTodo", A.value ""]
put $ elist { entriesList = Entry s False : entriesList elist }
]
widgetEntries :: EntriesWidget ()
widgetEntries = do
elist <- get
el E.section [ classList [("main", True), ("hidden", null $ entriesList elist)] ]
[ lift (allCompletedToggle (allCompleted elist)) >>= put . flip markAllComplete elist
, el_ E.ul [ A.class_ "todo-list" ] $ elistToEntriesListWidget elist
]
where
addChecked v l = if v then (A.checked "checked" : l) else l
allCompletedToggle v = clickEl E.input (addChecked v [A.type_ "checkbox", A.class_ "toggle-all", A.name "toggle"]) (const $ not v) []
elistToEntriesListWidget elist = orr $ map (numberedEntryToWidget elist) $ filter (isEntryVisible (entriesVisibility elist) . snd) $ zip [0..] $ entriesList elist
numberedEntryToWidget elist (i,e) = do
let l = entriesList elist
me <- lift $ widgetEntry e
put $ elist { entriesList = case me of
Nothing -> take i l ++ drop (i+1) l
Just e' -> take i l ++ [e'] ++ drop (i+1) l
}
isEntryVisible Completed = entryCompleted
isEntryVisible Active = not . entryCompleted
isEntryVisible All = const True
allCompleted elist = entriesLeft elist <= 0
markAllComplete v elist = elist { entriesList = map (\e -> e {entryCompleted = v}) (entriesList elist) }
widgetEntry :: Entry -> Widget HTML (Maybe Entry)
widgetEntry todo = go False
where
go editing = ego editing >>= either go return
ego editing = do
el E.li [ classList [ ("completed", completed), ("editing", editing) ] ] $
[ el E.div [ A.class_ "view" ] $
[ clickEl E.input (addChecked completed [A.type_ "checkbox", A.class_ "toggle"])
(\_e -> Right $ Just $ todo { entryCompleted = not completed }) []
, either (const $ Left $ not editing) absurd <$> elEvent Ev.dblclick E.label [] (text desc)
, clickEl E.button [A.class_ "destroy"] (const $ Right Nothing) []
]
, if editing
then fmap (\desc' -> Right $ Just $ todo { entryDescription = desc' }) $
inputEnter [A.autofocus "autofocus", A.class_ "edit", A.name "title", A.value $ JSS.pack desc]
else never
]
completed = entryCompleted todo
desc = entryDescription todo
addChecked v l = if v then (A.checked "checked" : l) else l
widgetControls :: EntriesWidget ()
widgetControls = do
elist <- get
el E.footer
[ classList [("footer", True), ("hidden", null $ entriesList elist)] ]
[ widgetControlsCount , widgetControlsFilters , widgetControlsClear ]
widgetControlsCount :: EntriesWidget a
widgetControlsCount = do
elist <- get
el E.span
[ A.class_ "todo-count" ]
[ el E.strong [] [ lift $ text (fromString $ show $ entriesLeft elist) ]
, lift $ text $ (if entriesLeft elist == 1 then " item" else " items") ++ " left"
]
widgetControlsFilters :: EntriesWidget ()
widgetControlsFilters = el_ E.ul [ A.class_ "filters" ] $ do
elist <- get
newVisibility <- lift $ visibilityWidget $ entriesVisibility elist
put $ elist { entriesVisibility = newVisibility }
where
visibilityWidget :: EntriesVisibility -> Widget HTML EntriesVisibility
visibilityWidget visibility = orr $ intersperse (text " ") $
[ visibilitySwap "#/" "All" All visibility
, visibilitySwap "#/active" "Active" Active visibility
, visibilitySwap "#/completed" "Completed" Completed visibility
]
visibilitySwap :: String -> String -> EntriesVisibility -> EntriesVisibility -> Widget HTML EntriesVisibility
visibilitySwap uri label visibility actualVisibility = do
clickEl E.li [] (const visibility)
[ el_ E.a
[ A.href $ fromString uri, classList [("selected", visibility == actualVisibility)] ] $
text label
]
widgetControlsClear :: EntriesWidget ()
widgetControlsClear = do
elist <- get
_ <- lift $ clickEl E.button
[ classList [("clear-completed", True), ("hidden", entriesCompleted elist == 0)] ]
(const ())
[ text ("Clear completed (" ++ fromString (show $ entriesCompleted elist) ++ ")") ]
put $ elist { entriesList = filter (not . entryCompleted) (entriesList elist) }