diff options
author | Gregor Kleen <gkleen@yggdrasil.li> | 2016-02-26 19:11:56 +0000 |
---|---|---|
committer | Gregor Kleen <gkleen@yggdrasil.li> | 2016-02-26 19:11:56 +0000 |
commit | 022a5a69dfcfc7b62a940d9c3070e6ae37cc993e (patch) | |
tree | 90261b972b67f084558021a6f399aeef65d480b6 | |
parent | 0a6e4d0cdee9aba48869361637613cb74c749289 (diff) | |
download | thermoprint-022a5a69dfcfc7b62a940d9c3070e6ae37cc993e.tar thermoprint-022a5a69dfcfc7b62a940d9c3070e6ae37cc993e.tar.gz thermoprint-022a5a69dfcfc7b62a940d9c3070e6ae37cc993e.tar.bz2 thermoprint-022a5a69dfcfc7b62a940d9c3070e6ae37cc993e.tar.xz thermoprint-022a5a69dfcfc7b62a940d9c3070e6ae37cc993e.zip |
Switched to bulk deletion/abortion
-rw-r--r-- | webgui/data/index.html | 6 | ||||
-rw-r--r-- | webgui/data/style.css | 24 | ||||
-rw-r--r-- | webgui/src/Main.hs | 121 |
3 files changed, 110 insertions, 41 deletions
diff --git a/webgui/data/index.html b/webgui/data/index.html index b677387..b0a2de2 100644 --- a/webgui/data/index.html +++ b/webgui/data/index.html | |||
@@ -59,7 +59,8 @@ | |||
59 | <tr> | 59 | <tr> |
60 | <td colspan="2" style="border-style:none;"></td> | 60 | <td colspan="2" style="border-style:none;"></td> |
61 | <td> | 61 | <td> |
62 | <input id="allowDeletion" type="checkbox" /><label for="allowDeletion">Allow Deletion</label> | 62 | <!-- <input id="allowDeletion" type="checkbox" /><label for="allowDeletion">Allow Deletion</label> --> |
63 | <button id="enactDeletion">Delete marked</button> | ||
63 | </td> | 64 | </td> |
64 | </tr> | 65 | </tr> |
65 | </tfoot> | 66 | </tfoot> |
@@ -88,7 +89,8 @@ | |||
88 | <tr> | 89 | <tr> |
89 | <td colspan="4" style="border-style:none;"></td> | 90 | <td colspan="4" style="border-style:none;"></td> |
90 | <td> | 91 | <td> |
91 | <input id="allowAbortion" type="checkbox" /><label for="allowAbortion">Allow Abortion</label> | 92 | <!-- <input id="allowAbortion" type="checkbox" /><label for="allowAbortion">Allow Abortion</label> --> |
93 | <button id="enactAbortion">Abort marked</button> | ||
92 | </td> | 94 | </td> |
93 | </tr> | 95 | </tr> |
94 | </tfoot> | 96 | </tfoot> |
diff --git a/webgui/data/style.css b/webgui/data/style.css index d496e2a..11c532c 100644 --- a/webgui/data/style.css +++ b/webgui/data/style.css | |||
@@ -90,12 +90,24 @@ tfoot tr:first-child td { | |||
90 | color:#c00000; | 90 | color:#c00000; |
91 | } | 91 | } |
92 | 92 | ||
93 | #draftListBody button { | 93 | tbody tr:nth-child(odd) td { |
94 | width:6em; | 94 | background-color:#f8f8f8; |
95 | } | ||
96 | |||
97 | tbody tr:nth-child(even) td { | ||
98 | background-color:#ffffff; | ||
99 | } | ||
100 | |||
101 | tbody tr.printer td { | ||
102 | background-color:initial; | ||
95 | } | 103 | } |
96 | 104 | ||
97 | tr.focused td { | 105 | tbody tr.focused td { |
98 | background-color:#f0f0f0; | 106 | background-color:#dfdfdf; |
107 | } | ||
108 | |||
109 | #draftListBody button { | ||
110 | width:6em; | ||
99 | } | 111 | } |
100 | 112 | ||
101 | .close { | 113 | .close { |
@@ -108,6 +120,10 @@ tr.focused td { | |||
108 | text-decoration:none; | 120 | text-decoration:none; |
109 | } | 121 | } |
110 | 122 | ||
123 | span.mark { | ||
124 | margin-right:1em; | ||
125 | } | ||
126 | |||
111 | /*----- Tabs -----*/ | 127 | /*----- Tabs -----*/ |
112 | .tabs { | 128 | .tabs { |
113 | display:block; | 129 | display:block; |
diff --git a/webgui/src/Main.hs b/webgui/src/Main.hs index 018e59b..a295fd9 100644 --- a/webgui/src/Main.hs +++ b/webgui/src/Main.hs | |||
@@ -21,6 +21,7 @@ import System.Environment | |||
21 | import Data.ByteString (ByteString) | 21 | import Data.ByteString (ByteString) |
22 | import qualified Data.ByteString as BS | 22 | import qualified Data.ByteString as BS |
23 | import qualified Data.ByteString.Char8 as CBS | 23 | import qualified Data.ByteString.Char8 as CBS |
24 | import qualified Data.ByteString.Lazy.Char8 as CLBS | ||
24 | 25 | ||
25 | import Data.Text (Text) | 26 | import Data.Text (Text) |
26 | import qualified Data.Text as T | 27 | import qualified Data.Text as T |
@@ -102,6 +103,13 @@ config = do | |||
102 | hostEnv = "ADDR" | 103 | hostEnv = "ADDR" |
103 | portEnv = "PORT" | 104 | portEnv = "PORT" |
104 | 105 | ||
106 | fatal :: String -> UI a | ||
107 | fatal str = do | ||
108 | window <- askWindow | ||
109 | (getBody window #) . set children =<< sequence [UI.p # set TP.text str # set UI.class_ "fatal"] | ||
110 | liftIO (throwIO $ ErrorCall str) | ||
111 | return undefined | ||
112 | |||
105 | setup :: Config -> Window -> Event (Either WebSocketException URI) -> UI () | 113 | setup :: Config -> Window -> Event (Either WebSocketException URI) -> UI () |
106 | setup Config{..} window (split -> (socketErr, dataUpdate)) = void $ do | 114 | setup Config{..} window (split -> (socketErr, dataUpdate)) = void $ do |
107 | onEvent socketErr handleSocketErr | 115 | onEvent socketErr handleSocketErr |
@@ -130,11 +138,6 @@ setup Config{..} window (split -> (socketErr, dataUpdate)) = void $ do | |||
130 | errors #+ [UI.li # set TP.text str] | 138 | errors #+ [UI.li # set TP.text str] |
131 | errorsTab # set style [("display", "inline-block")] | 139 | errorsTab # set style [("display", "inline-block")] |
132 | runFunction $ switchTab "errors" | 140 | runFunction $ switchTab "errors" |
133 | fatal :: String -> UI a | ||
134 | fatal str = do | ||
135 | (getBody window #) . set children =<< sequence [UI.p # set TP.text str # set UI.class_ "fatal"] | ||
136 | liftIO (throwIO $ ErrorCall str) | ||
137 | return undefined | ||
138 | 141 | ||
139 | maybeM = maybe $ return () | 142 | maybeM = maybe $ return () |
140 | 143 | ||
@@ -147,7 +150,9 @@ setup Config{..} window (split -> (socketErr, dataUpdate)) = void $ do | |||
147 | status <- stepper init statusEvent | 150 | status <- stepper init statusEvent |
148 | return (status, triggerStatusChange) | 151 | return (status, triggerStatusChange) |
149 | 152 | ||
150 | Client{..} = mkClient (Nat $ either (fatal . ("Error while communicating to Thermoprint.Server: " ++) . show) return <=< liftIO . runEitherT) server | 153 | Client{..} = mkClient (hoistNat $ Nat liftIO) server |
154 | withFatal :: EitherT ServantError UI a -> UI a | ||
155 | withFatal a = either (fatal . ("Error while communicating to Thermoprint.Server: " ++) . show) return =<< runEitherT a | ||
151 | 156 | ||
152 | handleEditor selectedPrinter (_, modifyFocusedJobs) = do | 157 | handleEditor selectedPrinter (_, modifyFocusedJobs) = do |
153 | title <- fatal' "Could not find editor title field" =<< getElementById window "editorTitle" | 158 | title <- fatal' "Could not find editor title field" =<< getElementById window "editorTitle" |
@@ -189,8 +194,8 @@ setup Config{..} window (split -> (socketErr, dataUpdate)) = void $ do | |||
189 | return False | 194 | return False |
190 | Right p -> do | 195 | Right p -> do |
191 | draftId <- case associatedDraft of | 196 | draftId <- case associatedDraft of |
192 | Nothing -> draftCreate (T.pack <$> eTitle) p | 197 | Nothing -> withFatal $ draftCreate (T.pack <$> eTitle) p |
193 | Just i -> i <$ when (different s) (draftReplace i (T.pack <$> eTitle) p) | 198 | Just i -> i <$ when (different s) (withFatal $ draftReplace i (T.pack <$> eTitle) p) |
194 | time <- liftIO getCurrentTime | 199 | time <- liftIO getCurrentTime |
195 | modifyStatus (\x -> x { associatedDraft = Just draftId, lastSaved = Just (time, s) }) | 200 | modifyStatus (\x -> x { associatedDraft = Just draftId, lastSaved = Just (time, s) }) |
196 | return True | 201 | return True |
@@ -217,9 +222,9 @@ setup Config{..} window (split -> (socketErr, dataUpdate)) = void $ do | |||
217 | Right po -> case associatedDraft of | 222 | Right po -> case associatedDraft of |
218 | Just dId -> do | 223 | Just dId -> do |
219 | saved <- saveAction False | 224 | saved <- saveAction False |
220 | when saved $ reFocusJob =<< draftPrint dId =<< currentValue selectedPrinter | 225 | when saved $ reFocusJob =<< withFatal . draftPrint dId =<< currentValue selectedPrinter |
221 | Nothing -> do | 226 | Nothing -> do |
222 | reFocusJob =<< flip jobCreate po =<< currentValue selectedPrinter | 227 | reFocusJob =<< withFatal . flip jobCreate po =<< currentValue selectedPrinter |
223 | Left err -> emitError $ "Could not print draft due to error parsing bbcode: " ++ show err | 228 | Left err -> emitError $ "Could not print draft due to error parsing bbcode: " ++ show err |
224 | 229 | ||
225 | onEvent (whenE saveDraft $ tick autoSaveTimer) (const . void $ saveAction True) | 230 | onEvent (whenE saveDraft $ tick autoSaveTimer) (const . void $ saveAction True) |
@@ -243,28 +248,52 @@ setup Config{..} window (split -> (socketErr, dataUpdate)) = void $ do | |||
243 | discardable EditorState{..} = not (maybe True null eTitle && null eText) | 248 | discardable EditorState{..} = not (maybe True null eTitle && null eText) |
244 | 249 | ||
245 | handleDraftTable changeEditorState = do | 250 | handleDraftTable changeEditorState = do |
246 | allowDeletion <- fatal' "Could not find deletion switch" =<< getElementById window "allowDeletion" | 251 | -- allowDeletion <- fatal' "Could not find deletion switch" =<< getElementById window "allowDeletion" |
247 | deletion' <- allowDeletion # get UI.checked | 252 | -- deletion' <- allowDeletion # get UI.checked |
253 | |||
254 | -- deletion <- stepper deletion' $ UI.checkedChange allowDeletion | ||
255 | (marking, (liftIO .) -> updateMarking) <- stepper' $ Set.empty | ||
248 | 256 | ||
249 | deletion <- stepper deletion' $ UI.checkedChange allowDeletion | 257 | |
258 | enactDeletion <- fatal' "Could not find deletion button" =<< getElementById window "enactDeletion" | ||
259 | on UI.click enactDeletion . const $ currentValue marking >>= mapM_ (runEitherT . draftDelete) >> updateMarking Set.empty | ||
260 | -- deletion' <- allowDeletion # get UI.checked | ||
250 | let | 261 | let |
262 | updateMarking' = callFunction (mangle <$> ffi getChecked) >>= updateMarking | ||
263 | where mangle = Set.fromList . map DraftId . fromMaybe [] . parse | ||
264 | getChecked = "$.makeArray($('input[name=draftMark]:checked').map(function() {return $(this).val()}))" | ||
265 | parse str | ||
266 | | [(i, rs)] <- [ (i, rs) | (i, ',' : rs) <- reads str ] = (:) <$> Just i <*> parse rs | ||
267 | | r@([_]) <- [ i | (i, "") <- reads str ] = Just r | ||
268 | | otherwise = Nothing | ||
269 | |||
251 | toTable :: Map DraftId (Maybe DraftTitle) -> UI [Element] | 270 | toTable :: Map DraftId (Maybe DraftTitle) -> UI [Element] |
252 | toTable = mapM toLine . Map.toList | 271 | toTable = mapM toLine . Map.toList |
253 | 272 | ||
254 | toLine (id@(DraftId (show -> tId)), fromMaybe "" . fmap T.unpack -> title) = do | 273 | toLine (id@(DraftId (show -> tId)), fromMaybe "" . fmap T.unpack -> title) = do |
255 | id' <- UI.td # set TP.text tId | 274 | id' <- UI.td # set TP.text tId |
256 | title' <- UI.td # set TP.text title | 275 | title' <- UI.td # set TP.text title |
257 | delete <- UI.button | 276 | mark <- UI.input |
258 | # set TP.text "Delete" | 277 | # set UI.type_ "checkbox" |
259 | # sink UI.enabled deletion | 278 | # set UI.name "draftMark" |
260 | on UI.click delete . const $ draftDelete id >> changeEditorState (\s@(EditorState{..}) -> if associatedDraft == Just id then def else s) | 279 | # set UI.id_ ("draftMark" ++ tId) |
280 | # set UI.value tId | ||
281 | # sink UI.checked (Set.member id <$> marking) | ||
282 | on UI.checkedChange mark . const $ updateMarking' | ||
283 | mark' <- UI.span #+ [ return mark | ||
284 | , UI.label # set UI.for ("draftMark" ++ tId) # set UI.text "Mark" | ||
285 | ] # set UI.class_ "mark" | ||
286 | -- delete <- UI.button | ||
287 | -- # set TP.text "Delete" | ||
288 | -- # sink UI.enabled deletion | ||
289 | -- on UI.click delete . const $ draftDelete id >> changeEditorState (\s@(EditorState{..}) -> if associatedDraft == Just id then def else s) | ||
261 | load <- UI.button | 290 | load <- UI.button |
262 | # set TP.text "Load" | 291 | # set TP.text "Load" |
263 | on UI.click load . const $ loadDraft id | 292 | on UI.click load . const $ loadDraft id |
264 | actions <- UI.td # set children [load, delete] | 293 | actions <- UI.td # set children [mark', load] |
265 | UI.tr # set children [id', title', actions] | 294 | UI.tr # set children [id', title', actions] |
266 | loadDraft id = do | 295 | loadDraft id = do |
267 | (title, po) <- draft id | 296 | (title, po) <- withFatal $ draft id |
268 | let | 297 | let |
269 | t = cobbcode po | 298 | t = cobbcode po |
270 | case t of | 299 | case t of |
@@ -281,7 +310,7 @@ setup Config{..} window (split -> (socketErr, dataUpdate)) = void $ do | |||
281 | changeEditorState (const newState) | 310 | changeEditorState (const newState) |
282 | runFunction $ switchTab "editor" | 311 | runFunction $ switchTab "editor" |
283 | table <- fatal' "Could not find draft table" =<< getElementById window "draftListBody" | 312 | table <- fatal' "Could not find draft table" =<< getElementById window "draftListBody" |
284 | initialContent <- toTable =<< drafts | 313 | initialContent <- toTable =<< withFatal drafts |
285 | return table # set children initialContent | 314 | return table # set children initialContent |
286 | 315 | ||
287 | update <- do | 316 | update <- do |
@@ -292,7 +321,7 @@ setup Config{..} window (split -> (socketErr, dataUpdate)) = void $ do | |||
292 | -- return $ unionWith const (() <$ filterE concernsDrafts dataUpdate) (tick recheckTimer) | 321 | -- return $ unionWith const (() <$ filterE concernsDrafts dataUpdate) (tick recheckTimer) |
293 | return $ filterE concernsDrafts dataUpdate | 322 | return $ filterE concernsDrafts dataUpdate |
294 | 323 | ||
295 | onEvent update . const $ drafts >>= toTable >>= (\c -> return table # set children c) | 324 | onEvent update . const $ withFatal drafts >>= toTable >>= (\c -> return table # set children c) |
296 | 325 | ||
297 | concernsDrafts :: URI -> Bool | 326 | concernsDrafts :: URI -> Bool |
298 | concernsDrafts (uriPath -> p) | 327 | concernsDrafts (uriPath -> p) |
@@ -307,10 +336,14 @@ setup Config{..} window (split -> (socketErr, dataUpdate)) = void $ do | |||
307 | | otherwise = False | 336 | | otherwise = False |
308 | 337 | ||
309 | handleJobTable (focusedJobs, _) = do | 338 | handleJobTable (focusedJobs, _) = do |
310 | allowAbortion <- do | 339 | -- allowAbortion <- do |
311 | allowAbortion <- fatal' "Could not find abortion switch" =<< getElementById window "allowAbortion" | 340 | -- allowAbortion <- fatal' "Could not find abortion switch" =<< getElementById window "allowAbortion" |
312 | flip stepper (UI.checkedChange allowAbortion) =<< (allowAbortion # get UI.checked) | 341 | -- flip stepper (UI.checkedChange allowAbortion) =<< (allowAbortion # get UI.checked) |
313 | 342 | (marking, (liftIO .) -> updateMarking) <- stepper' $ Set.empty | |
343 | |||
344 | enactAbortion <- fatal' "Could not find deletion button" =<< getElementById window "enactAbortion" | ||
345 | on UI.click enactAbortion . const $ currentValue marking >>= mapM_ (runEitherT . jobDelete) >> updateMarking Set.empty | ||
346 | |||
314 | (selectedPrinter, updatePrinter) <- do | 347 | (selectedPrinter, updatePrinter) <- do |
315 | autoselectPrinter <- fatal' "Could not find printer autoselect switch" =<< getElementById window "autoselectPrinter" | 348 | autoselectPrinter <- fatal' "Could not find printer autoselect switch" =<< getElementById window "autoselectPrinter" |
316 | (selectedPrinter, printerSelect) <- stepper' Nothing | 349 | (selectedPrinter, printerSelect) <- stepper' Nothing |
@@ -323,14 +356,22 @@ setup Config{..} window (split -> (socketErr, dataUpdate)) = void $ do | |||
323 | return (selectedPrinter, updatePrinterSelect) | 356 | return (selectedPrinter, updatePrinterSelect) |
324 | 357 | ||
325 | let | 358 | let |
359 | updateMarking' = callFunction (mangle <$> ffi getChecked) >>= updateMarking | ||
360 | where mangle = Set.fromList . map JobId . fromMaybe [] . parse | ||
361 | getChecked = "$.makeArray($('input[name=jobMark]:checked').map(function() {return $(this).val()}))" | ||
362 | parse str | ||
363 | | [(i, rs)] <- [ (i, rs) | (i, ',' : rs) <- reads str ] = (:) <$> Just i <*> parse rs | ||
364 | | r@([_]) <- [ i | (i, "") <- reads str ] = Just r | ||
365 | | otherwise = Nothing | ||
366 | |||
326 | -- getServerState :: UI [(PrinterId, PrinterStatus, [(JobId, UTCTime, JobStatus)])] | 367 | -- getServerState :: UI [(PrinterId, PrinterStatus, [(JobId, UTCTime, JobStatus)])] |
327 | getServerState = map mangleTuple . Map.toList <$> (Map.traverseWithKey (\pId status -> (,) status <$> getJobState pId) =<< printers) | 368 | getServerState = map mangleTuple . Map.toList <$> (Map.traverseWithKey (\pId status -> (,) status <$> getJobState pId) =<< withFatal printers) |
328 | -- getJobState :: PrinterId -> UI [(JobId, UTCTime, JobStatus)] | 369 | -- getJobState :: PrinterId -> UI [(JobId, UTCTime, JobStatus)] |
329 | getJobState pId = toList <$> jobs (Just pId) Nothing Nothing | 370 | getJobState pId = toList <$> withFatal (jobs (Just pId) Nothing Nothing) |
330 | mangleTuple (a, (b, c)) = (a, b, c) | 371 | mangleTuple (a, (b, c)) = (a, b, c) |
331 | 372 | ||
332 | -- jobSort :: (JobId, UTCTime, JobStatus) -> (JobId, UTCTime, JobStatus) -> Ordering | 373 | -- jobSort :: (JobId, UTCTime, JobStatus) -> (JobId, UTCTime, JobStatus) -> Ordering |
333 | jobSort (id, time, status) (id', time', status') = queueSort status status' <> compare time' time <> compare id' id | 374 | jobSort (id, time, status) (id', time', status') = queueSort status status' <> compare time' time <> compare id id' |
334 | where | 375 | where |
335 | compare' :: Ord a => a -> a -> Ordering | 376 | compare' :: Ord a => a -> a -> Ordering |
336 | compare' | 377 | compare' |
@@ -356,15 +397,25 @@ setup Config{..} window (split -> (socketErr, dataUpdate)) = void $ do | |||
356 | jId' <- UI.td # set UI.text (show jId) | 397 | jId' <- UI.td # set UI.text (show jId) |
357 | jStatus' <- UI.td # set UI.text (show status) | 398 | jStatus' <- UI.td # set UI.text (show status) |
358 | time' <- UI.td # set UI.text (formatTime defaultTimeLocale "%F %X" time) | 399 | time' <- UI.td # set UI.text (formatTime defaultTimeLocale "%F %X" time) |
359 | abortButton <- UI.button # sink UI.enabled allowAbortion # set UI.text "Abort" | 400 | mark <- UI.input |
360 | on UI.click abortButton . const $ jobDelete rJId | 401 | # set UI.type_ "checkbox" |
361 | let abortButton' = case status of | 402 | # set UI.name "jobMark" |
362 | Queued _ -> [abortButton] | 403 | # set UI.id_ ("jobMark" ++ show jId) |
404 | # set UI.value (show jId) | ||
405 | # sink UI.checked (Set.member rJId <$> marking) | ||
406 | on UI.checkedChange mark . const $ updateMarking' | ||
407 | mark' <- UI.span #+ [ return mark | ||
408 | , UI.label # set UI.for ("jobMark" ++ show jId) # set UI.text "Mark" | ||
409 | ] # set UI.class_ "mark" | ||
410 | -- abortButton <- UI.button # sink UI.enabled allowAbortion # set UI.text "Abort" | ||
411 | -- on UI.click abortButton . const $ jobDelete rJId | ||
412 | let mark'' = case status of | ||
413 | (Queued _) -> [mark'] | ||
363 | _ -> [] | 414 | _ -> [] |
364 | viewJob = do | 415 | viewJob = do |
365 | tabLinkList <- fatal' "Could not find tab link list" =<< getElementById window "tabLinks" | 416 | tabLinkList <- fatal' "Could not find tab link list" =<< getElementById window "tabLinks" |
366 | tabContainer <- fatal' "Could not find tab container" =<< getElementById window "tabContent" | 417 | tabContainer <- fatal' "Could not find tab container" =<< getElementById window "tabContent" |
367 | content <- job rJId | 418 | content <- withFatal $ job rJId |
368 | let | 419 | let |
369 | text = cobbcode content | 420 | text = cobbcode content |
370 | case text of | 421 | case text of |
@@ -381,9 +432,9 @@ setup Config{..} window (split -> (socketErr, dataUpdate)) = void $ do | |||
381 | runFunction . switchTab $ "viewJob" ++ show jId | 432 | runFunction . switchTab $ "viewJob" ++ show jId |
382 | viewButton <- UI.button # set UI.text "View" | 433 | viewButton <- UI.button # set UI.text "View" |
383 | on UI.click viewButton . const $ viewJob | 434 | on UI.click viewButton . const $ viewJob |
384 | actions <- UI.td # set children (viewButton : abortButton') | 435 | actions <- UI.td # set children (mark'' ++ [viewButton]) |
385 | UI.tr # set UI.id_ ("job" ++ show jId) # set children [jPId, jId', time', jStatus', actions] # sink UI.class_ (bool "" "focused" . Set.member rJId <$> focusedJobs) | 436 | UI.tr # set UI.id_ ("job" ++ show jId) # set children [jPId, jId', time', jStatus', actions] # sink UI.class_ (bool "" "focused" . Set.member rJId <$> focusedJobs) |
386 | (:) <$> UI.tr # set children [pId', pFiller, pStatus', pSelect'] <*> mapM toLine jobs | 437 | (:) <$> UI.tr # set children [pId', pFiller, pStatus', pSelect'] # set UI.class_ "printer" <*> mapM toLine jobs |
387 | 438 | ||
388 | update <- do | 439 | update <- do |
389 | -- recheckTimer <- timer | 440 | -- recheckTimer <- timer |