From 6f77b03555b124fa3b4c2342780185488660cdf9 Mon Sep 17 00:00:00 2001 From: Martin Pander Date: Tue, 3 Mar 2026 21:11:58 +0100 Subject: [PATCH] WIP --- flake.lock | 18 +- go.mod | 37 +- go.sum | 86 ++- internal/components/multiPageForm.go | 34 + internal/components/taskEditor.go | 1043 ++++++++++++++++++++++++++ internal/pages/main.go | 22 +- internal/pages/tasks.go | 454 +++++------ todo.md | 10 + 8 files changed, 1409 insertions(+), 295 deletions(-) create mode 100644 internal/components/multiPageForm.go create mode 100644 internal/components/taskEditor.go create mode 100644 todo.md diff --git a/flake.lock b/flake.lock index 8492a93..e53428f 100644 --- a/flake.lock +++ b/flake.lock @@ -5,11 +5,11 @@ "nixpkgs-lib": "nixpkgs-lib" }, "locked": { - "lastModified": 1769996383, - "narHash": "sha256-AnYjnFWgS49RlqX7LrC4uA+sCCDBj0Ry/WOJ5XWAsa0=", + "lastModified": 1777988971, + "narHash": "sha256-qIoWPDs+0/8JecyYgE3gpKQxW/4bLW/gp45vow9ioCQ=", "owner": "hercules-ci", "repo": "flake-parts", - "rev": "57928607ea566b5db3ad13af0e57e921e6b12381", + "rev": "0678d8986be1661af6bb555f3489f2fdfc31f6ff", "type": "github" }, "original": { @@ -20,11 +20,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1771848320, - "narHash": "sha256-0MAd+0mun3K/Ns8JATeHT1sX28faLII5hVLq0L3BdZU=", + "lastModified": 1777954456, + "narHash": "sha256-hGdgeU2Nk87RAuZyYjyDjFL6LK7dAZN5RE9+hrDTkDU=", "owner": "nixos", "repo": "nixpkgs", - "rev": "2fc6539b481e1d2569f25f8799236694180c0993", + "rev": "549bd84d6279f9852cae6225e372cc67fb91a4c1", "type": "github" }, "original": { @@ -36,11 +36,11 @@ }, "nixpkgs-lib": { "locked": { - "lastModified": 1769909678, - "narHash": "sha256-cBEymOf4/o3FD5AZnzC3J9hLbiZ+QDT/KDuyHXVJOpM=", + "lastModified": 1777168982, + "narHash": "sha256-GOkGPcboWE9BmGCRMLX3worL4EMnsnG8MyKmXNeYuhQ=", "owner": "nix-community", "repo": "nixpkgs.lib", - "rev": "72716169fe93074c333e8d0173151350670b824c", + "rev": "f5901329dade4a6ea039af1433fb087bd9c1fe14", "type": "github" }, "original": { diff --git a/go.mod b/go.mod index 0e54fea..1f0fe6e 100644 --- a/go.mod +++ b/go.mod @@ -1,28 +1,35 @@ module tasksquire -go 1.25 - -toolchain go1.25.7 +go 1.26.2 require ( - charm.land/bubbles/v2 v2.0.0 // indirect - charm.land/bubbletea/v2 v2.0.0 // indirect - charm.land/lipgloss/v2 v2.0.0 // indirect - github.com/charmbracelet/colorprofile v0.4.2 // indirect - github.com/charmbracelet/ultraviolet v0.0.0-20260205113103-524a6607adb8 // indirect - github.com/charmbracelet/x/ansi v0.11.6 // indirect + charm.land/bubbles/v2 v2.1.0 + charm.land/bubbletea/v2 v2.0.6 + charm.land/huh/v2 v2.0.3 + charm.land/lipgloss/v2 v2.0.3 + github.com/charmbracelet/x/ansi v0.11.7 + golang.org/x/term v0.43.0 +) + +require ( + github.com/atotto/clipboard v0.1.4 // indirect + github.com/catppuccin/go v0.3.0 // indirect + github.com/charmbracelet/colorprofile v0.4.3 // indirect + github.com/charmbracelet/ultraviolet v0.0.0-20260428153724-66037269d7be // indirect + github.com/charmbracelet/x/exp/ordered v0.1.0 // indirect + github.com/charmbracelet/x/exp/strings v0.1.0 // indirect github.com/charmbracelet/x/term v0.2.2 // indirect github.com/charmbracelet/x/termios v0.1.1 // indirect github.com/charmbracelet/x/windows v0.2.2 // indirect github.com/clipperhouse/displaywidth v0.11.0 // indirect - github.com/clipperhouse/stringish v0.1.1 // indirect github.com/clipperhouse/uax29/v2 v2.7.0 // indirect - github.com/lucasb-eyer/go-colorful v1.3.0 // indirect - github.com/mattn/go-runewidth v0.0.20 // indirect + github.com/dustin/go-humanize v1.0.1 // indirect + github.com/lucasb-eyer/go-colorful v1.4.0 // indirect + github.com/mattn/go-runewidth v0.0.23 // indirect + github.com/mitchellh/hashstructure/v2 v2.0.2 // indirect github.com/muesli/cancelreader v0.2.2 // indirect github.com/rivo/uniseg v0.4.7 // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect - golang.org/x/sync v0.19.0 // indirect - golang.org/x/sys v0.41.0 // indirect - golang.org/x/term v0.40.0 // indirect + golang.org/x/sync v0.20.0 // indirect + golang.org/x/sys v0.44.0 // indirect ) diff --git a/go.sum b/go.sum index 2c50afe..127faea 100644 --- a/go.sum +++ b/go.sum @@ -1,50 +1,68 @@ -charm.land/bubbles/v2 v2.0.0 h1:tE3eK/pHjmtrDiRdoC9uGNLgpopOd8fjhEe31B/ai5s= -charm.land/bubbles/v2 v2.0.0/go.mod h1:rCHoleP2XhU8um45NTuOWBPNVHxnkXKTiZqcclL/qOI= -charm.land/bubbletea/v2 v2.0.0 h1:p0d6CtWyJXJ9GfzMpUUqbP/XUUhhlk06+vCKWmox1wQ= -charm.land/bubbletea/v2 v2.0.0/go.mod h1:3LRff2U4WIYXy7MTxfbAQ+AdfM3D8Xuvz2wbsOD9OHQ= -charm.land/lipgloss/v2 v2.0.0 h1:sd8N/B3x892oiOjFfBQdXBQp3cAkvjGaU5TvVZC3ivo= -charm.land/lipgloss/v2 v2.0.0/go.mod h1:w6SnmsBFBmEFBodiEDurGS/sdUY/u1+v72DqUzc6J14= -github.com/charmbracelet/colorprofile v0.4.1 h1:a1lO03qTrSIRaK8c3JRxJDZOvhvIeSco3ej+ngLk1kk= -github.com/charmbracelet/colorprofile v0.4.1/go.mod h1:U1d9Dljmdf9DLegaJ0nGZNJvoXAhayhmidOdcBwAvKk= -github.com/charmbracelet/colorprofile v0.4.2 h1:BdSNuMjRbotnxHSfxy+PCSa4xAmz7szw70ktAtWRYrY= -github.com/charmbracelet/colorprofile v0.4.2/go.mod h1:0rTi81QpwDElInthtrQ6Ni7cG0sDtwAd4C4le060fT8= -github.com/charmbracelet/ultraviolet v0.0.0-20260205113103-524a6607adb8 h1:eyFRbAmexyt43hVfeyBofiGSEmJ7krjLOYt/9CF5NKA= -github.com/charmbracelet/ultraviolet v0.0.0-20260205113103-524a6607adb8/go.mod h1:SQpCTRNBtzJkwku5ye4S3HEuthAlGy2n9VXZnWkEW98= -github.com/charmbracelet/x/ansi v0.11.6 h1:GhV21SiDz/45W9AnV2R61xZMRri5NlLnl6CVF7ihZW8= -github.com/charmbracelet/x/ansi v0.11.6/go.mod h1:2JNYLgQUsyqaiLovhU2Rv/pb8r6ydXKS3NIttu3VGZQ= +charm.land/bubbles/v2 v2.1.0 h1:YSnNh5cPYlYjPxRrzs5VEn3vwhtEn3jVGRBT3M7/I0g= +charm.land/bubbles/v2 v2.1.0/go.mod h1:l97h4hym2hvWBVfmJDtrEHHCtkIKeTEb3TTJ4ZOB3wY= +charm.land/bubbletea/v2 v2.0.6 h1:UHN/91OyuhaOFGSrBXQ/hMZD8IO1Uc4BvHlgHXL2WJo= +charm.land/bubbletea/v2 v2.0.6/go.mod h1:MH/D8ZLlN3op37vQvijKuU29g3rqTp+aQapURFonF9g= +charm.land/huh/v2 v2.0.3 h1:2cJsMqEPwSywGHvdlKsJyQKPtSJLVnFKyFbsYZTlLkU= +charm.land/huh/v2 v2.0.3/go.mod h1:93eEveeeqn47MwiC3tf+2atZ2l7Is88rAtmZNZ8x9Wc= +charm.land/lipgloss/v2 v2.0.3 h1:yM2zJ4Cf5Y51b7RHIwioil4ApI/aypFXXVHSwlM6RzU= +charm.land/lipgloss/v2 v2.0.3/go.mod h1:7myLU9iG/3xluAWzpY/fSxYYHCgoKTie7laxk6ATwXA= +github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ= +github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE= +github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4= +github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= +github.com/aymanbagabas/go-udiff v0.4.1 h1:OEIrQ8maEeDBXQDoGCbbTTXYJMYRCRO1fnodZ12Gv5o= +github.com/aymanbagabas/go-udiff v0.4.1/go.mod h1:0L9PGwj20lrtmEMeyw4WKJ/TMyDtvAoK9bf2u/mNo3w= +github.com/catppuccin/go v0.3.0 h1:d+0/YicIq+hSTo5oPuRi5kOpqkVA5tAsU6dNhvRu+aY= +github.com/catppuccin/go v0.3.0/go.mod h1:8IHJuMGaUUjQM82qBrGNBv7LFq6JI3NnQCF6MOlZjpc= +github.com/charmbracelet/colorprofile v0.4.3 h1:QPa1IWkYI+AOB+fE+mg/5/4HRMZcaXex9t5KX76i20Q= +github.com/charmbracelet/colorprofile v0.4.3/go.mod h1:/zT4BhpD5aGFpqQQqw7a+VtHCzu+zrQtt1zhMt9mR4Q= +github.com/charmbracelet/ultraviolet v0.0.0-20260428153724-66037269d7be h1:j7w8VP/D4lu5+/4GamMmFy8nrtadcl82/fjvDgSHwLo= +github.com/charmbracelet/ultraviolet v0.0.0-20260428153724-66037269d7be/go.mod h1:3YdTxlnV/L0bQ3VN8WOSw8doF7LZV/xawUQ4MuAPDvo= +github.com/charmbracelet/x/ansi v0.11.7 h1:kzv1kJvjg2S3r9KHo8hDdHFQLEqn4RBCb39dAYC84jI= +github.com/charmbracelet/x/ansi v0.11.7/go.mod h1:9qGpnAVYz+8ACONkZBUWPtL7lulP9No6p1epAihUZwQ= +github.com/charmbracelet/x/conpty v0.1.1 h1:s1bUxjoi7EpqiXysVtC+a8RrvPPNcNvAjfi4jxsAuEs= +github.com/charmbracelet/x/conpty v0.1.1/go.mod h1:OmtR77VODEFbiTzGE9G1XiRJAga6011PIm4u5fTNZpk= +github.com/charmbracelet/x/errors v0.0.0-20240508181413-e8d8b6e2de86 h1:JSt3B+U9iqk37QUU2Rvb6DSBYRLtWqFqfxf8l5hOZUA= +github.com/charmbracelet/x/errors v0.0.0-20240508181413-e8d8b6e2de86/go.mod h1:2P0UgXMEa6TsToMSuFqKFQR+fZTO9CNGUNokkPatT/0= +github.com/charmbracelet/x/exp/golden v0.0.0-20250806222409-83e3a29d542f h1:pk6gmGpCE7F3FcjaOEKYriCvpmIN4+6OS/RD0vm4uIA= +github.com/charmbracelet/x/exp/golden v0.0.0-20250806222409-83e3a29d542f/go.mod h1:IfZAMTHB6XkZSeXUqriemErjAWCCzT0LwjKFYCZyw0I= +github.com/charmbracelet/x/exp/ordered v0.1.0 h1:55/qLwjIh0gL0Vni+QAWk7T/qRVP6sBf+2agPBgnOFE= +github.com/charmbracelet/x/exp/ordered v0.1.0/go.mod h1:5UHwmG+is5THxMyCJHNPCn2/ecI07aKNrW+LcResjJ8= +github.com/charmbracelet/x/exp/strings v0.1.0 h1:i69S2XI7uG1u4NLGeJPSYU++Nmjvpo9nwd6aoEm7gkA= +github.com/charmbracelet/x/exp/strings v0.1.0/go.mod h1:/ehtMPNh9K4odGFkqYJKpIYyePhdp1hLBRvyY4bWkH8= github.com/charmbracelet/x/term v0.2.2 h1:xVRT/S2ZcKdhhOuSP4t5cLi5o+JxklsoEObBSgfgZRk= github.com/charmbracelet/x/term v0.2.2/go.mod h1:kF8CY5RddLWrsgVwpw4kAa6TESp6EB5y3uxGLeCqzAI= github.com/charmbracelet/x/termios v0.1.1 h1:o3Q2bT8eqzGnGPOYheoYS8eEleT5ZVNYNy8JawjaNZY= github.com/charmbracelet/x/termios v0.1.1/go.mod h1:rB7fnv1TgOPOyyKRJ9o+AsTU/vK5WHJ2ivHeut/Pcwo= github.com/charmbracelet/x/windows v0.2.2 h1:IofanmuvaxnKHuV04sC0eBy/smG6kIKrWG2/jYn2GuM= github.com/charmbracelet/x/windows v0.2.2/go.mod h1:/8XtdKZzedat74NQFn0NGlGL4soHB0YQZrETF96h75k= -github.com/clipperhouse/displaywidth v0.9.0 h1:Qb4KOhYwRiN3viMv1v/3cTBlz3AcAZX3+y9OLhMtAtA= -github.com/clipperhouse/displaywidth v0.9.0/go.mod h1:aCAAqTlh4GIVkhQnJpbL0T/WfcrJXHcj8C0yjYcjOZA= +github.com/charmbracelet/x/xpty v0.1.3 h1:eGSitii4suhzrISYH50ZfufV3v085BXQwIytcOdFSsw= +github.com/charmbracelet/x/xpty v0.1.3/go.mod h1:poPYpWuLDBFCKmKLDnhBp51ATa0ooD8FhypRwEFtH3Y= github.com/clipperhouse/displaywidth v0.11.0 h1:lBc6kY44VFw+TDx4I8opi/EtL9m20WSEFgwIwO+UVM8= github.com/clipperhouse/displaywidth v0.11.0/go.mod h1:bkrFNkf81G8HyVqmKGxsPufD3JhNl3dSqnGhOoSD/o0= -github.com/clipperhouse/stringish v0.1.1 h1:+NSqMOr3GR6k1FdRhhnXrLfztGzuG+VuFDfatpWHKCs= -github.com/clipperhouse/stringish v0.1.1/go.mod h1:v/WhFtE1q0ovMta2+m+UbpZ+2/HEXNWYXQgCt4hdOzA= -github.com/clipperhouse/uax29/v2 v2.5.0 h1:x7T0T4eTHDONxFJsL94uKNKPHrclyFI0lm7+w94cO8U= -github.com/clipperhouse/uax29/v2 v2.5.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsVRgg6W7ihQeh4g= github.com/clipperhouse/uax29/v2 v2.7.0 h1:+gs4oBZ2gPfVrKPthwbMzWZDaAFPGYK72F0NJv2v7Vk= github.com/clipperhouse/uax29/v2 v2.7.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM= -github.com/lucasb-eyer/go-colorful v1.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQwVHXptag= -github.com/lucasb-eyer/go-colorful v1.3.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= -github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw= -github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs= -github.com/mattn/go-runewidth v0.0.20 h1:WcT52H91ZUAwy8+HUkdM3THM6gXqXuLJi9O3rjcQQaQ= -github.com/mattn/go-runewidth v0.0.20/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs= +github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s= +github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/lucasb-eyer/go-colorful v1.4.0 h1:UtrWVfLdarDgc44HcS7pYloGHJUjHV/4FwW4TvVgFr4= +github.com/lucasb-eyer/go-colorful v1.4.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= +github.com/mattn/go-runewidth v0.0.23 h1:7ykA0T0jkPpzSvMS5i9uoNn2Xy3R383f9HDx3RybWcw= +github.com/mattn/go-runewidth v0.0.23/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs= +github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4= +github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE= github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= -golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= -golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= -golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= -golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k= -golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/term v0.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg= -golang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM= +golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI= +golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo= +golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= +golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/sys v0.44.0 h1:ildZl3J4uzeKP07r2F++Op7E9B29JRUy+a27EibtBTQ= +golang.org/x/sys v0.44.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/term v0.43.0 h1:S4RLU2sB31O/NCl+zFN9Aru9A/Cq2aqKpTZJ6B+DwT4= +golang.org/x/term v0.43.0/go.mod h1:lrhlHNdQJHO+1qVYiHfFKVuVioJIheAc3fBSMFYEIsk= diff --git a/internal/components/multiPageForm.go b/internal/components/multiPageForm.go new file mode 100644 index 0000000..a6b7401 --- /dev/null +++ b/internal/components/multiPageForm.go @@ -0,0 +1,34 @@ +package components + +import ( + tea "charm.land/bubbletea/v2" + "charm.land/huh/v2" +) + +type Page struct { + form huh.Form +} + +type MultiPageForm struct { + // pages []Page + form huh.Form +} + +func NewMultiPageForm(form huh.Form) *MultiPageForm { + return &MultiPageForm{ + form: form, + } +} + +func (f *MultiPageForm) Init() tea.Cmd { + return nil +} + +func (f *MultiPageForm) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + _, cmd := f.form.Update(msg) + return f, cmd +} + +func (f *MultiPageForm) View() tea.View { + return tea.NewView(f.form.View()) +} diff --git a/internal/components/taskEditor.go b/internal/components/taskEditor.go new file mode 100644 index 0000000..265c7f6 --- /dev/null +++ b/internal/components/taskEditor.go @@ -0,0 +1,1043 @@ +// package components +// +// import ( +// "fmt" +// "log/slog" +// "tasksquire/common" +// "time" +// +// "tasksquire/components/input" +// "tasksquire/components/picker" +// "tasksquire/components/timestampeditor" +// "tasksquire/taskwarrior" +// +// "github.com/charmbracelet/bubbles/key" +// "github.com/charmbracelet/bubbles/list" +// "github.com/charmbracelet/bubbles/textarea" +// "github.com/charmbracelet/bubbles/viewport" +// tea "github.com/charmbracelet/bubbletea" +// "github.com/charmbracelet/huh" +// "github.com/charmbracelet/lipgloss" +// ) +// +// type mode int +// +// const ( +// modeNormal mode = iota +// modeInsert +// ) +// +// type TaskEditorPage struct { +// common *common.Common +// task taskwarrior.Task +// +// colWidth int +// colHeight int +// +// mode mode +// +// columnCursor int +// +// area int +// areaPicker *areaPicker +// areas []area +// +// infoViewport viewport.Model +// } +// +// func NewTaskEditorPage(com *common.Common, task taskwarrior.Task) *TaskEditorPage { +// p := TaskEditorPage{ +// common: com, +// task: task, +// } +// +// if p.task.Project == "" { +// p.task.Project = "(none)" +// } +// +// tagOptions := p.common.TW.GetTags() +// +// p.areas = []area{ +// NewTaskEdit(p.common, &p.task, p.task.Uuid == ""), +// NewTagEdit(p.common, &p.task.Tags, tagOptions), +// NewTimeEdit(p.common, &p.task.Due, &p.task.Scheduled, &p.task.Wait, &p.task.Until), +// NewDetailsEdit(p.common, &p.task), +// } +// +// // p.areaList = NewAreaList(common, areaItems) +// // p.selectedArea = areaTask +// // p.columns = append([]tea.Model{p.areaList}, p.areas[p.selectedArea]...) +// +// p.areaPicker = NewAreaPicker(com, []string{"Task", "Tags", "Dates"}) +// +// p.infoViewport = viewport.New(0, 0) +// if p.task.Uuid != "" { +// p.infoViewport.SetContent(p.common.TW.GetInformation(&p.task)) +// } +// +// p.columnCursor = 1 +// if p.task.Uuid == "" { +// p.mode = modeInsert +// } else { +// p.mode = modeNormal +// } +// +// p.SetSize(com.Width(), com.Height()) +// +// return &p +// } +// +// func (p *TaskEditorPage) SetSize(width, height int) { +// p.common.SetSize(width, height) +// +// if width >= 70 { +// p.colWidth = 70 - p.common.Styles.ColumnFocused.GetVerticalFrameSize() +// } else { +// p.colWidth = width - p.common.Styles.ColumnFocused.GetVerticalFrameSize() +// } +// +// if height >= 40 { +// p.colHeight = 40 - p.common.Styles.ColumnFocused.GetVerticalFrameSize() +// } else { +// p.colHeight = height - p.common.Styles.ColumnFocused.GetVerticalFrameSize() +// } +// +// p.infoViewport.Width = width - p.colWidth - p.common.Styles.ColumnFocused.GetHorizontalFrameSize()*2 - 5 +// if p.infoViewport.Width < 0 { +// p.infoViewport.Width = 0 +// } +// p.infoViewport.Height = p.colHeight +// } +// +// func (p *TaskEditorPage) Init() tea.Cmd { +// var cmds []tea.Cmd +// for _, a := range p.areas { +// cmds = append(cmds, a.Init()) +// } +// return tea.Batch(cmds...) +// } +// +// func (p *TaskEditorPage) Update(msg tea.Msg) (tea.Model, tea.Cmd) { +// switch msg := msg.(type) { +// case tea.WindowSizeMsg: +// p.SetSize(msg.Width, msg.Height) +// case changeAreaMsg: +// p.area = int(msg) +// case changeModeMsg: +// p.mode = mode(msg) +// case prevColumnMsg: +// p.columnCursor-- +// maxCols := 2 +// if p.task.Uuid != "" { +// maxCols = 3 +// } +// if p.columnCursor < 0 { +// p.columnCursor = maxCols - 1 +// } +// case nextColumnMsg: +// p.columnCursor++ +// maxCols := 2 +// if p.task.Uuid != "" { +// maxCols = 3 +// } +// if p.columnCursor >= maxCols { +// p.columnCursor = 0 +// } +// case prevAreaMsg: +// p.area-- +// if p.area < 0 { +// p.area = len(p.areas) - 1 +// } +// p.areas[p.area].SetCursor(-1) +// case nextAreaMsg: +// p.area++ +// if p.area > len(p.areas)-1 { +// p.area = 0 +// } +// p.areas[p.area].SetCursor(0) +// } +// +// switch p.mode { +// case modeNormal: +// switch msg := msg.(type) { +// case tea.KeyMsg: +// switch { +// case key.Matches(msg, p.common.Keymap.Back): +// model, err := p.common.PopPage() +// if err != nil { +// slog.Error("page stack empty") +// return nil, tea.Quit +// } +// return model, BackCmd +// case key.Matches(msg, p.common.Keymap.Insert): +// return p, changeMode(modeInsert) +// case key.Matches(msg, p.common.Keymap.Ok): +// model, err := p.common.PopPage() +// if err != nil { +// slog.Error("page stack empty") +// return nil, tea.Quit +// } +// return model, p.updateTasksCmd +// case key.Matches(msg, p.common.Keymap.PrevPage): +// return p, prevArea() +// case key.Matches(msg, p.common.Keymap.NextPage): +// return p, nextArea() +// case key.Matches(msg, p.common.Keymap.Left): +// return p, prevColumn() +// case key.Matches(msg, p.common.Keymap.Right): +// return p, nextColumn() +// case key.Matches(msg, p.common.Keymap.Up): +// if p.columnCursor == 0 { +// picker, cmd := p.areaPicker.Update(msg) +// p.areaPicker = picker.(*areaPicker) +// return p, cmd +// } else if p.columnCursor == 1 { +// model, cmd := p.areas[p.area].Update(prevFieldMsg{}) +// p.areas[p.area] = model.(area) +// return p, cmd +// } else if p.columnCursor == 2 { +// p.infoViewport.LineUp(1) +// return p, nil +// } +// case key.Matches(msg, p.common.Keymap.Down): +// if p.columnCursor == 0 { +// picker, cmd := p.areaPicker.Update(msg) +// p.areaPicker = picker.(*areaPicker) +// return p, cmd +// } else if p.columnCursor == 1 { +// model, cmd := p.areas[p.area].Update(nextFieldMsg{}) +// p.areas[p.area] = model.(area) +// return p, cmd +// } else if p.columnCursor == 2 { +// p.infoViewport.LineDown(1) +// return p, nil +// } +// } +// } +// +// // var cmd tea.Cmd +// // if p.columnCursor == 0 { +// // p., cmd = p.areaList.Update(msg) +// // p.selectedArea = p.areaList.(areaList).Area() +// // cmds = append(cmds, cmd) +// // } else { +// // p.areas[p.selectedArea][p.columnCursor-1], cmd = p.areas[p.selectedArea][p.columnCursor-1].Update(msg) +// // cmds = append(cmds, cmd) +// // } +// case modeInsert: +// switch msg := msg.(type) { +// case tea.KeyMsg: +// switch { +// case key.Matches(msg, p.common.Keymap.Back): +// model, cmd := p.areas[p.area].Update(msg) +// p.areas[p.area] = model.(area) +// if cmd != nil { +// _, ok := cmd().(input.SuppressBackMsg) +// if ok { +// return p, nil +// } +// } +// return p, changeMode(modeNormal) +// case key.Matches(msg, p.common.Keymap.Prev): +// if p.columnCursor == 0 { +// picker, cmd := p.areaPicker.Update(msg) +// p.areaPicker = picker.(*areaPicker) +// return p, cmd +// } else if p.columnCursor == 1 { +// model, cmd := p.areas[p.area].Update(prevFieldMsg{}) +// p.areas[p.area] = model.(area) +// return p, cmd +// } +// return p, nil +// case key.Matches(msg, p.common.Keymap.Next): +// if p.columnCursor == 0 { +// picker, cmd := p.areaPicker.Update(msg) +// p.areaPicker = picker.(*areaPicker) +// return p, cmd +// } else if p.columnCursor == 1 { +// model, cmd := p.areas[p.area].Update(nextFieldMsg{}) +// p.areas[p.area] = model.(area) +// return p, cmd +// } +// return p, nil +// case key.Matches(msg, p.common.Keymap.Ok): +// isFiltering := p.areas[p.area].IsFiltering() +// model, cmd := p.areas[p.area].Update(msg) +// if p.area != 3 { +// p.areas[p.area] = model.(area) +// if isFiltering { +// return p, cmd +// } +// return p, tea.Batch(cmd, nextField()) +// } +// return p, cmd +// } +// } +// +// if p.columnCursor == 0 { +// picker, cmd := p.areaPicker.Update(msg) +// p.areaPicker = picker.(*areaPicker) +// return p, cmd +// } else if p.columnCursor == 2 { +// var cmd tea.Cmd +// p.infoViewport, cmd = p.infoViewport.Update(msg) +// return p, cmd +// } else { +// model, cmd := p.areas[p.area].Update(msg) +// p.areas[p.area] = model.(area) +// return p, cmd +// } +// } +// return p, nil +// } +// +// func (p *TaskEditorPage) View() string { +// var focusedStyle, blurredStyle lipgloss.Style +// if p.mode == modeInsert { +// focusedStyle = p.common.Styles.ColumnInsert +// } else { +// focusedStyle = p.common.Styles.ColumnFocused +// } +// blurredStyle = p.common.Styles.ColumnBlurred +// +// var area string +// if p.columnCursor == 1 { +// area = focusedStyle.Copy().Width(p.colWidth).Height(p.colHeight).Render(p.areas[p.area].View()) +// } else { +// area = blurredStyle.Copy().Width(p.colWidth).Height(p.colHeight).Render(p.areas[p.area].View()) +// } +// +// if p.task.Uuid != "" { +// var infoView string +// if p.columnCursor == 2 { +// infoView = focusedStyle.Copy().Width(p.infoViewport.Width).Height(p.infoViewport.Height).Render(p.infoViewport.View()) +// } else { +// infoView = blurredStyle.Copy().Width(p.infoViewport.Width).Height(p.infoViewport.Height).Render(p.infoViewport.View()) +// } +// area = lipgloss.JoinHorizontal( +// lipgloss.Top, +// area, +// infoView, +// ) +// } +// +// tabs := "" +// for i, a := range p.areas { +// style := p.common.Styles.Base +// if i == p.area { +// style = style.Bold(true).Foreground(p.common.Styles.Palette.Accent.GetForeground()) +// } else { +// style = style.Foreground(p.common.Styles.Palette.Muted.GetForeground()) +// } +// tabs += style.Render(fmt.Sprintf(" %s ", a.GetName())) +// } +// +// page := lipgloss.JoinVertical( +// lipgloss.Left, +// tabs, +// area, +// ) +// +// // return lipgloss.Place(p.common.Width(), p.common.Height(), lipgloss.Center, lipgloss.Center, lipgloss.JoinHorizontal( +// // lipgloss.Center, +// // picker, +// // area, +// // )) +// return lipgloss.Place(p.common.Width(), p.common.Height(), lipgloss.Center, lipgloss.Center, page) +// } +// +// type area interface { +// tea.Model +// SetCursor(c int) +// GetName() string +// IsFiltering() bool +// } +// +// type focusMsg struct{} +// +// type areaPicker struct { +// common *common.Common +// list list.Model +// } +// +// type item string +// +// func (i item) Title() string { return string(i) } +// func (i item) Description() string { return "test" } +// func (i item) FilterValue() string { return "" } +// +// func NewAreaPicker(common *common.Common, items []string) *areaPicker { +// listItems := make([]list.Item, len(items)) +// for i, itm := range items { +// listItems[i] = item(itm) +// } +// +// list := list.New(listItems, list.DefaultDelegate{}, 20, 50) +// list.SetFilteringEnabled(false) +// list.SetShowStatusBar(false) +// list.SetShowHelp(false) +// list.SetShowPagination(false) +// list.SetShowTitle(false) +// +// return &areaPicker{ +// common: common, +// list: list, +// } +// } +// +// func (a *areaPicker) Area() int { +// // switch a.list.SelectedItem() { +// // case item("Task"): +// // return areaTask +// // case item("Tags"): +// // return areaTags +// // case item("Dates"): +// // return areaTime +// // default: +// // return areaTask +// // } +// return 0 +// } +// +// func (a *areaPicker) Init() tea.Cmd { +// return nil +// } +// +// func (a *areaPicker) Update(msg tea.Msg) (tea.Model, tea.Cmd) { +// var cmds []tea.Cmd +// var cmd tea.Cmd +// cursor := a.list.Cursor() +// // switch msg.(type) { +// // case nextFieldMsg: +// // a.list, cmd = a.list.Update(a.list.KeyMap.CursorDown) +// // case prevFieldMsg: +// // a.list, cmd = a.list.Update(a.list.KeyMap.CursorUp) +// // } +// a.list, cmd = a.list.Update(msg) +// cmds = append(cmds, cmd) +// if cursor != a.list.Cursor() { +// cmds = append(cmds, changeArea(a.Area())) +// } +// +// return a, tea.Batch(cmds...) +// } +// +// func (a *areaPicker) View() string { +// return a.list.View() +// } +// +// type EditableField interface { +// tea.Model +// Focus() tea.Cmd +// Blur() tea.Cmd +// } +// +// type taskEdit struct { +// common *common.Common +// fields []EditableField +// cursor int +// +// projectPicker *picker.Picker +// // newProjectName *string +// newAnnotation *string +// udaValues map[string]*string +// } +// +// func NewTaskEdit(com *common.Common, task *taskwarrior.Task, isNew bool) *taskEdit { +// // newProject := "" +// if task.Project == "" { +// task.Project = "(none)" +// } +// +// itemProvider := func() []list.Item { +// projects := com.TW.GetProjects() +// items := []list.Item{picker.NewItem("(none)")} +// for _, proj := range projects { +// items = append(items, picker.NewItem(proj)) +// } +// return items +// } +// onSelect := func(item list.Item) tea.Cmd { +// return nil +// } +// onCreate := func(newProject string) tea.Cmd { +// // The new project name will be used as the project value +// // when the task is saved +// return nil +// } +// +// opts := []picker.PickerOption{picker.WithOnCreate(onCreate)} +// +// // Check if task has a pre-filled project (e.g., from ProjectTaskPickerPage) +// hasPrefilledProject := task.Project != "" && task.Project != "(none)" +// +// if isNew && !hasPrefilledProject { +// // New task with no project → start in filter mode for quick project search +// opts = append(opts, picker.WithFilterByDefault(true)) +// } else { +// // Either existing task OR new task with pre-filled project → show list with project selected +// opts = append(opts, picker.WithDefaultValue(task.Project)) +// } +// +// projPicker := picker.New(com, "Project", itemProvider, onSelect, opts...) +// projPicker.SetSize(70, 8) +// projPicker.Blur() +// +// defaultKeymap := huh.NewDefaultKeyMap() +// +// fields := []EditableField{ +// huh.NewInput(). +// Title("Task"). +// Value(&task.Description). +// Validate(func(desc string) error { +// if desc == "" { +// return fmt.Errorf("task description is required") +// } +// return nil +// }). +// Inline(true). +// Prompt(": "). +// WithTheme(com.Styles.Form), +// +// projPicker, +// +// // huh.NewInput(). +// // Title("New Project"). +// // Value(&newProject). +// // Inline(true). +// // Prompt(": "). +// // WithTheme(com.Styles.Form), +// } +// +// udaValues := make(map[string]*string) +// for _, uda := range com.Udas { +// if uda.Name == "parenttask" { +// continue +// } +// switch uda.Type { +// case taskwarrior.UdaTypeNumeric: +// val := "" +// udaValues[uda.Name] = &val +// fields = append(fields, huh.NewInput(). +// Title(uda.Label). +// Value(udaValues[uda.Name]). +// Validate(taskwarrior.ValidateNumeric). +// Inline(true). +// Prompt(": "). +// WithTheme(com.Styles.Form)) +// case taskwarrior.UdaTypeString: +// if len(uda.Values) > 0 { +// var val string +// values := make([]string, len(uda.Values)) +// for i, v := range uda.Values { +// values[i] = v +// if v == "" { +// values[i] = "(none)" +// } +// if v == uda.Default { +// val = values[i] +// } +// } +// if val == "" { +// val = values[0] +// } +// if v, ok := task.Udas[uda.Name]; ok { +// //TODO: handle uda types correctly +// val = v.(string) +// } +// udaValues[uda.Name] = &val +// +// fields = append(fields, huh.NewSelect[string](). +// Options(huh.NewOptions(values...)...). +// Title(uda.Label). +// Value(udaValues[uda.Name]). +// WithKeyMap(defaultKeymap). +// WithTheme(com.Styles.Form)) +// } else { +// val := "" +// udaValues[uda.Name] = &val +// fields = append(fields, huh.NewInput(). +// Title(uda.Label). +// Value(udaValues[uda.Name]). +// Inline(true). +// Prompt(": "). +// WithTheme(com.Styles.Form)) +// } +// case taskwarrior.UdaTypeDate: +// val := "" +// udaValues[uda.Name] = &val +// fields = append(fields, huh.NewInput(). +// Title(uda.Label). +// Value(udaValues[uda.Name]). +// Validate(taskwarrior.ValidateDate). +// Inline(true). +// Prompt(": "). +// WithTheme(com.Styles.Form)) +// case taskwarrior.UdaTypeDuration: +// val := "" +// udaValues[uda.Name] = &val +// fields = append(fields, huh.NewInput(). +// Title(uda.Label). +// Value(udaValues[uda.Name]). +// Validate(taskwarrior.ValidateDuration). +// Inline(true). +// Prompt(": "). +// WithTheme(com.Styles.Form)) +// } +// } +// +// newAnnotation := "" +// fields = append(fields, huh.NewInput(). +// Title("New Annotation"). +// Value(&newAnnotation). +// Inline(true). +// Prompt(": "). +// WithTheme(com.Styles.Form)) +// +// t := taskEdit{ +// common: com, +// fields: fields, +// projectPicker: projPicker, +// +// udaValues: udaValues, +// +// // newProjectName: &newProject, +// newAnnotation: &newAnnotation, +// } +// +// t.fields[0].Focus() +// +// return &t +// } +// +// func (t *taskEdit) GetName() string { +// return "Task" +// } +// +// func (t *taskEdit) IsFiltering() bool { +// if f, ok := t.fields[t.cursor].(interface{ IsFiltering() bool }); ok { +// return f.IsFiltering() +// } +// return false +// } +// +// func (t *taskEdit) SetCursor(c int) { +// t.fields[t.cursor].Blur() +// if c < 0 { +// t.cursor = len(t.fields) - 1 +// } else { +// t.cursor = c +// } +// t.fields[t.cursor].Focus() +// } +// +// func (t *taskEdit) Init() tea.Cmd { +// var cmds []tea.Cmd +// // Ensure focus on the active field (especially for the first one) +// if len(t.fields) > 0 { +// cmds = append(cmds, func() tea.Msg { +// return focusMsg{} +// }) +// } +// for _, f := range t.fields { +// cmds = append(cmds, f.Init()) +// } +// return tea.Batch(cmds...) +// } +// +// func (t *taskEdit) Update(msg tea.Msg) (tea.Model, tea.Cmd) { +// switch msg.(type) { +// case focusMsg: +// if len(t.fields) > 0 { +// return t, t.fields[t.cursor].Focus() +// } +// case nextFieldMsg: +// if t.cursor == len(t.fields)-1 { +// t.fields[t.cursor].Blur() +// return t, nextArea() +// } +// t.fields[t.cursor].Blur() +// t.cursor++ +// t.fields[t.cursor].Focus() +// case prevFieldMsg: +// if t.cursor == 0 { +// t.fields[t.cursor].Blur() +// return t, prevArea() +// } +// t.fields[t.cursor].Blur() +// t.cursor-- +// t.fields[t.cursor].Focus() +// default: +// field, cmd := t.fields[t.cursor].Update(msg) +// t.fields[t.cursor] = field.(EditableField) +// return t, cmd +// } +// +// return t, nil +// } +// +// func (t *taskEdit) View() string { +// views := make([]string, len(t.fields)) +// for i, field := range t.fields { +// views[i] = field.View() +// if i < len(t.fields)-1 { +// views[i] += "\n" +// } +// } +// return lipgloss.JoinVertical( +// lipgloss.Left, +// views..., +// ) +// } +// +// type tagEdit struct { +// common *common.Common +// fields []huh.Field +// +// cursor int +// } +// +// func NewTagEdit(common *common.Common, selected *[]string, options []string) *tagEdit { +// defaultKeymap := huh.NewDefaultKeyMap() +// +// t := tagEdit{ +// common: common, +// fields: []huh.Field{ +// input.NewMultiSelect(common). +// Options(true, input.NewOptions(options...)...). +// // Key("tags"). +// Title("Tags"). +// Value(selected). +// Filterable(true). +// WithKeyMap(defaultKeymap). +// WithTheme(common.Styles.Form), +// }, +// } +// +// return &t +// } +// +// func (t *tagEdit) GetName() string { +// return "Tags" +// } +// +// func (t *tagEdit) IsFiltering() bool { +// if f, ok := t.fields[t.cursor].(interface{ IsFiltering() bool }); ok { +// return f.IsFiltering() +// } +// return false +// } +// +// func (t *tagEdit) SetCursor(c int) { +// t.fields[t.cursor].Blur() +// if c < 0 { +// t.cursor = len(t.fields) - 1 +// } else { +// t.cursor = c +// } +// t.fields[t.cursor].Focus() +// } +// +// func (t *tagEdit) Init() tea.Cmd { +// return nil +// } +// +// func (t *tagEdit) Update(msg tea.Msg) (tea.Model, tea.Cmd) { +// switch msg.(type) { +// case nextFieldMsg: +// if t.cursor == len(t.fields)-1 { +// t.fields[t.cursor].Blur() +// return t, nextArea() +// } +// t.fields[t.cursor].Blur() +// t.cursor++ +// t.fields[t.cursor].Focus() +// case prevFieldMsg: +// if t.cursor == 0 { +// t.fields[t.cursor].Blur() +// return t, prevArea() +// } +// t.fields[t.cursor].Blur() +// t.cursor-- +// t.fields[t.cursor].Focus() +// default: +// field, cmd := t.fields[t.cursor].Update(msg) +// t.fields[t.cursor] = field.(huh.Field) +// return t, cmd +// } +// return t, nil +// } +// +// func (t tagEdit) View() string { +// views := make([]string, len(t.fields)) +// for i, field := range t.fields { +// views[i] = field.View() +// } +// return lipgloss.JoinVertical( +// lipgloss.Left, +// views..., +// ) +// } +// +// type timeEdit struct { +// common *common.Common +// fields []*timestampeditor.TimestampEditor +// +// cursor int +// +// // Store task field pointers to update them +// due *string +// scheduled *string +// wait *string +// until *string +// } +// +// func NewTimeEdit(common *common.Common, due *string, scheduled *string, wait *string, until *string) *timeEdit { +// // Create timestamp editors for each date field +// dueEditor := timestampeditor.New(common). +// Title("Due"). +// Description("When the task is due"). +// ValueFromString(*due) +// +// scheduledEditor := timestampeditor.New(common). +// Title("Scheduled"). +// Description("When to start working on the task"). +// ValueFromString(*scheduled) +// +// waitEditor := timestampeditor.New(common). +// Title("Wait"). +// Description("Hide task until this date"). +// ValueFromString(*wait) +// +// untilEditor := timestampeditor.New(common). +// Title("Until"). +// Description("Task expires after this date"). +// ValueFromString(*until) +// +// t := timeEdit{ +// common: common, +// fields: []*timestampeditor.TimestampEditor{ +// dueEditor, +// scheduledEditor, +// waitEditor, +// untilEditor, +// }, +// due: due, +// scheduled: scheduled, +// wait: wait, +// until: until, +// } +// +// // Focus the first field +// if len(t.fields) > 0 { +// t.fields[0].Focus() +// } +// +// return &t +// } +// +// func (t *timeEdit) GetName() string { +// return "Dates" +// } +// +// func (t *timeEdit) IsFiltering() bool { +// return false +// } +// +// func (t *timeEdit) SetCursor(c int) { +// if len(t.fields) == 0 { +// return +// } +// +// // Blur the current field +// t.fields[t.cursor].Blur() +// +// // Set new cursor position +// if c < 0 { +// t.cursor = len(t.fields) - 1 +// } else { +// t.cursor = c +// } +// +// // Focus the new field +// t.fields[t.cursor].Focus() +// } +// +// func (t *timeEdit) Init() tea.Cmd { +// return nil +// } +// +// func (t *timeEdit) Update(msg tea.Msg) (tea.Model, tea.Cmd) { +// switch msg := msg.(type) { +// case nextFieldMsg: +// if t.cursor == len(t.fields)-1 { +// // Update task field before moving to next area +// t.syncToTaskFields() +// t.fields[t.cursor].Blur() +// return t, nextArea() +// } +// t.fields[t.cursor].Blur() +// t.cursor++ +// t.fields[t.cursor].Focus() +// return t, nil +// case prevFieldMsg: +// if t.cursor == 0 { +// // Update task field before moving to previous area +// t.syncToTaskFields() +// t.fields[t.cursor].Blur() +// return t, prevArea() +// } +// t.fields[t.cursor].Blur() +// t.cursor-- +// t.fields[t.cursor].Focus() +// return t, nil +// default: +// // Update the current timestamp editor +// model, cmd := t.fields[t.cursor].Update(msg) +// t.fields[t.cursor] = model.(*timestampeditor.TimestampEditor) +// return t, cmd +// } +// } +// +// func (t *timeEdit) View() string { +// views := make([]string, len(t.fields)) +// +// for i, field := range t.fields { +// views[i] = field.View() +// if i < len(t.fields)-1 { +// views[i] += "\n" +// } +// } +// +// return lipgloss.JoinVertical( +// lipgloss.Left, +// views..., +// ) +// } +// +// // syncToTaskFields converts the timestamp editor values back to task field strings +// func (t *timeEdit) syncToTaskFields() { +// // Update the task fields with values from the timestamp editors +// // GetValueString() returns empty string for unset timestamps +// if len(t.fields) > 0 { +// *t.due = t.fields[0].GetValueString() +// } +// if len(t.fields) > 1 { +// *t.scheduled = t.fields[1].GetValueString() +// } +// if len(t.fields) > 2 { +// *t.wait = t.fields[2].GetValueString() +// } +// if len(t.fields) > 3 { +// *t.until = t.fields[3].GetValueString() +// } +// } +// +// type detailsEdit struct { +// com *common.Common +// vp viewport.Model +// ta textarea.Model +// details string +// // renderer *glamour.TermRenderer +// } +// +// func NewDetailsEdit(com *common.Common, task *taskwarrior.Task) *detailsEdit { +// // renderer, err := glamour.NewTermRenderer( +// // // glamour.WithStandardStyle("light"), +// // glamour.WithAutoStyle(), +// // glamour.WithWordWrap(40), +// // ) +// // if err != nil { +// // slog.Error(err.Error()) +// // return nil +// // } +// +// vp := viewport.New(com.Width(), 40-com.Styles.ColumnFocused.GetVerticalFrameSize()) +// ta := textarea.New() +// ta.SetWidth(70) +// ta.SetHeight(40 - com.Styles.ColumnFocused.GetVerticalFrameSize() - 2) +// ta.ShowLineNumbers = false +// ta.FocusedStyle.CursorLine = lipgloss.NewStyle() +// ta.Focus() +// if task.Udas["details"] != nil { +// ta.SetValue(task.Udas["details"].(string)) +// } +// d := detailsEdit{ +// com: com, +// // renderer: renderer, +// vp: vp, +// ta: ta, +// } +// +// return &d +// } +// +// func (d *detailsEdit) GetName() string { +// return "Details" +// } +// +// func (d *detailsEdit) IsFiltering() bool { +// return false +// } +// +// func (d *detailsEdit) SetCursor(c int) { +// } +// +// func (d *detailsEdit) Init() tea.Cmd { +// return textarea.Blink +// } +// +// func (d *detailsEdit) Update(msg tea.Msg) (tea.Model, tea.Cmd) { +// switch msg.(type) { +// case nextFieldMsg: +// return d, nextArea() +// case prevFieldMsg: +// return d, prevArea() +// default: +// var vpCmd, taCmd tea.Cmd +// d.vp, vpCmd = d.vp.Update(msg) +// d.ta, taCmd = d.ta.Update(msg) +// return d, tea.Batch(vpCmd, taCmd) +// } +// } +// +// func (d *detailsEdit) View() string { +// return d.ta.View() +// } +// +// func (p *TaskEditorPage) updateTasksCmd() tea.Msg { +// p.task.Project = p.areas[0].(*taskEdit).projectPicker.GetValue() +// +// if p.task.Project == "(none)" { +// p.task.Project = "" +// } +// +// for _, uda := range p.common.Udas { +// if val, ok := p.areas[0].(*taskEdit).udaValues[uda.Name]; ok { +// if *val == "(none)" { +// *val = "" +// } +// p.task.Udas[uda.Name] = *val +// } +// } +// +// // Sync timestamp fields from the timeEdit area (area 2) +// p.areas[2].(*timeEdit).syncToTaskFields() +// +// if *(p.areas[0].(*taskEdit).newAnnotation) != "" { +// p.task.Annotations = append(p.task.Annotations, taskwarrior.Annotation{ +// Entry: time.Now().Format("20060102T150405Z"), +// Description: *(p.areas[0].(*taskEdit).newAnnotation), +// }) +// } +// +// if _, ok := p.task.Udas["details"]; ok || p.areas[3].(*detailsEdit).ta.Value() != "" { +// p.task.Udas["details"] = p.areas[3].(*detailsEdit).ta.Value() +// } +// +// p.common.TW.ImportTask(&p.task) +// return UpdatedTasksMsg{} +// } +// +// // type StatusLine struct { ... } +// // ... diff --git a/internal/pages/main.go b/internal/pages/main.go index e94c33d..5998e06 100644 --- a/internal/pages/main.go +++ b/internal/pages/main.go @@ -3,8 +3,8 @@ package pages import ( "tasksquire/internal/common" - tea "charm.land/bubbletea/v2" "charm.land/bubbles/v2/key" + tea "charm.land/bubbletea/v2" "charm.land/lipgloss/v2" ) @@ -48,7 +48,7 @@ func (m *MainPage) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.common.SetSize(msg.Width, msg.Height) tabHeight := lipgloss.Height(m.renderTabBar()) - contentHeight := max(msg.Height - tabHeight, 0) + contentHeight := max(msg.Height-tabHeight, 0) newMsg := tea.WindowSizeMsg{Width: msg.Width, Height: contentHeight} activePage, cmd := m.activePage.Update(newMsg) @@ -58,14 +58,14 @@ func (m *MainPage) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case tea.KeyMsg: // Only handle tab key for page switching when at the top level (no subpages active) if key.Matches(msg, m.common.Keymap.Next) && !m.common.HasSubpages() { - // if m.activePage == m.taskPage { - // m.activePage = m.timePage - // m.currentTab = 1 - // } else { - // m.activePage = m.taskPage - // m.currentTab = 0 - // } - // + // if m.activePage == m.taskPage { + // m.activePage = m.timePage + // m.currentTab = 1 + // } else { + // m.activePage = m.taskPage + // m.currentTab = 0 + // } + // tabHeight := lipgloss.Height(m.renderTabBar()) contentHeight := m.height - tabHeight if contentHeight < 0 { @@ -100,7 +100,7 @@ func (m *MainPage) renderTabBar() string { } func (m *MainPage) View() tea.View { - v := tea.NewView(lipgloss.JoinVertical(lipgloss.Left, m.renderTabBar(), m.activePage.View().Content)) + v := tea.NewView(lipgloss.NewStyle().Margin(1, 3).Render(lipgloss.JoinVertical(lipgloss.Left, m.renderTabBar(), m.activePage.View().Content))) v.AltScreen = true return v // return lipgloss.JoinVertical(lipgloss.Left, m.renderTabBar(), m.activePage.View()) diff --git a/internal/pages/tasks.go b/internal/pages/tasks.go index 993f23b..894fd10 100644 --- a/internal/pages/tasks.go +++ b/internal/pages/tasks.go @@ -6,9 +6,9 @@ import ( "tasksquire/internal/components/table" "tasksquire/internal/taskwarrior" + "charm.land/bubbles/v2/key" tea "charm.land/bubbletea/v2" "charm.land/lipgloss/v2" - "charm.land/bubbles/v2/key" ) type TaskPage struct { @@ -33,11 +33,11 @@ type TaskPage struct { func NewTaskPage(com *common.Common, report *taskwarrior.Report) *TaskPage { p := &TaskPage{ - common: com, - activeReport: report, - activeContext: com.TW.GetActiveContext(), - activeProject: "", - taskTable: table.New(), + common: com, + activeReport: report, + activeContext: com.TW.GetActiveContext(), + activeProject: "", + taskTable: table.New(), // detailsPanelActive: false, // detailsViewer: detailsviewer.New(com), } @@ -61,7 +61,7 @@ func (p *TaskPage) SetSize(width int, height int) { // // Set component size (component handles its own border/padding) // // p.detailsViewer.SetSize(baseWidth, detailsHeight) // } else { - tableHeight = baseHeight + tableHeight = baseHeight // } p.taskTable.SetWidth(baseWidth) @@ -75,170 +75,170 @@ func (p *TaskPage) Init() tea.Cmd { func (p *TaskPage) Update(msg tea.Msg) (tea.Model, tea.Cmd) { var cmds []tea.Cmd switch msg := msg.(type) { - case tea.WindowSizeMsg: - p.SetSize(msg.Width, msg.Height) - // case BackMsg: - case common.TickMsg: - cmds = append(cmds, p.getTasks()) - cmds = append(cmds, common.DoTick()) - return p, tea.Batch(cmds...) - case common.TaskMsg: - p.tasks = taskwarrior.Tasks(msg) - p.populateTaskTable(p.tasks) - // case UpdateReportMsg: - // p.activeReport = msg - // cmds = append(cmds, p.getTasks()) - // case UpdateContextMsg: - // p.activeContext = msg - // p.common.TW.SetContext(msg) - // cmds = append(cmds, p.getTasks()) - // case UpdateProjectMsg: - // p.activeProject = string(msg) - // cmds = append(cmds, p.getTasks()) - // case TaskPickedMsg: - // if msg.Task != nil && msg.Task.Status == "pending" { - // p.common.TW.StopActiveTasks() - // p.common.TW.StartTask(msg.Task) - // } - // cmds = append(cmds, p.getTasks()) - // case UpdatedTasksMsg: - // cmds = append(cmds, p.getTasks()) - case tea.KeyPressMsg: - // Handle ESC when details panel is active - // if p.detailsPanelActive && key.Matches(msg, p.common.Keymap.Back) { - // p.detailsPanelActive = false - // p.detailsViewer.Blur() - // p.SetSize(p.common.Width(), p.common.Height()) - // return p, nil - // } + case tea.WindowSizeMsg: + p.SetSize(msg.Width, msg.Height) + // case BackMsg: + case common.TickMsg: + cmds = append(cmds, p.getTasks()) + cmds = append(cmds, common.DoTick()) + return p, tea.Batch(cmds...) + case common.TaskMsg: + p.tasks = taskwarrior.Tasks(msg) + p.populateTaskTable(p.tasks) + // case UpdateReportMsg: + // p.activeReport = msg + // cmds = append(cmds, p.getTasks()) + // case UpdateContextMsg: + // p.activeContext = msg + // p.common.TW.SetContext(msg) + // cmds = append(cmds, p.getTasks()) + // case UpdateProjectMsg: + // p.activeProject = string(msg) + // cmds = append(cmds, p.getTasks()) + // case TaskPickedMsg: + // if msg.Task != nil && msg.Task.Status == "pending" { + // p.common.TW.StopActiveTasks() + // p.common.TW.StartTask(msg.Task) + // } + // cmds = append(cmds, p.getTasks()) + // case UpdatedTasksMsg: + // cmds = append(cmds, p.getTasks()) + case tea.KeyPressMsg: + // Handle ESC when details panel is active + // if p.detailsPanelActive && key.Matches(msg, p.common.Keymap.Back) { + // p.detailsPanelActive = false + // p.detailsViewer.Blur() + // p.SetSize(p.common.Width(), p.common.Height()) + // return p, nil + // } - switch { - case key.Matches(msg, p.common.Keymap.Quit): - return p, tea.Quit - } -// case key.Matches(msg, p.common.Keymap.SetReport): -// p.subpage = NewReportPickerPage(p.common, p.activeReport) -// cmd := p.subpage.Init() -// p.common.PushPage(p) -// return p.subpage, cmd -// case key.Matches(msg, p.common.Keymap.SetContext): -// p.subpage = NewContextPickerPage(p.common) -// cmd := p.subpage.Init() -// p.common.PushPage(p) -// return p.subpage, cmd -// case key.Matches(msg, p.common.Keymap.Add): -// p.subpage = NewTaskEditorPage(p.common, taskwarrior.NewTask()) -// cmd := p.subpage.Init() -// p.common.PushPage(p) -// return p.subpage, cmd -// case key.Matches(msg, p.common.Keymap.Edit): -// p.subpage = NewTaskEditorPage(p.common, *p.selectedTask) -// cmd := p.subpage.Init() -// p.common.PushPage(p) -// return p.subpage, cmd -// case key.Matches(msg, p.common.Keymap.Subtask): -// if p.selectedTask != nil { -// // Create new task inheriting parent's attributes -// newTask := taskwarrior.NewTask() -// -// // Set parent relationship -// newTask.Parent = p.selectedTask.Uuid -// -// // Copy parent's attributes -// newTask.Project = p.selectedTask.Project -// newTask.Priority = p.selectedTask.Priority -// newTask.Tags = make([]string, len(p.selectedTask.Tags)) -// copy(newTask.Tags, p.selectedTask.Tags) -// -// // Copy UDAs (except "details" which is task-specific) -// if p.selectedTask.Udas != nil { -// newTask.Udas = make(map[string]any) -// for k, v := range p.selectedTask.Udas { -// // Skip "details" UDA - it's specific to parent task -// if k == "details" { -// continue -// } -// // Deep copy other UDA values -// newTask.Udas[k] = v -// } -// } -// -// // Open task editor with pre-populated task -// p.subpage = NewTaskEditorPage(p.common, newTask) -// cmd := p.subpage.Init() -// p.common.PushPage(p) -// return p.subpage, cmd -// } -// return p, nil -// case key.Matches(msg, p.common.Keymap.Ok): -// p.common.TW.SetTaskDone(p.selectedTask) -// return p, p.getTasks() -// case key.Matches(msg, p.common.Keymap.Delete): -// p.common.TW.DeleteTask(p.selectedTask) -// return p, p.getTasks() -// case key.Matches(msg, p.common.Keymap.SetProject): -// p.subpage = NewProjectPickerPage(p.common, p.activeProject) -// cmd := p.subpage.Init() -// p.common.PushPage(p) -// return p.subpage, cmd -// case key.Matches(msg, p.common.Keymap.PickProjectTask): -// p.subpage = NewProjectTaskPickerPage(p.common) -// cmd := p.subpage.Init() -// p.common.PushPage(p) -// return p.subpage, cmd -// case key.Matches(msg, p.common.Keymap.Tag): -// if p.selectedTask != nil { -// tag := p.common.TW.GetConfig().Get("uda.tasksquire.tag.default") -// if p.selectedTask.HasTag(tag) { -// p.selectedTask.RemoveTag(tag) -// } else { -// p.selectedTask.AddTag(tag) -// } -// p.common.TW.ImportTask(p.selectedTask) -// return p, p.getTasks() -// } -// return p, nil -// case key.Matches(msg, p.common.Keymap.Undo): -// p.common.TW.Undo() -// return p, p.getTasks() -// case key.Matches(msg, p.common.Keymap.StartStop): -// if p.selectedTask != nil && p.selectedTask.Status == "pending" { -// if p.selectedTask.Start == "" { -// p.common.TW.StopActiveTasks() -// p.common.TW.StartTask(p.selectedTask) -// } else { -// p.common.TW.StopTask(p.selectedTask) -// } -// return p, p.getTasks() -// } -// case key.Matches(msg, p.common.Keymap.ViewDetails): -// if p.selectedTask != nil { -// // Toggle details panel -// p.detailsPanelActive = !p.detailsPanelActive -// if p.detailsPanelActive { -// p.detailsViewer.SetTask(p.selectedTask) -// p.detailsViewer.Focus() -// } else { -// p.detailsViewer.Blur() -// } -// p.SetSize(p.common.Width(), p.common.Height()) -// return p, nil -// } -// } -// } -// - var cmd tea.Cmd -// -// // Route keyboard messages to details viewer when panel is active -// if p.detailsPanelActive { -// var viewerCmd tea.Cmd -// var viewerModel tea.Model -// viewerModel, viewerCmd = p.detailsViewer.Update(msg) -// p.detailsViewer = viewerModel.(*detailsviewer.DetailsViewer) -// cmds = append(cmds, viewerCmd) -// } else { -// // Route to table when details panel not active - p.taskTable, cmd = p.taskTable.Update(msg) + switch { + case key.Matches(msg, p.common.Keymap.Quit): + return p, tea.Quit + } + // case key.Matches(msg, p.common.Keymap.SetReport): + // p.subpage = NewReportPickerPage(p.common, p.activeReport) + // cmd := p.subpage.Init() + // p.common.PushPage(p) + // return p.subpage, cmd + // case key.Matches(msg, p.common.Keymap.SetContext): + // p.subpage = NewContextPickerPage(p.common) + // cmd := p.subpage.Init() + // p.common.PushPage(p) + // return p.subpage, cmd + // case key.Matches(msg, p.common.Keymap.Add): + // p.subpage = NewTaskEditorPage(p.common, taskwarrior.NewTask()) + // cmd := p.subpage.Init() + // p.common.PushPage(p) + // return p.subpage, cmd + // case key.Matches(msg, p.common.Keymap.Edit): + // p.subpage = NewTaskEditorPage(p.common, *p.selectedTask) + // cmd := p.subpage.Init() + // p.common.PushPage(p) + // return p.subpage, cmd + // case key.Matches(msg, p.common.Keymap.Subtask): + // if p.selectedTask != nil { + // // Create new task inheriting parent's attributes + // newTask := taskwarrior.NewTask() + // + // // Set parent relationship + // newTask.Parent = p.selectedTask.Uuid + // + // // Copy parent's attributes + // newTask.Project = p.selectedTask.Project + // newTask.Priority = p.selectedTask.Priority + // newTask.Tags = make([]string, len(p.selectedTask.Tags)) + // copy(newTask.Tags, p.selectedTask.Tags) + // + // // Copy UDAs (except "details" which is task-specific) + // if p.selectedTask.Udas != nil { + // newTask.Udas = make(map[string]any) + // for k, v := range p.selectedTask.Udas { + // // Skip "details" UDA - it's specific to parent task + // if k == "details" { + // continue + // } + // // Deep copy other UDA values + // newTask.Udas[k] = v + // } + // } + // + // // Open task editor with pre-populated task + // p.subpage = NewTaskEditorPage(p.common, newTask) + // cmd := p.subpage.Init() + // p.common.PushPage(p) + // return p.subpage, cmd + // } + // return p, nil + // case key.Matches(msg, p.common.Keymap.Ok): + // p.common.TW.SetTaskDone(p.selectedTask) + // return p, p.getTasks() + // case key.Matches(msg, p.common.Keymap.Delete): + // p.common.TW.DeleteTask(p.selectedTask) + // return p, p.getTasks() + // case key.Matches(msg, p.common.Keymap.SetProject): + // p.subpage = NewProjectPickerPage(p.common, p.activeProject) + // cmd := p.subpage.Init() + // p.common.PushPage(p) + // return p.subpage, cmd + // case key.Matches(msg, p.common.Keymap.PickProjectTask): + // p.subpage = NewProjectTaskPickerPage(p.common) + // cmd := p.subpage.Init() + // p.common.PushPage(p) + // return p.subpage, cmd + // case key.Matches(msg, p.common.Keymap.Tag): + // if p.selectedTask != nil { + // tag := p.common.TW.GetConfig().Get("uda.tasksquire.tag.default") + // if p.selectedTask.HasTag(tag) { + // p.selectedTask.RemoveTag(tag) + // } else { + // p.selectedTask.AddTag(tag) + // } + // p.common.TW.ImportTask(p.selectedTask) + // return p, p.getTasks() + // } + // return p, nil + // case key.Matches(msg, p.common.Keymap.Undo): + // p.common.TW.Undo() + // return p, p.getTasks() + // case key.Matches(msg, p.common.Keymap.StartStop): + // if p.selectedTask != nil && p.selectedTask.Status == "pending" { + // if p.selectedTask.Start == "" { + // p.common.TW.StopActiveTasks() + // p.common.TW.StartTask(p.selectedTask) + // } else { + // p.common.TW.StopTask(p.selectedTask) + // } + // return p, p.getTasks() + // } + // case key.Matches(msg, p.common.Keymap.ViewDetails): + // if p.selectedTask != nil { + // // Toggle details panel + // p.detailsPanelActive = !p.detailsPanelActive + // if p.detailsPanelActive { + // p.detailsViewer.SetTask(p.selectedTask) + // p.detailsViewer.Focus() + // } else { + // p.detailsViewer.Blur() + // } + // p.SetSize(p.common.Width(), p.common.Height()) + // return p, nil + // } + // } + // } + // + var cmd tea.Cmd + // + // // Route keyboard messages to details viewer when panel is active + // if p.detailsPanelActive { + // var viewerCmd tea.Cmd + // var viewerModel tea.Model + // viewerModel, viewerCmd = p.detailsViewer.Update(msg) + // p.detailsViewer = viewerModel.(*detailsviewer.DetailsViewer) + // cmds = append(cmds, viewerCmd) + // } else { + // // Route to table when details panel not active + p.taskTable, cmd = p.taskTable.Update(msg) cmds = append(cmds, cmd) if len(p.tasks) > 0 { @@ -246,7 +246,7 @@ func (p *TaskPage) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } else { p.selectedTask = nil } - // } + // } } return p, tea.Batch(cmds...) @@ -259,21 +259,23 @@ func (p *TaskPage) View() tea.View { tableView := p.taskTable.View() + // + // if !p.detailsPanelActive { + // return tableView + // } + // + // // Combine table and details panel vertically + // return lipgloss.JoinVertical( + // lipgloss.Left, + // tableView, + // p.detailsViewer.View(), + // ) + // } + // + return tea.NewView(tableView) } -// -// if !p.detailsPanelActive { -// return tableView -// } -// -// // Combine table and details panel vertically -// return lipgloss.JoinVertical( -// lipgloss.Left, -// tableView, -// p.detailsViewer.View(), -// ) -// } -// + func (p *TaskPage) populateTaskTable(tasks taskwarrior.Tasks) { if len(tasks) == 0 { return @@ -286,15 +288,15 @@ func (p *TaskPage) populateTaskTable(tasks taskwarrior.Tasks) { baseWidth := p.common.Width() - p.common.Styles.Base.GetHorizontalFrameSize() var tableHeight int -// if p.detailsPanelActive { -// // Allocate 60% for table, 40% for details panel -// // Minimum 5 lines for details, minimum 10 lines for table - // detailsHeight := max(min(baseHeight*2/5, baseHeight-10), 5) - // tableHeight = baseHeight - detailsHeight - 1 // -1 for spacing -// } else { - tableHeight = baseHeight -// } -// + // if p.detailsPanelActive { + // // Allocate 60% for table, 40% for details panel + // // Minimum 5 lines for details, minimum 10 lines for table + // detailsHeight := max(min(baseHeight*2/5, baseHeight-10), 5) + // tableHeight = baseHeight - detailsHeight - 1 // -1 for spacing + // } else { + tableHeight = baseHeight + // } + // numCols := len(p.activeReport.Columns) taskRows := make([]table.Row, len(tasks)) @@ -302,40 +304,40 @@ func (p *TaskPage) populateTaskTable(tasks taskwarrior.Tasks) { widths := make([]int, numCols) for i, task := range tasks { - row := make(table.Row, numCols) - for j, colKey := range p.activeReport.Columns { - val := task.GetString(colKey) - row[j] = val - widths[j] = max(widths[j], lipgloss.Width(val)) - } - taskRows[i] = row - taskStyles[i] = common.GetTaskTabelStyle(task, *p.common) + row := make(table.Row, numCols) + for j, colKey := range p.activeReport.Columns { + val := task.GetString(colKey) + row[j] = val + widths[j] = max(widths[j], lipgloss.Width(val)) + } + taskRows[i] = row + taskStyles[i] = common.GetTaskTabelStyle(task, *p.common) } var columns []table.Column for j, w := range widths { - title := p.activeReport.Labels[j] + title := p.activeReport.Labels[j] - width := 0 - if w > 0 { - width = max(w, lipgloss.Width(title)) - } - - columns = append(columns, table.Column{ - Title: title, - Width: width, - }) + width := 0 + if w > 0 { + width = max(w, lipgloss.Width(title)) + } + + columns = append(columns, table.Column{ + Title: title, + Width: width, + }) } if len(columns) > 0 { - usedWidth := 0 - for i := 0; i < len(columns)-1; i++ { - usedWidth += columns[i].Width + 1 // padding/border offset - } + usedWidth := 0 + for i := 0; i < len(columns)-1; i++ { + usedWidth += columns[i].Width + 1 // padding/border offset + } - remaining := p.taskTable.Width() - usedWidth - 1 - lastIdx := len(columns) - 1 - columns[lastIdx].Width = max(columns[lastIdx].Width, remaining) + remaining := p.taskTable.Width() - usedWidth - 1 + lastIdx := len(columns) - 1 + columns[lastIdx].Width = max(columns[lastIdx].Width, remaining) } p.taskTable = table.New( @@ -346,9 +348,9 @@ func (p *TaskPage) populateTaskTable(tasks taskwarrior.Tasks) { table.WithWidth(baseWidth), table.WithHeight(tableHeight), table.WithStyles(table.Styles{ - Header: p.common.Styles.TableStyle.Header , - Cell: p.common.Styles.TableStyle.Cell, - Selected: p.common.Styles.TableStyle.Selected, + Header: p.common.Styles.TableStyle.Header, + Cell: p.common.Styles.TableStyle.Cell, + Selected: p.common.Styles.TableStyle.Selected, }), ) @@ -360,11 +362,11 @@ func (p *TaskPage) populateTaskTable(tasks taskwarrior.Tasks) { } else { p.taskTable.SetCursor(len(p.tasks) - 1) } -// -// // Refresh details content if panel is active -// if p.detailsPanelActive && p.selectedTask != nil { -// p.detailsViewer.SetTask(p.selectedTask) -// } + // // Refresh details content if panel is active + // + // if p.detailsPanelActive && p.selectedTask != nil { + // p.detailsViewer.SetTask(p.selectedTask) + // } } func (p *TaskPage) getTasks() tea.Cmd { diff --git a/todo.md b/todo.md new file mode 100644 index 0000000..ac2880d --- /dev/null +++ b/todo.md @@ -0,0 +1,10 @@ +- [>] Integrate task editor + - v1 + * Single huh form + - v2 + * custom pages +- [ ] Integrate report switcher + - [ ] Use modal (lipgloss compositing?) + - [ ] Make modal reusable for other switchers + +- [x] Add padding to main page