diff options
Diffstat (limited to 'webgui/src')
-rw-r--r-- | webgui/src/Main.hs | 121 |
1 files changed, 86 insertions, 35 deletions
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 |