aboutsummaryrefslogtreecommitdiff
path: root/webgui
diff options
context:
space:
mode:
authorGregor Kleen <gkleen@yggdrasil.li>2016-02-26 19:11:56 +0000
committerGregor Kleen <gkleen@yggdrasil.li>2016-02-26 19:11:56 +0000
commit022a5a69dfcfc7b62a940d9c3070e6ae37cc993e (patch)
tree90261b972b67f084558021a6f399aeef65d480b6 /webgui
parent0a6e4d0cdee9aba48869361637613cb74c749289 (diff)
downloadthermoprint-022a5a69dfcfc7b62a940d9c3070e6ae37cc993e.tar
thermoprint-022a5a69dfcfc7b62a940d9c3070e6ae37cc993e.tar.gz
thermoprint-022a5a69dfcfc7b62a940d9c3070e6ae37cc993e.tar.bz2
thermoprint-022a5a69dfcfc7b62a940d9c3070e6ae37cc993e.tar.xz
thermoprint-022a5a69dfcfc7b62a940d9c3070e6ae37cc993e.zip
Switched to bulk deletion/abortion
Diffstat (limited to 'webgui')
-rw-r--r--webgui/data/index.html6
-rw-r--r--webgui/data/style.css24
-rw-r--r--webgui/src/Main.hs121
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 { 93tbody tr:nth-child(odd) td {
94 width:6em; 94 background-color:#f8f8f8;
95}
96
97tbody tr:nth-child(even) td {
98 background-color:#ffffff;
99}
100
101tbody tr.printer td {
102 background-color:initial;
95} 103}
96 104
97tr.focused td { 105tbody 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
123span.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
21import Data.ByteString (ByteString) 21import Data.ByteString (ByteString)
22import qualified Data.ByteString as BS 22import qualified Data.ByteString as BS
23import qualified Data.ByteString.Char8 as CBS 23import qualified Data.ByteString.Char8 as CBS
24import qualified Data.ByteString.Lazy.Char8 as CLBS
24 25
25import Data.Text (Text) 26import Data.Text (Text)
26import qualified Data.Text as T 27import 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
106fatal :: String -> UI a
107fatal 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
105setup :: Config -> Window -> Event (Either WebSocketException URI) -> UI () 113setup :: Config -> Window -> Event (Either WebSocketException URI) -> UI ()
106setup Config{..} window (split -> (socketErr, dataUpdate)) = void $ do 114setup 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