From 034371a4ccd02132f7b1ad42c1eaad1cecfe0cb7 Mon Sep 17 00:00:00 2001 From: Mirko Westermeier Date: Thu, 27 Mar 2025 15:49:49 +0100 Subject: [PATCH 1/8] Simple brick app architecture --- .gitignore | 3 +++ app/App.hs | 35 ++++++++++++++++++++++++++++++- terminal-time-tracking-tool.cabal | 5 ++++- 3 files changed, 41 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 3562a67..335332a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ +# Temporary t4 data directory +t4-data + # Vim swap files *.sw[a-p] diff --git a/app/App.hs b/app/App.hs index 1f6436e..a9d0a6c 100644 --- a/app/App.hs +++ b/app/App.hs @@ -1,5 +1,38 @@ module Main where +import Brick +import Graphics.Vty +import T4.Data +import T4.Storage +import Lens.Micro.Platform +import Control.Monad +import Control.Monad.IO.Class + +data T4State = T4State + { _dir :: FilePath + , _clocks :: [Clock] + } deriving Show +makeLenses ''T4State + main :: IO () main = do - putStrLn "T4" + let clockDir = "t4-data" -- TODO + initState = T4State clockDir [] + void $ defaultMain t4App initState + +t4App :: App T4State () () +t4App = App + { appDraw = drawT4 + , appChooseCursor = neverShowCursor + , appHandleEvent = resizeOrQuit + , appStartEvent = initT4 + , appAttrMap = const $ attrMap defAttr [] + } + +initT4 :: EventM () T4State () +initT4 = do + loadedClocks <- liftIO . loadDataFromDir =<< use dir + clocks .= loadedClocks + +drawT4 :: T4State -> [Widget ()] +drawT4 state = [str (show $ state ^. clocks)] diff --git a/terminal-time-tracking-tool.cabal b/terminal-time-tracking-tool.cabal index b58533c..48d7ecb 100644 --- a/terminal-time-tracking-tool.cabal +++ b/terminal-time-tracking-tool.cabal @@ -21,7 +21,6 @@ common basics , aeson , yaml , text - , brick library import: basics @@ -32,7 +31,11 @@ library executable t4 import: basics + ghc-options: -threaded build-depends: terminal-time-tracking-tool + , vty + , brick + , microlens-platform hs-source-dirs: app main-is: App.hs From 15055cf36c6fe797c6e37f8b75c0f39898dff83d Mon Sep 17 00:00:00 2001 From: Mirko Westermeier Date: Mon, 31 Mar 2025 15:51:42 +0200 Subject: [PATCH 2/8] Summary: use upper case --- lib/T4/Data.hs | 4 ++-- test/T4/DataSpec.hs | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/T4/Data.hs b/lib/T4/Data.hs index 53a1fbd..987f765 100644 --- a/lib/T4/Data.hs +++ b/lib/T4/Data.hs @@ -62,8 +62,8 @@ getDay :: Clock -> Day getDay = localDay . getLocalTime . time summary :: Clock -> String -summary (Out t) = "out (" ++ sltString t ++ ")" -summary (In t mc ts) = "in (" ++ sltString t ++ ")" ++ catStr ++ tagsStr +summary (Out t) = "OUT (" ++ sltString t ++ ")" +summary (In t mc ts) = "IN (" ++ sltString t ++ ")" ++ catStr ++ tagsStr where catStr = maybe "" ((" [" ++) . (++ "]")) mc tagsStr = concatMap (" #" ++) ts diff --git a/test/T4/DataSpec.hs b/test/T4/DataSpec.hs index 5c163ef..01ee70a 100644 --- a/test/T4/DataSpec.hs +++ b/test/T4/DataSpec.hs @@ -83,11 +83,11 @@ spec = do context "Summary: stringification for humans" $ do it "Simple clock in" $ - summary cIn `shouldBe` "in (2017-11-23 17:42:37) [foo] #bar #baz" + summary cIn `shouldBe` "IN (2017-11-23 17:42:37) [foo] #bar #baz" it "Clock in without category" $ - summary cInNoCat `shouldBe` "in (2017-11-23 17:42:37) #bar #baz" + summary cInNoCat `shouldBe` "IN (2017-11-23 17:42:37) #bar #baz" it "Clock out" $ - summary cOut `shouldBe` "out (2017-11-23 17:42:37)" + summary cOut `shouldBe` "OUT (2017-11-23 17:42:37)" context "YAML clock data" $ do it "Reading simple clock-in data" $ From 9e015451f1d8067eea8716d7930a458704707703 Mon Sep 17 00:00:00 2001 From: Mirko Westermeier Date: Wed, 2 Apr 2025 12:17:40 +0200 Subject: [PATCH 3/8] Show summary in TUI --- app/App.hs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/App.hs b/app/App.hs index a9d0a6c..0d3ff99 100644 --- a/app/App.hs +++ b/app/App.hs @@ -35,4 +35,6 @@ initT4 = do clocks .= loadedClocks drawT4 :: T4State -> [Widget ()] -drawT4 state = [str (show $ state ^. clocks)] +drawT4 state = lastClock ++ widgets + where lastClock = maybe [] (return . str . summary) (state ^. clocks ^? _last) + widgets = [str "TODO"] -- TODO From 820628760d491c5e2e935aa2db5a2a2cc0e3f4da Mon Sep 17 00:00:00 2001 From: Mirko Westermeier Date: Wed, 2 Apr 2025 15:40:32 +0200 Subject: [PATCH 4/8] Move TUI to designated directory --- app/App.hs => t4-tui/TUI.hs | 0 terminal-time-tracking-tool.cabal | 6 +++--- 2 files changed, 3 insertions(+), 3 deletions(-) rename app/App.hs => t4-tui/TUI.hs (100%) diff --git a/app/App.hs b/t4-tui/TUI.hs similarity index 100% rename from app/App.hs rename to t4-tui/TUI.hs diff --git a/terminal-time-tracking-tool.cabal b/terminal-time-tracking-tool.cabal index 48d7ecb..bb40ae5 100644 --- a/terminal-time-tracking-tool.cabal +++ b/terminal-time-tracking-tool.cabal @@ -29,15 +29,15 @@ library , Completion hs-source-dirs: lib -executable t4 +executable t5 import: basics ghc-options: -threaded build-depends: terminal-time-tracking-tool , vty , brick , microlens-platform - hs-source-dirs: app - main-is: App.hs + hs-source-dirs: t4-tui + main-is: TUI.hs test-suite t4-test import: basics From 64fd369d199dd4a16263333ba999f444e7334a2e Mon Sep 17 00:00:00 2001 From: Mirko Westermeier Date: Thu, 3 Apr 2025 18:06:17 +0200 Subject: [PATCH 5/8] Simplify disk storage handling (64451a0) --- .gitignore | 3 --- t4-tui/TUI.hs | 4 ++-- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/.gitignore b/.gitignore index 335332a..3562a67 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,3 @@ -# Temporary t4 data directory -t4-data - # Vim swap files *.sw[a-p] diff --git a/t4-tui/TUI.hs b/t4-tui/TUI.hs index 0d3ff99..8e97b0f 100644 --- a/t4-tui/TUI.hs +++ b/t4-tui/TUI.hs @@ -16,8 +16,8 @@ makeLenses ''T4State main :: IO () main = do - let clockDir = "t4-data" -- TODO - initState = T4State clockDir [] + dd <- getStorageDirectory + let initState = T4State dd [] void $ defaultMain t4App initState t4App :: App T4State () () From effd0ddb11646b79bf3e162bbbfe68b97e865496 Mon Sep 17 00:00:00 2001 From: Mirko Westermeier Date: Tue, 8 Apr 2025 17:32:35 +0200 Subject: [PATCH 6/8] Use t5 command name in README --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 01c1278..bb915b4 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,9 @@ Haskell library and terminal GUI tool for time tracking. -## Run tool +## Run t4 TUI (t5) - cabal run t4 + cabal run t5 ## Run tests From 475e6d3fbdfdfc866b9a521af6b7484a359d23dc Mon Sep 17 00:00:00 2001 From: Mirko Westermeier Date: Tue, 29 Apr 2025 15:54:54 +0200 Subject: [PATCH 7/8] Add a duration box with second updates --- t4-tui/TUI.hs | 68 ++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 56 insertions(+), 12 deletions(-) diff --git a/t4-tui/TUI.hs b/t4-tui/TUI.hs index 8e97b0f..dbc82e6 100644 --- a/t4-tui/TUI.hs +++ b/t4-tui/TUI.hs @@ -1,30 +1,52 @@ module Main where -import Brick -import Graphics.Vty import T4.Data import T4.Storage +import Util +import Data.Time +import Brick +import Brick.BChan +import Brick.Widgets.Border +import Graphics.Vty import Lens.Micro.Platform import Control.Monad import Control.Monad.IO.Class +import Control.Concurrent + +data Tick = Tick + deriving Show data T4State = T4State - { _dir :: FilePath - , _clocks :: [Clock] + { _dir :: FilePath + , _clocks :: [Clock] + , _now :: SimpleLocalTime + , _durConf :: DurationConfig } deriving Show makeLenses ''T4State main :: IO () main = do - dd <- getStorageDirectory - let initState = T4State dd [] - void $ defaultMain t4App initState -t4App :: App T4State () () + -- Prepare initial state + dd <- getStorageDirectory + curr <- getCurrentSLT + let initState = T4State dd [] curr manDurationConfig + + -- Prepare ticking thread + tickChan <- newBChan 42 + tickThreadID <- forkIO $ forever $ do writeBChan tickChan Tick + threadDelay $ 1000 * 1000 -- 1 sec. + -- Go + void $ customMainWithDefaultVty (Just tickChan) t4App initState + + -- Cleanup + killThread tickThreadID + +t4App :: App T4State Tick () t4App = App { appDraw = drawT4 , appChooseCursor = neverShowCursor - , appHandleEvent = resizeOrQuit + , appHandleEvent = handleEvent , appStartEvent = initT4 , appAttrMap = const $ attrMap defAttr [] } @@ -35,6 +57,28 @@ initT4 = do clocks .= loadedClocks drawT4 :: T4State -> [Widget ()] -drawT4 state = lastClock ++ widgets - where lastClock = maybe [] (return . str . summary) (state ^. clocks ^? _last) - widgets = [str "TODO"] -- TODO +drawT4 state = [ui] + where ui = hBox [ box (lastClock (state^.clocks^?_last)) + , fill ' ' + , box (duration (state^.durConf) durPair) + ] + durPair = do c1 <- state^.clocks^?_last + guard $ isIn c1 + let t1 = getLocalTime $ time c1 + t2 = getLocalTime $ state^.now + return $ diffLocalTime t2 t1 + box = border . padLeftRight 2 + +lastClock :: Maybe Clock -> Widget () +lastClock = str . maybe "no data" summary + +duration :: DurationConfig -> Maybe NominalDiffTime -> Widget () +duration dc = str . maybe "Not clocked in" (showDiffTime dc) + +handleEvent :: BrickEvent () Tick -> EventM () T4State () +handleEvent (AppEvent Tick) = do + c <- liftIO getCurrentSLT + now .= c + return () +handleEvent (VtyEvent (EvKey KEsc [])) = halt +handleEvent e = liftIO $ print e From 48d5d9edd3f63131870f5b8717d8aba1f1addfcc Mon Sep 17 00:00:00 2001 From: Mirko Westermeier Date: Wed, 30 Apr 2025 13:39:11 +0200 Subject: [PATCH 8/8] Clear the screen after existing the t5 --- t4-tui/TUI.hs | 2 ++ terminal-time-tracking-tool.cabal | 1 + 2 files changed, 3 insertions(+) diff --git a/t4-tui/TUI.hs b/t4-tui/TUI.hs index dbc82e6..405e44b 100644 --- a/t4-tui/TUI.hs +++ b/t4-tui/TUI.hs @@ -12,6 +12,7 @@ import Lens.Micro.Platform import Control.Monad import Control.Monad.IO.Class import Control.Concurrent +import System.Process data Tick = Tick deriving Show @@ -40,6 +41,7 @@ main = do void $ customMainWithDefaultVty (Just tickChan) t4App initState -- Cleanup + callCommand "clear" killThread tickThreadID t4App :: App T4State Tick () diff --git a/terminal-time-tracking-tool.cabal b/terminal-time-tracking-tool.cabal index 0c16f62..401e00c 100644 --- a/terminal-time-tracking-tool.cabal +++ b/terminal-time-tracking-tool.cabal @@ -49,6 +49,7 @@ executable t5 , vty , brick , microlens-platform + , process hs-source-dirs: t4-tui main-is: TUI.hs