Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit c32be9f

Browse files
committed
Improve design of detail view (used by "info" & "next" commands)
1 parent 987a4fe commit c32be9f

File tree

8 files changed

+255
-40
lines changed

8 files changed

+255
-40
lines changed

tasklite-core/app/Main.hs

+1-1
Original file line numberDiff line numberDiff line change
@@ -847,7 +847,7 @@ executeCLiCommand conf now connection cmd =
847847
Stop ids -> stopTasks conf connection ids
848848
Prioritize val ids -> adjustPriority conf val ids
849849
InfoTask idSubstr -> infoTask conf connection idSubstr
850-
NextTask -> nextTask connection
850+
NextTask -> nextTask conf connection
851851
FindTask aPattern -> findTask connection aPattern
852852
AddTag tagText ids -> addTag conf connection tagText ids
853853
AddNote noteText ids -> addNote conf connection noteText ids

tasklite-core/example-config.yaml

+3
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ dueStyle: yellow
1010
overdueStyle: red
1111
tagStyle: blue
1212
utcFormat: YYYY-MM-DD H:MI:S
13+
#| FIXME: Blocked by https://github.com/vincenthz/hs-hourglass/issue
14+
# utcFormatShort: YYYY-DDD H:MI
15+
utcFormatShort: YYYY-MM-DD H:MI
1316

1417
#| Optional, uses the XDG directory per default
1518
#| https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html

tasklite-core/package.yaml

+1
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ default-extensions:
6161
- LambdaCase
6262
- MultiParamTypeClasses
6363
- MultiWayIf
64+
- NamedFieldPuns
6465
- NoImplicitPrelude
6566
- OverloadedStrings
6667
- RecordWildCards

tasklite-core/source/Config.hs

+3
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ data Config = Config
2828
, overdueStyle :: AnsiStyle
2929
, tagStyle :: AnsiStyle
3030
, utcFormat :: TimeFormatString
31+
, utcFormatShort :: TimeFormatString
3132
, dataDir :: FilePath
3233
, dbName :: FilePath
3334
, dateWidth :: Int
@@ -52,6 +53,7 @@ instance FromJSON Config where
5253
overdueStyle <- o .:? "overdueStyle" .!= overdueStyle defaultConfig
5354
tagStyle <- o .:? "tagStyle" .!= tagStyle defaultConfig
5455
utcFormat <- o .:? "utcFormat" .!= utcFormat defaultConfig
56+
utcFormatShort <- o .:? "utcFormatShort" .!= utcFormatShort defaultConfig
5557
dataDir <- o .:? "dataDir" .!= dataDir defaultConfig
5658
dbName <- o .:? "dbName" .!= dbName defaultConfig
5759
dateWidth <- o .:? "dateWidth" .!= dateWidth defaultConfig
@@ -134,6 +136,7 @@ defaultConfig = Config
134136
, overdueStyle = color Red
135137
, tagStyle = color Blue
136138
, utcFormat = toFormat ("YYYY-MM-DD H:MI:S" :: [Char])
139+
, utcFormatShort = toFormat ("YYYY-MM-DD H:MI" :: [Char])
137140
, dataDir = ""
138141
, dbName = "main.db"
139142
, dateWidth = 10

tasklite-core/source/Lib.hs

+174-32
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import Data.Hourglass
1010
import Data.Text as T
1111
import Data.ULID
1212
import Data.Coerce
13+
import Data.Yaml as Yaml
1314
import Database.Beam hiding (char)
1415
import Database.Beam.Sqlite
1516
import Database.Beam.Schema.Tables
@@ -664,12 +665,152 @@ stopTasks conf connection ids = do
664665
(show logMessages)
665666

666667

668+
formatTaskForInfo
669+
:: Config
670+
-> DateTime
671+
-> (TaskView, [TaskToTag], [TaskToNote])
672+
-> Doc AnsiStyle
673+
formatTaskForInfo conf now (taskV, tags, notes) =
674+
let
675+
mkGreen = annotate (color Green)
676+
grayOut = annotate (colorDull Black)
677+
stateHierarchy = getStateHierarchy now $ copyTimesToTask taskV
678+
mbCreatedUtc = fmap
679+
(pack . (timePrint $ utcFormat defaultConfig))
680+
(ulidTextToDateTime $ TaskView.ulid taskV)
681+
tagsPretty = tags
682+
<&> (\t -> (annotate (tagStyle conf) (pretty $ TaskToTag.tag t))
683+
<++> (fromMaybe mempty $ fmap
684+
(grayOut . pretty . pack . timePrint (utcFormat conf))
685+
(ulidTextToDateTime $ TaskToTag.ulid t))
686+
<++> (grayOut $ pretty $ TaskToTag.ulid t)
687+
)
688+
notesPretty = notes
689+
<&> (\n -> (fromMaybe mempty $ fmap
690+
(grayOut . pretty . pack . timePrint (utcFormat conf))
691+
(ulidTextToDateTime $ TaskToNote.ulid n))
692+
<++> (grayOut $ pretty $ TaskToNote.ulid n) <> hardline
693+
<> (indent 2 $ reflow $ TaskToNote.note n)
694+
<> hardline
695+
)
696+
697+
mbAwakeUtc = TaskView.awake_utc taskV
698+
mbReadyUtc = TaskView.ready_utc taskV
699+
mbWaitingUtc = TaskView.waiting_utc taskV
700+
mbReviewUtc = TaskView.review_utc taskV
701+
mbDueUtc = TaskView.due_utc taskV
702+
mbClosedUtc = TaskView.closed_utc taskV
703+
mbModifiedUtc = Just $ TaskView.modified_utc taskV
704+
705+
printIf :: Doc AnsiStyle -> Maybe Text -> Maybe (Doc AnsiStyle)
706+
printIf name value = fmap
707+
(\v -> name <+> (annotate (dueStyle conf) $ pretty v) <> hardline)
708+
value
709+
in
710+
hardline
711+
<> annotate bold (reflow $ TaskView.body taskV) <> hardline
712+
<> hardline
713+
<> (if P.null tags
714+
then mempty
715+
else (hsep $ (tags <&> TaskToTag.tag) <$$> (formatTag conf)) <> hardline
716+
<> hardline
717+
)
718+
719+
<> (if P.null notes
720+
then mempty
721+
else (notes
722+
<&> (\n -> (fromMaybe mempty $ fmap
723+
(grayOut . pretty . pack . timePrint (utcFormatShort conf))
724+
(ulidTextToDateTime $ TaskToNote.ulid n))
725+
<++> (align $ reflow $ TaskToNote.note n)
726+
)
727+
& vsep)
728+
<> hardline
729+
<> hardline
730+
)
731+
732+
<> " State:" <+> mkGreen (pretty stateHierarchy) <> hardline
733+
<> "Priority:" <+> annotate (priorityStyle conf)
734+
(pretty $ TaskView.priority taskV) <> hardline
735+
<> " ULID:" <+> grayOut (pretty $ TaskView.ulid taskV)
736+
<> hardline
737+
738+
<> hardline
739+
740+
<> ((
741+
(printIf "🆕 Created ", mbCreatedUtc) :
742+
(printIf "☀️ Awake ", mbAwakeUtc) :
743+
(printIf "📅 Ready ", mbReadyUtc) :
744+
(printIf "⏳ Waiting ", mbWaitingUtc) :
745+
(printIf "🔎 Review ", mbReviewUtc) :
746+
(printIf "📅 Due ", mbDueUtc) :
747+
(printIf "✅ Done ", mbClosedUtc) :
748+
(printIf "✏️ Modified ", mbModifiedUtc) :
749+
[])
750+
& sortBy (compare `on` snd)
751+
<&> (\tup -> (fst tup) (snd tup))
752+
& catMaybes
753+
& punctuate (pretty ("" :: Text))
754+
& vsep
755+
)
756+
757+
<> hardline
758+
759+
<> (fromMaybe mempty $ (fmap
760+
(\value -> "Repetition Duration:" <+> (mkGreen $ pretty value)
761+
<> hardline)
762+
(TaskView.repetition_duration taskV)
763+
))
764+
765+
<> (fromMaybe mempty $ (fmap
766+
(\value -> "Recurrence Duration:" <+> (mkGreen $ pretty value)
767+
<> hardline)
768+
(TaskView.recurrence_duration taskV)
769+
))
770+
771+
<> (fromMaybe mempty $ (fmap
772+
(\value -> "Group Ulid:"
773+
<+> (grayOut $ pretty value)
774+
<> hardline)
775+
(TaskView.group_ulid taskV)
776+
))
777+
778+
<> "User:" <+> (mkGreen $ pretty $ TaskView.user taskV) <> hardline
779+
780+
<> hardline
781+
782+
<> (fromMaybe mempty $ (fmap
783+
(\value -> "Metadata:" <> hardline
784+
<> indent 2 (pretty $ decodeUtf8 $ Yaml.encode value)
785+
<> hardline
786+
)
787+
(TaskView.metadata taskV)
788+
))
789+
790+
<> (if P.null tags
791+
then mempty
792+
else (annotate underlined "Tags Detailed:") <> hardline
793+
<> hardline
794+
<> vsep tagsPretty <> hardline
795+
<> hardline
796+
)
797+
798+
<> (if P.null notes
799+
then mempty
800+
else (annotate underlined "Notes Detailed:") <> hardline
801+
<> hardline
802+
<> vsep notesPretty <> hardline
803+
)
804+
805+
667806
infoTask :: Config -> Connection -> Text -> IO (Doc AnsiStyle)
668807
infoTask conf connection idSubstr = do
669808
execWithTask conf connection idSubstr $ \task -> do
670809
let
671810
taskUlid@(TaskUlid idText) = primaryKey task
672811

812+
now <- dateCurrent
813+
673814
runBeamSqlite connection $ do
674815
(mbFullTask :: Maybe TaskView) <- runSelectReturningOne $ select $
675816
filter_ (\tsk -> TaskView.ulid tsk ==. val_ idText) $
@@ -683,39 +824,36 @@ infoTask conf connection idSubstr = do
683824
filter_ (\theNote -> TaskToNote.task_ulid theNote ==. val_ taskUlid) $
684825
all_ (_tldbTaskToNote taskLiteDb)
685826

686-
let
687-
-- TODO: Colorize all YAML keys
688-
mkGreen = annotate (color Green)
689-
yamlList = (hang 2) . ("-" <+>)
690-
rmLastLine = unlines . P.reverse . P.drop 1 . P.reverse . lines . show
691-
formatTask fullTask =
692-
pretty fullTask <> hardline
693-
<> mkGreen "priority:"
694-
<+> (pretty $ TaskView.priority fullTask)
695-
<> hardline
696-
<> mkGreen "tags:\n"
697-
<> indent 2 (vsep $ fmap
698-
(yamlList . pretty . rmLastLine . pretty) tags)
699-
<> hardline
700-
<> mkGreen "notes:\n"
701-
<> indent 2 (vsep $ fmap
702-
(yamlList . pretty . rmLastLine . pretty) notes)
703-
704827
pure $ case mbFullTask of
705828
Nothing -> pretty noTasksWarning
706-
Just fullTask -> formatTask fullTask
829+
Just fullTask -> formatTaskForInfo conf now (fullTask, tags, notes)
707830

708831

709-
nextTask :: Connection -> IO (Doc AnsiStyle)
710-
nextTask connection = do
711-
let
712-
stateNullQuery = "select * from `tasks_view` where state is NULL "
713-
orderByAndLimit = "order by `priority` desc limit 1"
714-
tasks <- query_ connection $ Query $ stateNullQuery <> orderByAndLimit
832+
nextTask :: Config -> Connection -> IO (Doc AnsiStyle)
833+
nextTask conf connection = do
834+
now <- dateCurrent
835+
836+
runBeamSqlite connection $ do
837+
(mbFullTask :: Maybe TaskView) <- runSelectReturningOne $ select $
838+
limit_ 1 $
839+
orderBy_ (desc_ . TaskView.priority) $
840+
filter_ (\tsk -> TaskView.closed_utc tsk ==. val_ Nothing) $
841+
allFromView_ (_tldbTasksView taskLiteDb)
842+
843+
case mbFullTask of
844+
Nothing -> pure $ pretty noTasksWarning
845+
Just fullTask -> do
846+
tags <- runSelectReturningList $ select $
847+
filter_ (\tag -> TaskToTag.task_ulid tag ==.
848+
(val_ $ TaskUlid $ TaskView.ulid fullTask)) $
849+
all_ (_tldbTaskToTag taskLiteDb)
850+
851+
notes <- runSelectReturningList $ select $
852+
filter_ (\theNote -> TaskToNote.task_ulid theNote ==.
853+
(val_ $ TaskUlid $ TaskView.ulid fullTask)) $
854+
all_ (_tldbTaskToNote taskLiteDb)
715855

716-
pure $ case P.head (tasks :: [FullTask]) of
717-
Nothing -> pretty noTasksWarning
718-
Just task -> pretty task
856+
pure $ formatTaskForInfo conf now (fullTask, tags, notes)
719857

720858

721859
findTask :: Connection -> Text -> IO (Doc AnsiStyle)
@@ -1098,6 +1236,13 @@ showAtPrecision numOfDigits number =
10981236
else ""
10991237

11001238

1239+
formatTag :: Pretty a => Config -> a -> Doc AnsiStyle
1240+
formatTag conf =
1241+
annotate (tagStyle conf)
1242+
. (annotate (color Black) "+" <>)
1243+
. pretty
1244+
1245+
11011246
formatTaskLine :: Config -> DateTime -> Int -> FullTask -> Doc AnsiStyle
11021247
formatTaskLine conf now taskUlidWidth task =
11031248
let
@@ -1107,9 +1252,6 @@ formatTaskLine conf now taskUlidWidth task =
11071252
(ulidTextToDateTime $ FullTask.ulid task)
11081253
body = FullTask.body task
11091254
tags = fromMaybe [] $ FullTask.tags task
1110-
formatTag = annotate (tagStyle conf)
1111-
. (annotate (color Black) "+" <>)
1112-
. pretty
11131255
closedUtcMaybe = (FullTask.closed_utc task)
11141256
>>= parseUtc
11151257
<&> timePrint (utcFormat conf)
@@ -1151,7 +1293,7 @@ formatTaskLine conf now taskUlidWidth task =
11511293
else grayOutIfDone (reflow body)) :
11521294
annotate (dueStyle conf) (pretty dueUtcMaybe) :
11531295
annotate (closedStyle conf) (pretty closedUtcMaybe) :
1154-
hsep (tags <$$> formatTag) :
1296+
hsep (tags <$$> (formatTag conf)) :
11551297
(if (not $ P.null $ FullTask.notes task) then "📝" else "") :
11561298
[])
11571299
in

tasklite-core/source/Task.hs

+52
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import Protolude as P hiding ((%))
99
import Data.Aeson as Aeson
1010
import Data.Aeson.Text as Aeson
1111
import qualified Data.HashMap.Lazy as HM
12+
import Data.Hourglass (DateTime, timePrint)
1213
import Data.Yaml as Yaml
1314
import qualified Data.ByteString.Lazy as BSL
1415
import Data.Csv as Csv
@@ -28,6 +29,8 @@ import Test.QuickCheck
2829
import Test.QuickCheck.Instances.Text ()
2930
import Generic.Random
3031

32+
import Config (utcFormat, defaultConfig)
33+
3134

3235
-- From https://gist.github.com/chrisdone/7b0c4ebb5b9b94514959206df8992076
3336
instance Arbitrary Aeson.Value where
@@ -128,6 +131,24 @@ instance Arbitrary DerivedState where
128131
arbitrary = genericArbitraryU
129132

130133

134+
-- | A tuple of (Primary State, Secondary State)
135+
-- | Check out tasklite.org/concepts for a
136+
-- | detailed explanation of the different states
137+
-- | and how they relate to each other
138+
type StateHierachy = (DerivedState, DerivedState)
139+
140+
instance {-# OVERLAPS #-} Pretty StateHierachy where
141+
pretty stateH = (
142+
if fst stateH == snd stateH
143+
then show $ fst stateH
144+
else [fst stateH, snd stateH]
145+
<&> show
146+
& T.intercalate " and "
147+
)
148+
& T.replace "Is" ""
149+
& pretty
150+
151+
131152
textToDerivedState :: Text -> Maybe DerivedState
132153
textToDerivedState = \case
133154
"open" -> Just IsOpen
@@ -169,6 +190,37 @@ derivedStateToQuery = \case
169190
IsBlocked -> "" -- TODO
170191

171192

193+
getStateHierarchy :: DateTime -> Task -> StateHierachy
194+
getStateHierarchy now task =
195+
let
196+
nowTxt = pack $ timePrint (utcFormat defaultConfig) now
197+
in
198+
case Task.state task of
199+
Just Done -> (IsClosed, IsDone)
200+
Just Obsolete -> (IsClosed, IsObsolete)
201+
Just Deletable -> (IsClosed, IsDeletable)
202+
Nothing -> case closed_utc task of
203+
Just _ -> (IsClosed, IsClosed)
204+
Nothing -> case review_utc task of
205+
Just val -> if val > nowTxt
206+
then (IsOpen, IsWaiting)
207+
else (IsOpen, IsReview)
208+
Nothing -> case waiting_utc task of
209+
Just _ -> (IsOpen, IsWaiting)
210+
Nothing -> case (ready_utc task, awake_utc task) of
211+
(Just readyUtc, Just awakeUtc) ->
212+
if readyUtc < nowTxt && awakeUtc < nowTxt
213+
then (IsOpen, IsReady)
214+
else
215+
if readyUtc > nowTxt && awakeUtc < nowTxt
216+
then (IsOpen, IsAwake)
217+
else (IsOpen, IsAsleep)
218+
(Just readyUtc, Nothing) | readyUtc < nowTxt -> (IsOpen, IsReady)
219+
(Nothing, Just awakeUtc) | awakeUtc < nowTxt -> (IsOpen, IsAwake)
220+
(Nothing, Just awakeUtc) | awakeUtc > nowTxt -> (IsOpen, IsAsleep)
221+
_ -> (IsOpen, IsOpen)
222+
223+
172224
newtype Ulid = Ulid Text
173225

174226
-- | Uses _ to match Beam's defaults

0 commit comments

Comments
 (0)