diff options
author | Gregor Kleen <gkleen@yggdrasil.li> | 2016-02-25 18:04:49 +0100 |
---|---|---|
committer | Gregor Kleen <gkleen@yggdrasil.li> | 2016-02-25 18:04:49 +0100 |
commit | f515803694d7f8430b064f16a5a923b09ba70650 (patch) | |
tree | 1e5104f784e28a41a4d030e23e0841bb1173cb22 | |
parent | e6e3823982cb9755b7cb4727fb08171eed5b4332 (diff) | |
download | thermoprint-f515803694d7f8430b064f16a5a923b09ba70650.tar thermoprint-f515803694d7f8430b064f16a5a923b09ba70650.tar.gz thermoprint-f515803694d7f8430b064f16a5a923b09ba70650.tar.bz2 thermoprint-f515803694d7f8430b064f16a5a923b09ba70650.tar.xz thermoprint-f515803694d7f8430b064f16a5a923b09ba70650.zip |
Job/Printer list
-rw-r--r-- | spec/src/Thermoprint/API.hs | 12 | ||||
-rw-r--r-- | webgui/data/index.html | 32 | ||||
-rw-r--r-- | webgui/data/style.css | 4 | ||||
-rw-r--r-- | webgui/data/tabs.js | 2 | ||||
-rw-r--r-- | webgui/src/Main.hs | 114 |
5 files changed, 144 insertions, 20 deletions
diff --git a/spec/src/Thermoprint/API.hs b/spec/src/Thermoprint/API.hs index 3ffd239..5b9c266 100644 --- a/spec/src/Thermoprint/API.hs +++ b/spec/src/Thermoprint/API.hs | |||
@@ -7,7 +7,7 @@ | |||
7 | -- | A specification of an API for interacting with a set of printers | 7 | -- | A specification of an API for interacting with a set of printers |
8 | module Thermoprint.API | 8 | module Thermoprint.API |
9 | ( PrinterStatus(..) | 9 | ( PrinterStatus(..) |
10 | , JobStatus(..) | 10 | , JobStatus(..), queueSort |
11 | , PrintingError(..) | 11 | , PrintingError(..) |
12 | , DraftTitle | 12 | , DraftTitle |
13 | , Range(..), contains | 13 | , Range(..), contains |
@@ -25,6 +25,7 @@ import Data.Aeson | |||
25 | 25 | ||
26 | import Data.Monoid | 26 | import Data.Monoid |
27 | import Data.Maybe | 27 | import Data.Maybe |
28 | import Data.Function (on) | ||
28 | 29 | ||
29 | import Data.Set (Set) | 30 | import Data.Set (Set) |
30 | import Data.Map (Map) | 31 | import Data.Map (Map) |
@@ -70,6 +71,15 @@ data JobStatus = Queued PrinterId | |||
70 | | Failed PrintingError | 71 | | Failed PrintingError |
71 | deriving (Generic, Show, FromJSON, ToJSON) | 72 | deriving (Generic, Show, FromJSON, ToJSON) |
72 | 73 | ||
74 | queueSort :: JobStatus -> JobStatus -> Ordering | ||
75 | -- ^ Sort 'JobStatus' by their qualitative position in a printers queue | ||
76 | queueSort = compare `on` index | ||
77 | where | ||
78 | index (Queued _) = -1 | ||
79 | index (Printing _) = 0 | ||
80 | index Done = 1 | ||
81 | index (Failed _) = 1 | ||
82 | |||
73 | deriving instance Generic EncodingException | 83 | deriving instance Generic EncodingException |
74 | deriving instance NFData EncodingException | 84 | deriving instance NFData EncodingException |
75 | deriving instance FromJSON EncodingException | 85 | deriving instance FromJSON EncodingException |
diff --git a/webgui/data/index.html b/webgui/data/index.html index 3bdd047..e4a6c63 100644 --- a/webgui/data/index.html +++ b/webgui/data/index.html | |||
@@ -18,7 +18,7 @@ | |||
18 | <ul class="tab-links"> | 18 | <ul class="tab-links"> |
19 | <li class="active"><a href="#editor">Editor</a></li> | 19 | <li class="active"><a href="#editor">Editor</a></li> |
20 | <li><a href="#drafts">Drafts</a></li> | 20 | <li><a href="#drafts">Drafts</a></li> |
21 | <li><a href="#queue">Queue</a></li> | 21 | <li><a href="#printers">Printers & Jobs</a></li> |
22 | <li id="errors-tab" style="display:none;"><a href="#errors">Errors</a></li> | 22 | <li id="errors-tab" style="display:none;"><a href="#errors">Errors</a></li> |
23 | </ul> | 23 | </ul> |
24 | 24 | ||
@@ -44,6 +44,7 @@ | |||
44 | </div> | 44 | </div> |
45 | </div> | 45 | </div> |
46 | </div> | 46 | </div> |
47 | |||
47 | <div class="tab" id="drafts" style="text-align:center;"> | 48 | <div class="tab" id="drafts" style="text-align:center;"> |
48 | <table style="width:100%;" id="draftList"> | 49 | <table style="width:100%;" id="draftList"> |
49 | <thead> | 50 | <thead> |
@@ -65,8 +66,33 @@ | |||
65 | </table> | 66 | </table> |
66 | </div> | 67 | </div> |
67 | 68 | ||
68 | <div class="tab" id="queue"> | 69 | <div class="tab" id="printers" style="text-align:center;"> |
69 | Blub. | 70 | <table style="width:100%;" id="printerList"> |
71 | <thead> | ||
72 | <tr> | ||
73 | <td style="width:10em;">Printer Id</td> | ||
74 | <td style="width:10em;">Job Id</td> | ||
75 | <td style="width:20em;">Created</td> | ||
76 | <td>Status</td> | ||
77 | <td style="width:20em;">Actions</td> | ||
78 | </tr> | ||
79 | </thead> | ||
80 | <tbody id="printerListBody"></tbody> | ||
81 | <tfoot> | ||
82 | <tr> | ||
83 | <td colspan="4" style="border-style:none;"></td> | ||
84 | <td> | ||
85 | <input id="autoselectPrinter" type="radio" name="printer" value="Nothing" checked="checked" /><label for="autoselectPrinter">Have server select printer</label> | ||
86 | </td> | ||
87 | </tr> | ||
88 | <tr> | ||
89 | <td colspan="4" style="border-style:none;"></td> | ||
90 | <td> | ||
91 | <input id="allowAbortion" type="checkbox" /><label for="allowAbortion">Allow Abortion</label> | ||
92 | </td> | ||
93 | </tr> | ||
94 | </tfoot> | ||
95 | </table> | ||
70 | </div> | 96 | </div> |
71 | 97 | ||
72 | <ul class="tab" id="errors"> | 98 | <ul class="tab" id="errors"> |
diff --git a/webgui/data/style.css b/webgui/data/style.css index 9946208..6ab1704 100644 --- a/webgui/data/style.css +++ b/webgui/data/style.css | |||
@@ -89,6 +89,10 @@ tfoot tr:first-child td { | |||
89 | width:6em; | 89 | width:6em; |
90 | } | 90 | } |
91 | 91 | ||
92 | tr.focused td { | ||
93 | background-color:#d0d0d0; | ||
94 | } | ||
95 | |||
92 | /*----- Tabs -----*/ | 96 | /*----- Tabs -----*/ |
93 | .tabs { | 97 | .tabs { |
94 | display:block; | 98 | display:block; |
diff --git a/webgui/data/tabs.js b/webgui/data/tabs.js index 1d178d9..d3263c6 100644 --- a/webgui/data/tabs.js +++ b/webgui/data/tabs.js | |||
@@ -7,6 +7,8 @@ jQuery(document).ready(function() { | |||
7 | 7 | ||
8 | // Change/remove current tab to active | 8 | // Change/remove current tab to active |
9 | jQuery(this).parent('li').addClass('active').siblings().removeClass('active'); | 9 | jQuery(this).parent('li').addClass('active').siblings().removeClass('active'); |
10 | |||
11 | jQuery('.focused').removeClass('focused'); | ||
10 | 12 | ||
11 | e.preventDefault(); | 13 | e.preventDefault(); |
12 | }); | 14 | }); |
diff --git a/webgui/src/Main.hs b/webgui/src/Main.hs index cbb1a75..dbf34bc 100644 --- a/webgui/src/Main.hs +++ b/webgui/src/Main.hs | |||
@@ -39,14 +39,19 @@ import Control.Exception | |||
39 | import Control.Monad.Catch | 39 | import Control.Monad.Catch |
40 | 40 | ||
41 | import Control.Applicative | 41 | import Control.Applicative |
42 | import Control.Monad | 42 | import Control.Monad hiding (sequence) |
43 | import Data.Maybe | 43 | import Data.Maybe |
44 | import Data.Monoid | 44 | import Data.Monoid |
45 | import Text.Read hiding (get) | 45 | import Text.Read hiding (get) |
46 | import Data.Either | 46 | import Data.Either |
47 | import Data.List (isPrefixOf) | 47 | import Data.List (isPrefixOf, sortBy) |
48 | import Data.Ord | ||
49 | import qualified Data.Function as F | ||
48 | import Data.Bool | 50 | import Data.Bool |
49 | 51 | ||
52 | import Data.Traversable (sequence) | ||
53 | import Data.Foldable | ||
54 | |||
50 | import Paths_thermoprint_webgui | 55 | import Paths_thermoprint_webgui |
51 | 56 | ||
52 | import Debug.Trace | 57 | import Debug.Trace |
@@ -100,8 +105,10 @@ setup Config{..} window (split -> (socketErr, dataUpdate)) = void $ do | |||
100 | onEvent socketErr handleSocketErr | 105 | onEvent socketErr handleSocketErr |
101 | 106 | ||
102 | getElementById window "javascriptError" >>= maybeM delete | 107 | getElementById window "javascriptError" >>= maybeM delete |
108 | |||
109 | selectedPrinter <- handleJobTable | ||
103 | 110 | ||
104 | changeEditorStatus <- handleEditor | 111 | changeEditorStatus <- handleEditor selectedPrinter |
105 | 112 | ||
106 | handleDraftTable changeEditorStatus | 113 | handleDraftTable changeEditorStatus |
107 | 114 | ||
@@ -136,7 +143,7 @@ setup Config{..} window (split -> (socketErr, dataUpdate)) = void $ do | |||
136 | 143 | ||
137 | Client{..} = mkClient (Nat $ either (fatal . ("Error while communicating to Thermoprint.Server: " ++) . show) return <=< liftIO . runEitherT) server | 144 | Client{..} = mkClient (Nat $ either (fatal . ("Error while communicating to Thermoprint.Server: " ++) . show) return <=< liftIO . runEitherT) server |
138 | 145 | ||
139 | handleEditor = do | 146 | handleEditor selectedPrinter = do |
140 | title <- fatal' "Could not find editor title field" =<< getElementById window "editorTitle" | 147 | title <- fatal' "Could not find editor title field" =<< getElementById window "editorTitle" |
141 | text <- fatal' "Could not find editor text field" =<< getElementById window "editorText" | 148 | text <- fatal' "Could not find editor text field" =<< getElementById window "editorText" |
142 | status <- fatal' "Could not find editor status field" =<< getElementById window "editorStatus" | 149 | status <- fatal' "Could not find editor status field" =<< getElementById window "editorStatus" |
@@ -202,9 +209,9 @@ setup Config{..} window (split -> (socketErr, dataUpdate)) = void $ do | |||
202 | Right po -> case associatedDraft of | 209 | Right po -> case associatedDraft of |
203 | Just dId -> do | 210 | Just dId -> do |
204 | saved <- saveAction False | 211 | saved <- saveAction False |
205 | when saved $ runFunction . focusJob =<< draftPrint dId Nothing -- FIXME | 212 | when saved $ runFunction . focusJob =<< draftPrint dId =<< currentValue selectedPrinter |
206 | Nothing -> do | 213 | Nothing -> do |
207 | runFunction . focusJob =<< jobCreate Nothing po -- FIXME | 214 | runFunction . focusJob =<< flip jobCreate po =<< currentValue selectedPrinter |
208 | Left err -> emitError $ "Could not print draft due to error parsing bbcode: " ++ show err | 215 | Left err -> emitError $ "Could not print draft due to error parsing bbcode: " ++ show err |
209 | 216 | ||
210 | onEvent (whenE saveDraft $ tick autoSaveTimer) (const . void $ saveAction True) | 217 | onEvent (whenE saveDraft $ tick autoSaveTimer) (const . void $ saveAction True) |
@@ -214,6 +221,7 @@ setup Config{..} window (split -> (socketErr, dataUpdate)) = void $ do | |||
214 | return discardButton # sink UI.enabled (discardable <$> editorStatus) | 221 | return discardButton # sink UI.enabled (discardable <$> editorStatus) |
215 | 222 | ||
216 | return discardButton # sink UI.text (bool "Discard" "Save & Clear" <$> saveDraft) | 223 | return discardButton # sink UI.text (bool "Discard" "Save & Clear" <$> saveDraft) |
224 | return printButton # sink UI.text (maybe "Print" (\(PrinterId i) -> "Print on " ++ show i) <$> selectedPrinter) | ||
217 | 225 | ||
218 | on (whenE saveDraft . UI.click) saveButton . const . void $ saveAction False | 226 | on (whenE saveDraft . UI.click) saveButton . const . void $ saveAction False |
219 | on UI.click printButton $ const printAction | 227 | on UI.click printButton $ const printAction |
@@ -268,17 +276,91 @@ setup Config{..} window (split -> (socketErr, dataUpdate)) = void $ do | |||
268 | initialContent <- toTable =<< drafts | 276 | initialContent <- toTable =<< drafts |
269 | return table # set children initialContent | 277 | return table # set children initialContent |
270 | 278 | ||
271 | recheckTimer <- timer | 279 | update <- do |
272 | return recheckTimer # set interval 5000 | 280 | -- recheckTimer <- timer |
273 | start recheckTimer | 281 | -- return recheckTimer # set interval 5000 |
282 | -- start recheckTimer | ||
283 | |||
284 | -- return $ unionWith const (() <$ filterE concernsDrafts dataUpdate) (tick recheckTimer) | ||
285 | return $ filterE concernsDrafts dataUpdate | ||
274 | 286 | ||
275 | onEvent (unionWith const (() <$ filterE concernsDrafts dataUpdate) (tick recheckTimer)) . const $ drafts >>= toTable >>= (\c -> return table # set children c) | 287 | onEvent update . const $ drafts >>= toTable >>= (\c -> return table # set children c) |
276 | where | 288 | |
277 | concernsDrafts :: URI -> Bool | 289 | concernsDrafts :: URI -> Bool |
278 | concernsDrafts (uriPath -> p) | 290 | concernsDrafts (uriPath -> p) |
279 | | p == "drafts" = True | 291 | | p == "drafts" = True |
280 | | "draft/" `isPrefixOf` p = True | 292 | | "draft/" `isPrefixOf` p = True |
281 | | otherwise = False | 293 | | otherwise = False |
294 | |||
295 | concernsPrinters :: URI -> Bool | ||
296 | concernsPrinters (uriPath -> p) | ||
297 | | p == "printers" = True | ||
298 | | "jobs/" `isPrefixOf` p = True | ||
299 | | otherwise = False | ||
300 | |||
301 | handleJobTable = do | ||
302 | allowAbortion <- do | ||
303 | allowAbortion <- fatal' "Could not find abortion switch" =<< getElementById window "allowAbortion" | ||
304 | flip stepper (UI.checkedChange allowAbortion) =<< (allowAbortion # get UI.checked) | ||
305 | |||
306 | (selectedPrinter, updatePrinter) <- do | ||
307 | autoselectPrinter <- fatal' "Could not find printer autoselect switch" =<< getElementById window "autoselectPrinter" | ||
308 | (selectedPrinter, printerSelect) <- stepper' Nothing | ||
309 | return autoselectPrinter # sink UI.checked (isNothing <$> selectedPrinter) | ||
310 | let | ||
311 | -- getSelectedPrinter :: UI (Maybe PrinterId) | ||
312 | getSelectedPrinter = (fmap PrinterId . join . readMaybe) <$> callFunction (ffi "$('input[name=printer]:checked', '#printers').val()") | ||
313 | updatePrinterSelect = getSelectedPrinter >>= liftIO . printerSelect | ||
314 | on (domEvent "change") autoselectPrinter $ const updatePrinterSelect | ||
315 | return (selectedPrinter, updatePrinterSelect) | ||
316 | |||
317 | let | ||
318 | -- getServerState :: UI [(PrinterId, PrinterStatus, [(JobId, UTCTime, JobStatus)])] | ||
319 | getServerState = map mangleTuple . Map.toList <$> (Map.traverseWithKey (\pId status -> (,) status <$> getJobState pId) =<< printers) | ||
320 | -- getJobState :: PrinterId -> UI [(JobId, UTCTime, JobStatus)] | ||
321 | getJobState pId = toList <$> jobs (Just pId) Nothing Nothing | ||
322 | mangleTuple (a, (b, c)) = (a, b, c) | ||
323 | thrd (_, _, c) = c | ||
324 | |||
325 | -- toTable :: [(PrinterId, PrinterStatus, [(JobId, UTCTime, JobStatus)])] -> UI [Element] | ||
326 | toTable = fmap concat . mapM toSubTable | ||
327 | -- toSubTable :: (PrinterId, PrinterStatus, [(JobId, UTCTime, JobStatus)]) -> UI [Element] | ||
328 | toSubTable (rPId@(PrinterId pId), pStatus, (sortBy (queueSort `F.on` thrd) -> jobs)) = do | ||
329 | pId' <- UI.td # set UI.text (show pId) | ||
330 | pStatus' <- UI.td # set UI.text (show pStatus) | ||
331 | let selectId = "printer" ++ show pId | ||
332 | checked <- (== Just rPId) <$> currentValue selectedPrinter | ||
333 | pSelect <- UI.input # set UI.type_ "radio" # set UI.name "printer" # set UI.value (show $ Just pId) # set UI.id_ selectId # set UI.checked checked | ||
334 | on (domEvent "change") pSelect $ const updatePrinter | ||
335 | pSelectL <- UI.label # set UI.for selectId # set UI.text "Use for new jobs" | ||
336 | pSelect' <- UI.td # set children [pSelect, pSelectL] | ||
337 | pFiller <- UI.td # set UI.colspan 2 | ||
338 | let | ||
339 | toLine ((JobId jId), time, status) = do | ||
340 | jPId <- UI.td | ||
341 | jId' <- UI.td # set UI.text (show jId) | ||
342 | jStatus' <- UI.td # set UI.text (show status) | ||
343 | abortButton <- UI.button # sink UI.enabled allowAbortion # set UI.text "Abort" | ||
344 | actions <- UI.td # set children [abortButton] | ||
345 | UI.tr # set UI.id_ ("job" ++ show jId) # set children [jPId, jId', jStatus', actions] | ||
346 | (:) <$> UI.tr # set children [pId', pFiller, pStatus', pSelect'] <*> mapM toLine jobs | ||
347 | |||
348 | update <- do | ||
349 | -- recheckTimer <- timer | ||
350 | -- return recheckTimer # set interval 5000 | ||
351 | -- start recheckTimer | ||
352 | |||
353 | -- return $ unionWith const (() <$ filterE concernsPrinters dataUpdate) (tick recheckTimer) | ||
354 | return $ filterE concernsPrinters dataUpdate | ||
355 | |||
356 | table <- fatal' "Could not find printer table" =<< getElementById window "printerListBody" | ||
357 | initialContent <- toTable =<< getServerState | ||
358 | return table # set children initialContent | ||
359 | |||
360 | onEvent update . const $ getServerState >>= toTable >>= (\c -> return table # set children c) >> updatePrinter | ||
361 | |||
362 | return selectedPrinter | ||
363 | |||
282 | 364 | ||
283 | focusJob :: JobId -> JSFunction () | 365 | focusJob :: JobId -> JSFunction () |
284 | focusJob (JobId (fromInteger -> i)) = ffi "alert(%1)" (i :: Int) -- FIXME | 366 | focusJob (JobId (fromInteger -> i)) = ffi "alert(%1)" (i :: Int) -- FIXME |