From ada7db46cf312b4426bcb7d6c36b9fafdcb2d86f Mon Sep 17 00:00:00 2001 From: XeRo Date: Mon, 29 Dec 2025 17:46:48 +0500 Subject: [PATCH] =?UTF-8?q?=D0=A0=D0=B0=D0=B1=D0=BE=D1=82=D0=B0=20=D0=BD?= =?UTF-8?q?=D0=B0=D0=B4=20=D1=81=D1=82=D0=B8=D0=BB=D1=8F=D0=BC=D0=B8,=20?= =?UTF-8?q?=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D0=B0=20=D0=BD=D0=B0=D0=B4=20?= =?UTF-8?q?=D0=BC=D0=BE=D0=B4=D0=B0=D0=BB=D1=8C=D0=BD=D1=8B=D0=BC=D0=B8=20?= =?UTF-8?q?=D0=BE=D0=BA=D0=BD=D0=B0=D0=BC=D0=B8,=20=D0=B8=D0=B7=D0=BC?= =?UTF-8?q?=D0=B5=D0=BD=D0=B5=D0=BD=D0=B8=D0=B5=20=D1=81=D0=BF=D0=B8=D1=81?= =?UTF-8?q?=D0=BA=D0=B0=20=D0=BF=D1=80=D0=B8=D0=BB=D0=BE=D0=B6=D0=B5=D0=BD?= =?UTF-8?q?=D0=B8=D0=B9,=20=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D0=B0=20=D1=81?= =?UTF-8?q?=20=D1=80=D0=B0=D0=B7=D0=BC=D0=B5=D1=80=D0=B0=D0=BC=D0=B8=20?= =?UTF-8?q?=D0=BE=D0=BA=D0=BE=D0=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + go.mod | 2 +- log.txt | 436 ++++++++++++++++++++++++++++++++++++++++++++++ setup/final.go | 108 +++++++++--- setup/form.go | 120 ++++++++++--- setup/list.go | 139 +++++++++++---- setup/main.go | 84 +++++++-- setup/modal.go | 185 ++++++++++++++++++++ setup/ui/style.go | 181 ++++++++++++++++--- types/modal.go | 10 ++ types/msg.go | 21 ++- types/names.go | 4 + 12 files changed, 1161 insertions(+), 130 deletions(-) create mode 100644 setup/modal.go create mode 100644 types/modal.go create mode 100644 types/names.go diff --git a/.gitignore b/.gitignore index 78361d2..df6058f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ *.tmp *.log +*.txt /vendor/ /bin/ diff --git a/go.mod b/go.mod index 7b0936a..8c0a991 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,7 @@ require ( github.com/charmbracelet/glamour v0.10.0 github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834 github.com/expr-lang/expr v1.17.7 + github.com/muesli/reflow v0.3.0 github.com/sirupsen/logrus v1.9.3 gopkg.in/natefinch/lumberjack.v2 v2.2.1 gopkg.in/yaml.v3 v3.0.1 @@ -36,7 +37,6 @@ require ( github.com/microcosm-cc/bluemonday v1.0.27 // indirect github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect github.com/muesli/cancelreader v0.2.2 // indirect - github.com/muesli/reflow v0.3.0 // indirect github.com/muesli/termenv v0.16.0 // indirect github.com/rivo/uniseg v0.4.7 // indirect github.com/sahilm/fuzzy v0.1.1 // indirect diff --git a/log.txt b/log.txt index e69de29..12d6817 100644 --- a/log.txt +++ b/log.txt @@ -0,0 +1,436 @@ +time=13:34:29 29-12-2025 level=info msg=modal close msg get +time=13:34:29 29-12-2025 level=info msg=sections Repko about, focus 0, fields len 0 +time=13:34:30 29-12-2025 level=info msg=sections user, focus 0, fields len 2 +time=13:34:30 29-12-2025 level=info msg=1, focus is 0 +time=13:34:30 29-12-2025 level=info msg=field choose_user, capture keys true +time=13:34:30 29-12-2025 level=info msg=1, focus is 0 +time=13:34:30 29-12-2025 level=info msg=field choose_user, capture keys true +time=13:34:31 29-12-2025 level=info msg=1, focus is 0 +time=13:34:31 29-12-2025 level=info msg=field choose_user, capture keys false +time=13:34:32 29-12-2025 level=info msg=1, focus is 1 +time=13:34:32 29-12-2025 level=info msg=field new_user, capture keys false +time=13:34:32 29-12-2025 level=info msg=sections user, focus 2, fields len 2 +time=13:34:32 29-12-2025 level=info msg=1, focus is 0 +time=13:34:32 29-12-2025 level=info msg=field api_domain, capture keys false +time=13:34:32 29-12-2025 level=info msg=1, focus is 1 +time=13:34:32 29-12-2025 level=info msg=field panel_domain, capture keys false +time=13:34:33 29-12-2025 level=info msg=1, focus is 2 +time=13:34:33 29-12-2025 level=info msg=field cookie_domain, capture keys false +time=13:34:33 29-12-2025 level=info msg=1, focus is 3 +time=13:34:33 29-12-2025 level=info msg=field is_secure, capture keys false +time=13:34:33 29-12-2025 level=info msg=1, focus is 4 +time=13:34:33 29-12-2025 level=info msg=field server_address, capture keys false +time=13:34:34 29-12-2025 level=info msg=sections domain, focus 5, fields len 5 +time=13:34:34 29-12-2025 level=info msg=1, focus is 0 +time=13:34:34 29-12-2025 level=info msg=field database_type, capture keys false +time=13:34:35 29-12-2025 level=info msg=sections database, focus 1, fields len 1 +time=13:34:36 29-12-2025 level=info msg=sections paths, focus 0, fields len 1 +time=13:34:37 29-12-2025 level=info msg=1, focus is 0 +time=13:34:37 29-12-2025 level=info msg=field debian_root, capture keys true +time=13:34:37 29-12-2025 level=info msg=1, focus is 0 +time=13:34:37 29-12-2025 level=info msg=field debian_root, capture keys true +time=13:34:37 29-12-2025 level=info msg=sections paths, focus 0, fields len 1 +time=13:54:35 29-12-2025 level=info msg=modal close msg get +time=13:54:37 29-12-2025 level=info msg=sections Repko about, focus 0, fields len 0 +time=13:54:38 29-12-2025 level=info msg=1, focus is 0 +time=13:54:38 29-12-2025 level=info msg=field choose_user, capture keys false +time=13:54:38 29-12-2025 level=info msg=1, focus is 1 +time=13:54:38 29-12-2025 level=info msg=field new_user, capture keys false +time=13:54:39 29-12-2025 level=info msg=2 +time=13:54:39 29-12-2025 level=info msg=1, focus is 1 +time=13:54:39 29-12-2025 level=info msg=field new_user, capture keys false +time=13:54:40 29-12-2025 level=info msg=sections user, focus 0, fields len 2 +time=13:54:40 29-12-2025 level=info msg=1, focus is 0 +time=13:54:40 29-12-2025 level=info msg=field choose_user, capture keys true +time=13:54:41 29-12-2025 level=info msg=sections user, focus 0, fields len 2 +time=13:54:41 29-12-2025 level=info msg=chose {FieldIndex:1 Value:Existing} +time=13:54:42 29-12-2025 level=info msg=1, focus is 0 +time=13:54:42 29-12-2025 level=info msg=field choose_user, capture keys false +time=13:54:42 29-12-2025 level=info msg=1, focus is 1 +time=13:54:42 29-12-2025 level=info msg=field select_user, capture keys false +time=13:54:42 29-12-2025 level=info msg=sections user, focus 2, fields len 2 +time=13:54:42 29-12-2025 level=info msg=1, focus is 0 +time=13:54:42 29-12-2025 level=info msg=field api_domain, capture keys false +time=13:54:43 29-12-2025 level=info msg=1, focus is 1 +time=13:54:43 29-12-2025 level=info msg=field panel_domain, capture keys false +time=13:54:43 29-12-2025 level=info msg=1, focus is 2 +time=13:54:43 29-12-2025 level=info msg=field cookie_domain, capture keys false +time=13:54:43 29-12-2025 level=info msg=1, focus is 3 +time=13:54:43 29-12-2025 level=info msg=field is_secure, capture keys false +time=13:54:43 29-12-2025 level=info msg=1, focus is 4 +time=13:54:43 29-12-2025 level=info msg=field server_address, capture keys false +time=13:54:44 29-12-2025 level=info msg=sections domain, focus 5, fields len 5 +time=13:54:44 29-12-2025 level=info msg=1, focus is 0 +time=13:54:44 29-12-2025 level=info msg=field database_type, capture keys false +time=13:54:44 29-12-2025 level=info msg=sections database, focus 1, fields len 1 +time=13:54:45 29-12-2025 level=info msg=1, focus is 0 +time=13:54:45 29-12-2025 level=info msg=field debian_root, capture keys false +time=13:54:45 29-12-2025 level=info msg=sections paths, focus 1, fields len 1 +time=13:57:35 29-12-2025 level=info msg=modal close msg get +time=14:00:04 29-12-2025 level=info msg=modal close msg get +time=14:08:24 29-12-2025 level=info msg=modal close msg get +time=14:08:25 29-12-2025 level=info msg=sections Repko about, focus 0, fields len 0 +time=14:08:25 29-12-2025 level=info msg=1, focus is 0 +time=14:08:25 29-12-2025 level=info msg=field choose_user, capture keys false +time=14:08:26 29-12-2025 level=info msg=1, focus is 1 +time=14:08:26 29-12-2025 level=info msg=field new_user, capture keys false +time=14:08:26 29-12-2025 level=info msg=sections user, focus 2, fields len 2 +time=14:08:28 29-12-2025 level=info msg=1, focus is 0 +time=14:08:28 29-12-2025 level=info msg=field api_domain, capture keys false +time=14:28:24 29-12-2025 level=info msg=modal close msg get +time=14:28:25 29-12-2025 level=info msg=2 +time=14:28:25 29-12-2025 level=info msg=2 +time=14:28:25 29-12-2025 level=info msg=sections Repko about, focus 0, fields len 0 +time=14:28:26 29-12-2025 level=info msg=1, focus is 0 +time=14:28:26 29-12-2025 level=info msg=field choose_user, capture keys false +time=14:28:26 29-12-2025 level=info msg=1, focus is 1 +time=14:28:26 29-12-2025 level=info msg=field new_user, capture keys false +time=14:28:27 29-12-2025 level=info msg=2 +time=14:28:27 29-12-2025 level=info msg=1, focus is 1 +time=14:28:27 29-12-2025 level=info msg=field new_user, capture keys false +time=14:28:28 29-12-2025 level=info msg=1, focus is 0 +time=14:28:28 29-12-2025 level=info msg=field choose_user, capture keys false +time=14:28:29 29-12-2025 level=info msg=1, focus is 1 +time=14:28:29 29-12-2025 level=info msg=field new_user, capture keys false +time=14:28:29 29-12-2025 level=info msg=1, focus is 0 +time=14:28:29 29-12-2025 level=info msg=field choose_user, capture keys false +time=14:28:29 29-12-2025 level=info msg=1, focus is 1 +time=14:28:29 29-12-2025 level=info msg=field new_user, capture keys false +time=14:28:49 29-12-2025 level=info msg=modal close msg get +time=14:28:50 29-12-2025 level=info msg=2 +time=14:28:50 29-12-2025 level=info msg=sections Repko about, focus 0, fields len 0 +time=14:28:50 29-12-2025 level=info msg=1, focus is 0 +time=14:28:50 29-12-2025 level=info msg=field choose_user, capture keys false +time=14:28:51 29-12-2025 level=info msg=1, focus is 1 +time=14:28:51 29-12-2025 level=info msg=field new_user, capture keys false +time=14:28:52 29-12-2025 level=info msg=sections user, focus 2, fields len 2 +time=14:28:52 29-12-2025 level=info msg=1, focus is 0 +time=14:28:52 29-12-2025 level=info msg=field api_domain, capture keys false +time=14:28:53 29-12-2025 level=info msg=1, focus is 1 +time=14:28:53 29-12-2025 level=info msg=field panel_domain, capture keys false +time=14:28:53 29-12-2025 level=info msg=1, focus is 2 +time=14:28:53 29-12-2025 level=info msg=field cookie_domain, capture keys false +time=14:29:53 29-12-2025 level=info msg=1, focus is 1 +time=14:29:53 29-12-2025 level=info msg=field panel_domain, capture keys false +time=14:29:53 29-12-2025 level=info msg=1, focus is 0 +time=14:29:53 29-12-2025 level=info msg=field api_domain, capture keys false +time=14:29:53 29-12-2025 level=info msg=2 +time=14:29:53 29-12-2025 level=info msg=1, focus is 4 +time=14:29:53 29-12-2025 level=info msg=field server_address, capture keys false +time=14:29:53 29-12-2025 level=info msg=1, focus is 3 +time=14:29:53 29-12-2025 level=info msg=field is_secure, capture keys false +time=14:29:53 29-12-2025 level=info msg=1, focus is 2 +time=14:29:53 29-12-2025 level=info msg=field cookie_domain, capture keys false +time=14:29:53 29-12-2025 level=info msg=1, focus is 1 +time=14:29:53 29-12-2025 level=info msg=field panel_domain, capture keys false +time=14:29:53 29-12-2025 level=info msg=1, focus is 0 +time=14:29:53 29-12-2025 level=info msg=field api_domain, capture keys false +time=14:29:53 29-12-2025 level=info msg=2 +time=14:29:53 29-12-2025 level=info msg=1, focus is 4 +time=14:29:53 29-12-2025 level=info msg=field server_address, capture keys false +time=14:29:53 29-12-2025 level=info msg=1, focus is 3 +time=14:29:53 29-12-2025 level=info msg=field is_secure, capture keys false +time=14:29:53 29-12-2025 level=info msg=1, focus is 2 +time=14:29:53 29-12-2025 level=info msg=field cookie_domain, capture keys false +time=14:29:53 29-12-2025 level=info msg=1, focus is 1 +time=14:29:53 29-12-2025 level=info msg=field panel_domain, capture keys false +time=14:29:53 29-12-2025 level=info msg=1, focus is 0 +time=14:29:53 29-12-2025 level=info msg=field api_domain, capture keys false +time=14:29:53 29-12-2025 level=info msg=2 +time=14:29:53 29-12-2025 level=info msg=1, focus is 4 +time=14:29:53 29-12-2025 level=info msg=field server_address, capture keys false +time=14:29:53 29-12-2025 level=info msg=1, focus is 3 +time=14:29:53 29-12-2025 level=info msg=field is_secure, capture keys false +time=14:29:53 29-12-2025 level=info msg=1, focus is 2 +time=14:29:53 29-12-2025 level=info msg=field cookie_domain, capture keys false +time=14:29:53 29-12-2025 level=info msg=1, focus is 1 +time=14:29:53 29-12-2025 level=info msg=field panel_domain, capture keys false +time=14:29:53 29-12-2025 level=info msg=1, focus is 2 +time=14:29:53 29-12-2025 level=info msg=field cookie_domain, capture keys false +time=14:29:53 29-12-2025 level=info msg=1, focus is 3 +time=14:29:53 29-12-2025 level=info msg=field is_secure, capture keys false +time=14:29:53 29-12-2025 level=info msg=1, focus is 4 +time=14:29:53 29-12-2025 level=info msg=field server_address, capture keys false +time=14:29:53 29-12-2025 level=info msg=2 +time=14:29:53 29-12-2025 level=info msg=1, focus is 0 +time=14:29:53 29-12-2025 level=info msg=field api_domain, capture keys false +time=14:29:53 29-12-2025 level=info msg=1, focus is 1 +time=14:29:53 29-12-2025 level=info msg=field panel_domain, capture keys false +time=14:29:53 29-12-2025 level=info msg=1, focus is 2 +time=14:29:53 29-12-2025 level=info msg=field cookie_domain, capture keys false +time=14:29:53 29-12-2025 level=info msg=1, focus is 3 +time=14:29:53 29-12-2025 level=info msg=field is_secure, capture keys false +time=14:29:53 29-12-2025 level=info msg=1, focus is 4 +time=14:29:53 29-12-2025 level=info msg=field server_address, capture keys false +time=14:29:53 29-12-2025 level=info msg=2 +time=14:29:53 29-12-2025 level=info msg=1, focus is 0 +time=14:29:53 29-12-2025 level=info msg=field api_domain, capture keys false +time=14:29:53 29-12-2025 level=info msg=1, focus is 1 +time=14:29:53 29-12-2025 level=info msg=field panel_domain, capture keys false +time=14:29:53 29-12-2025 level=info msg=1, focus is 2 +time=14:29:53 29-12-2025 level=info msg=field cookie_domain, capture keys false +time=14:29:53 29-12-2025 level=info msg=1, focus is 3 +time=14:29:53 29-12-2025 level=info msg=field is_secure, capture keys false +time=14:29:53 29-12-2025 level=info msg=1, focus is 4 +time=14:29:53 29-12-2025 level=info msg=field server_address, capture keys false +time=14:29:53 29-12-2025 level=info msg=2 +time=14:29:53 29-12-2025 level=info msg=1, focus is 0 +time=14:29:53 29-12-2025 level=info msg=field api_domain, capture keys false +time=14:29:54 29-12-2025 level=info msg=1, focus is 1 +time=14:29:54 29-12-2025 level=info msg=field panel_domain, capture keys false +time=14:29:54 29-12-2025 level=info msg=1, focus is 0 +time=14:29:54 29-12-2025 level=info msg=field api_domain, capture keys false +time=14:29:54 29-12-2025 level=info msg=1, focus is 1 +time=14:29:54 29-12-2025 level=info msg=field panel_domain, capture keys false +time=14:30:06 29-12-2025 level=info msg=modal close msg get +time=14:30:08 29-12-2025 level=info msg=sections Repko about, focus 0, fields len 0 +time=14:31:23 29-12-2025 level=info msg=1, focus is 0 +time=14:31:23 29-12-2025 level=info msg=field choose_user, capture keys false +time=14:31:23 29-12-2025 level=info msg=1, focus is 1 +time=14:31:23 29-12-2025 level=info msg=field new_user, capture keys false +time=14:31:24 29-12-2025 level=info msg=sections user, focus 2, fields len 2 +time=14:32:32 29-12-2025 level=info msg=modal close msg get +time=14:38:03 29-12-2025 level=info msg=modal close msg get +time=14:38:04 29-12-2025 level=info msg=2 +time=14:38:04 29-12-2025 level=info msg=sections Repko about, focus 0, fields len 0 +time=14:38:05 29-12-2025 level=info msg=1, focus is 0 +time=14:38:05 29-12-2025 level=info msg=field choose_user, capture keys false +time=14:38:05 29-12-2025 level=info msg=1, focus is 1 +time=14:38:05 29-12-2025 level=info msg=field new_user, capture keys false +time=14:38:06 29-12-2025 level=info msg=2 +time=14:38:06 29-12-2025 level=info msg=1, focus is 1 +time=14:38:06 29-12-2025 level=info msg=field new_user, capture keys false +time=14:38:07 29-12-2025 level=info msg=2 +time=14:38:07 29-12-2025 level=info msg=1, focus is 1 +time=14:38:07 29-12-2025 level=info msg=field new_user, capture keys false +time=14:38:08 29-12-2025 level=info msg=sections user, focus 2, fields len 2 +time=14:38:08 29-12-2025 level=info msg=1, focus is 0 +time=14:38:08 29-12-2025 level=info msg=field api_domain, capture keys false +time=14:38:09 29-12-2025 level=info msg=1, focus is 1 +time=14:38:09 29-12-2025 level=info msg=field panel_domain, capture keys false +time=14:38:09 29-12-2025 level=info msg=1, focus is 2 +time=14:38:09 29-12-2025 level=info msg=field cookie_domain, capture keys false +time=14:38:09 29-12-2025 level=info msg=1, focus is 3 +time=14:38:09 29-12-2025 level=info msg=field is_secure, capture keys false +time=14:38:10 29-12-2025 level=info msg=1, focus is 2 +time=14:38:10 29-12-2025 level=info msg=field cookie_domain, capture keys false +time=14:39:11 29-12-2025 level=info msg=modal close msg get +time=14:39:11 29-12-2025 level=info msg=sections Repko about, focus 0, fields len 0 +time=14:39:52 29-12-2025 level=info msg=modal close msg get +time=14:40:10 29-12-2025 level=info msg=modal close msg get +time=14:40:12 29-12-2025 level=info msg=sections Repko about, focus 0, fields len 0 +time=14:40:31 29-12-2025 level=info msg=modal close msg get +time=14:40:32 29-12-2025 level=info msg=sections Repko about, focus 0, fields len 0 +time=14:40:32 29-12-2025 level=info msg=1, focus is 0 +time=14:40:32 29-12-2025 level=info msg=field choose_user, capture keys false +time=14:40:32 29-12-2025 level=info msg=1, focus is 1 +time=14:40:32 29-12-2025 level=info msg=field new_user, capture keys false +time=14:40:33 29-12-2025 level=info msg=sections user, focus 2, fields len 2 +time=14:40:33 29-12-2025 level=info msg=1, focus is 0 +time=14:40:33 29-12-2025 level=info msg=field api_domain, capture keys false +time=14:40:33 29-12-2025 level=info msg=1, focus is 1 +time=14:40:33 29-12-2025 level=info msg=field panel_domain, capture keys false +time=14:40:34 29-12-2025 level=info msg=1, focus is 2 +time=14:40:34 29-12-2025 level=info msg=field cookie_domain, capture keys false +time=14:40:34 29-12-2025 level=info msg=1, focus is 3 +time=14:40:34 29-12-2025 level=info msg=field is_secure, capture keys false +time=14:40:34 29-12-2025 level=info msg=1, focus is 4 +time=14:40:34 29-12-2025 level=info msg=field server_address, capture keys false +time=14:40:34 29-12-2025 level=info msg=2 +time=14:41:48 29-12-2025 level=info msg=1, focus is 0 +time=14:41:48 29-12-2025 level=info msg=field api_domain, capture keys false +time=14:41:48 29-12-2025 level=info msg=1, focus is 1 +time=14:41:48 29-12-2025 level=info msg=field panel_domain, capture keys false +time=14:41:48 29-12-2025 level=info msg=1, focus is 2 +time=14:41:48 29-12-2025 level=info msg=field cookie_domain, capture keys false +time=14:41:48 29-12-2025 level=info msg=1, focus is 3 +time=14:41:48 29-12-2025 level=info msg=field is_secure, capture keys false +time=14:41:48 29-12-2025 level=info msg=1, focus is 4 +time=14:41:48 29-12-2025 level=info msg=field server_address, capture keys false +time=14:41:49 29-12-2025 level=info msg=sections domain, focus 3, fields len 5 +time=14:41:49 29-12-2025 level=info msg=1, focus is 3 +time=14:41:49 29-12-2025 level=info msg=field is_secure, capture keys false +time=14:41:49 29-12-2025 level=info msg=1, focus is 4 +time=14:41:49 29-12-2025 level=info msg=field server_address, capture keys false +time=14:41:49 29-12-2025 level=info msg=sections domain, focus 5, fields len 5 +time=14:41:52 29-12-2025 level=info msg=1, focus is 0 +time=14:41:52 29-12-2025 level=info msg=field ssl_type, capture keys false +time=14:41:53 29-12-2025 level=info msg=sections ssl, focus 1, fields len 3 +time=14:41:53 29-12-2025 level=info msg=1, focus is 1 +time=14:41:53 29-12-2025 level=info msg=field ssl_cert_path, capture keys true +time=14:41:53 29-12-2025 level=info msg=1, focus is 1 +time=14:41:53 29-12-2025 level=info msg=field ssl_cert_path, capture keys true +time=14:41:54 29-12-2025 level=info msg=1, focus is 1 +time=14:41:54 29-12-2025 level=info msg=field ssl_cert_path, capture keys true +time=14:41:54 29-12-2025 level=info msg=sections ssl, focus 1, fields len 3 +time=14:41:55 29-12-2025 level=info msg=sections ssl, focus 1, fields len 3 +time=14:41:56 29-12-2025 level=info msg=sections ssl, focus 1, fields len 3 +time=14:41:57 29-12-2025 level=info msg=sections ssl, focus 1, fields len 3 +time=14:41:58 29-12-2025 level=info msg=1, focus is 1 +time=14:41:58 29-12-2025 level=info msg=field ssl_cert_path, capture keys true +time=14:41:58 29-12-2025 level=info msg=1, focus is 1 +time=14:41:58 29-12-2025 level=info msg=field ssl_cert_path, capture keys true +time=14:41:59 29-12-2025 level=info msg=sections ssl, focus 1, fields len 3 +time=14:41:59 29-12-2025 level=info msg=1, focus is 1 +time=14:41:59 29-12-2025 level=info msg=field ssl_cert_path, capture keys true +time=14:42:00 29-12-2025 level=info msg=1, focus is 1 +time=14:42:00 29-12-2025 level=info msg=field ssl_cert_path, capture keys true +time=14:42:00 29-12-2025 level=info msg=1, focus is 1 +time=14:42:00 29-12-2025 level=info msg=field ssl_cert_path, capture keys true +time=14:42:00 29-12-2025 level=info msg=1, focus is 1 +time=14:42:00 29-12-2025 level=info msg=field ssl_cert_path, capture keys true +time=14:42:00 29-12-2025 level=info msg=1, focus is 1 +time=14:42:00 29-12-2025 level=info msg=field ssl_cert_path, capture keys true +time=14:42:01 29-12-2025 level=info msg=1, focus is 1 +time=14:42:01 29-12-2025 level=info msg=field ssl_cert_path, capture keys true +time=14:42:01 29-12-2025 level=info msg=1, focus is 1 +time=14:42:01 29-12-2025 level=info msg=field ssl_cert_path, capture keys true +time=14:42:01 29-12-2025 level=info msg=1, focus is 1 +time=14:42:01 29-12-2025 level=info msg=field ssl_cert_path, capture keys true +time=14:42:01 29-12-2025 level=info msg=1, focus is 1 +time=14:42:01 29-12-2025 level=info msg=field ssl_cert_path, capture keys true +time=14:42:02 29-12-2025 level=info msg=1, focus is 1 +time=14:42:02 29-12-2025 level=info msg=field ssl_cert_path, capture keys true +time=14:42:02 29-12-2025 level=info msg=sections ssl, focus 1, fields len 3 +time=14:42:03 29-12-2025 level=info msg=1, focus is 1 +time=14:42:03 29-12-2025 level=info msg=field ssl_cert_path, capture keys true +time=14:42:03 29-12-2025 level=info msg=1, focus is 1 +time=14:42:03 29-12-2025 level=info msg=field ssl_cert_path, capture keys true +time=14:42:03 29-12-2025 level=info msg=1, focus is 1 +time=14:42:03 29-12-2025 level=info msg=field ssl_cert_path, capture keys true +time=14:42:03 29-12-2025 level=info msg=1, focus is 1 +time=14:42:03 29-12-2025 level=info msg=field ssl_cert_path, capture keys true +time=14:42:04 29-12-2025 level=info msg=1, focus is 1 +time=14:42:04 29-12-2025 level=info msg=field ssl_cert_path, capture keys true +time=14:42:04 29-12-2025 level=info msg=1, focus is 1 +time=14:42:04 29-12-2025 level=info msg=field ssl_cert_path, capture keys true +time=14:42:04 29-12-2025 level=info msg=1, focus is 1 +time=14:42:04 29-12-2025 level=info msg=field ssl_cert_path, capture keys true +time=14:42:05 29-12-2025 level=info msg=sections ssl, focus 1, fields len 3 +time=14:42:05 29-12-2025 level=info msg=1, focus is 1 +time=14:42:05 29-12-2025 level=info msg=field ssl_cert_path, capture keys true +time=14:42:05 29-12-2025 level=info msg=1, focus is 1 +time=14:42:05 29-12-2025 level=info msg=field ssl_cert_path, capture keys true +time=14:42:05 29-12-2025 level=info msg=1, focus is 1 +time=14:42:05 29-12-2025 level=info msg=field ssl_cert_path, capture keys true +time=14:42:06 29-12-2025 level=info msg=1, focus is 1 +time=14:42:06 29-12-2025 level=info msg=field ssl_cert_path, capture keys true +time=14:42:06 29-12-2025 level=info msg=1, focus is 1 +time=14:42:06 29-12-2025 level=info msg=field ssl_cert_path, capture keys true +time=14:42:06 29-12-2025 level=info msg=1, focus is 1 +time=14:42:06 29-12-2025 level=info msg=field ssl_cert_path, capture keys true +time=14:42:06 29-12-2025 level=info msg=1, focus is 1 +time=14:42:06 29-12-2025 level=info msg=field ssl_cert_path, capture keys true +time=14:42:07 29-12-2025 level=info msg=1, focus is 1 +time=14:42:07 29-12-2025 level=info msg=field ssl_cert_path, capture keys true +time=14:42:07 29-12-2025 level=info msg=1, focus is 1 +time=14:42:07 29-12-2025 level=info msg=field ssl_cert_path, capture keys true +time=14:42:07 29-12-2025 level=info msg=sections ssl, focus 1, fields len 3 +time=14:42:08 29-12-2025 level=info msg=1, focus is 1 +time=14:42:08 29-12-2025 level=info msg=field ssl_cert_path, capture keys true +time=14:42:08 29-12-2025 level=info msg=1, focus is 1 +time=14:42:08 29-12-2025 level=info msg=field ssl_cert_path, capture keys true +time=14:42:08 29-12-2025 level=info msg=1, focus is 1 +time=14:42:08 29-12-2025 level=info msg=field ssl_cert_path, capture keys true +time=14:42:08 29-12-2025 level=info msg=1, focus is 1 +time=14:42:08 29-12-2025 level=info msg=field ssl_cert_path, capture keys true +time=14:42:08 29-12-2025 level=info msg=1, focus is 1 +time=14:42:08 29-12-2025 level=info msg=field ssl_cert_path, capture keys true +time=14:42:08 29-12-2025 level=info msg=1, focus is 1 +time=14:42:08 29-12-2025 level=info msg=field ssl_cert_path, capture keys true +time=14:42:09 29-12-2025 level=info msg=sections ssl, focus 1, fields len 3 +time=14:42:10 29-12-2025 level=info msg=1, focus is 1 +time=14:42:10 29-12-2025 level=info msg=field ssl_cert_path, capture keys true +time=14:42:10 29-12-2025 level=info msg=1, focus is 1 +time=14:42:10 29-12-2025 level=info msg=field ssl_cert_path, capture keys true +time=14:42:10 29-12-2025 level=info msg=1, focus is 1 +time=14:42:10 29-12-2025 level=info msg=field ssl_cert_path, capture keys true +time=14:42:10 29-12-2025 level=info msg=1, focus is 1 +time=14:42:10 29-12-2025 level=info msg=field ssl_cert_path, capture keys true +time=14:42:10 29-12-2025 level=info msg=1, focus is 1 +time=14:42:10 29-12-2025 level=info msg=field ssl_cert_path, capture keys true +time=14:42:10 29-12-2025 level=info msg=1, focus is 1 +time=14:42:10 29-12-2025 level=info msg=field ssl_cert_path, capture keys true +time=14:42:11 29-12-2025 level=info msg=1, focus is 1 +time=14:42:11 29-12-2025 level=info msg=field ssl_cert_path, capture keys true +time=14:42:11 29-12-2025 level=info msg=1, focus is 1 +time=14:42:11 29-12-2025 level=info msg=field ssl_cert_path, capture keys true +time=14:42:11 29-12-2025 level=info msg=1, focus is 1 +time=14:42:11 29-12-2025 level=info msg=field ssl_cert_path, capture keys true +time=14:42:11 29-12-2025 level=info msg=1, focus is 1 +time=14:42:11 29-12-2025 level=info msg=field ssl_cert_path, capture keys true +time=14:42:11 29-12-2025 level=info msg=1, focus is 1 +time=14:42:11 29-12-2025 level=info msg=field ssl_cert_path, capture keys true +time=14:42:11 29-12-2025 level=info msg=1, focus is 1 +time=14:42:11 29-12-2025 level=info msg=field ssl_cert_path, capture keys true +time=14:42:12 29-12-2025 level=info msg=1, focus is 1 +time=14:42:12 29-12-2025 level=info msg=field ssl_cert_path, capture keys true +time=14:42:12 29-12-2025 level=info msg=1, focus is 1 +time=14:42:12 29-12-2025 level=info msg=field ssl_cert_path, capture keys true +time=14:42:12 29-12-2025 level=info msg=1, focus is 1 +time=14:42:12 29-12-2025 level=info msg=field ssl_cert_path, capture keys true +time=14:42:12 29-12-2025 level=info msg=1, focus is 1 +time=14:42:12 29-12-2025 level=info msg=field ssl_cert_path, capture keys true +time=14:42:12 29-12-2025 level=info msg=1, focus is 1 +time=14:42:12 29-12-2025 level=info msg=field ssl_cert_path, capture keys true +time=14:42:16 29-12-2025 level=info msg=modal close msg get +time=15:05:11 29-12-2025 level=info msg=modal close msg get +time=15:05:12 29-12-2025 level=info msg=sections Repko about, focus 0, fields len 0 +time=15:17:37 29-12-2025 level=info msg=modal close msg get +time=15:24:20 29-12-2025 level=info msg=modal close msg get +time=16:26:13 29-12-2025 level=info msg=modal close msg get +time=16:26:13 29-12-2025 level=info msg=sections Repko about, focus 0, fields len 0 +time=16:26:14 29-12-2025 level=info msg=1, focus is 0 +time=16:26:14 29-12-2025 level=info msg=field choose_user, capture keys false +time=16:26:14 29-12-2025 level=info msg=1, focus is 1 +time=16:26:14 29-12-2025 level=info msg=field new_user, capture keys false +time=16:26:58 29-12-2025 level=info msg=modal close msg get +time=16:49:25 29-12-2025 level=info msg=modal close msg get +time=17:01:58 29-12-2025 level=info msg=modal close msg get +time=17:04:53 29-12-2025 level=info msg=sections Repko about, focus 0, fields len 0 +time=17:04:54 29-12-2025 level=info msg=1, focus is 0 +time=17:04:54 29-12-2025 level=info msg=field choose_user, capture keys false +time=17:04:54 29-12-2025 level=info msg=1, focus is 1 +time=17:04:54 29-12-2025 level=info msg=field new_user, capture keys false +time=17:04:54 29-12-2025 level=info msg=sections user, focus 2, fields len 2 +time=17:04:55 29-12-2025 level=info msg=1, focus is 0 +time=17:04:55 29-12-2025 level=info msg=field api_domain, capture keys false +time=17:04:55 29-12-2025 level=info msg=1, focus is 1 +time=17:04:55 29-12-2025 level=info msg=field panel_domain, capture keys false +time=17:04:55 29-12-2025 level=info msg=1, focus is 2 +time=17:04:55 29-12-2025 level=info msg=field cookie_domain, capture keys false +time=17:04:55 29-12-2025 level=info msg=1, focus is 1 +time=17:04:55 29-12-2025 level=info msg=field panel_domain, capture keys false +time=17:04:55 29-12-2025 level=info msg=1, focus is 0 +time=17:04:55 29-12-2025 level=info msg=field api_domain, capture keys false +time=17:04:56 29-12-2025 level=info msg=sections domain, focus 5, fields len 5 +time=17:04:56 29-12-2025 level=info msg=1, focus is 0 +time=17:04:56 29-12-2025 level=info msg=field database_type, capture keys false +time=17:04:57 29-12-2025 level=info msg=sections database, focus 1, fields len 1 +time=17:04:57 29-12-2025 level=info msg=1, focus is 0 +time=17:04:57 29-12-2025 level=info msg=field debian_root, capture keys false +time=17:04:57 29-12-2025 level=info msg=sections paths, focus 1, fields len 1 +time=17:43:36 29-12-2025 level=info msg=modal close msg get +time=17:43:37 29-12-2025 level=info msg=sections Repko about, focus 0, fields len 0 +time=17:43:38 29-12-2025 level=info msg=1, focus is 0 +time=17:43:38 29-12-2025 level=info msg=field choose_user, capture keys false +time=17:43:38 29-12-2025 level=info msg=1, focus is 1 +time=17:43:38 29-12-2025 level=info msg=field new_user, capture keys false +time=17:43:39 29-12-2025 level=info msg=sections user, focus 2, fields len 2 +time=17:43:39 29-12-2025 level=info msg=1, focus is 0 +time=17:43:39 29-12-2025 level=info msg=field api_domain, capture keys false +time=17:43:39 29-12-2025 level=info msg=1, focus is 1 +time=17:43:39 29-12-2025 level=info msg=field panel_domain, capture keys false +time=17:43:39 29-12-2025 level=info msg=1, focus is 2 +time=17:43:39 29-12-2025 level=info msg=field cookie_domain, capture keys false +time=17:43:40 29-12-2025 level=info msg=1, focus is 3 +time=17:43:40 29-12-2025 level=info msg=field is_secure, capture keys false +time=17:43:40 29-12-2025 level=info msg=1, focus is 4 +time=17:43:40 29-12-2025 level=info msg=field server_address, capture keys false +time=17:43:40 29-12-2025 level=info msg=sections domain, focus 5, fields len 5 +time=17:43:40 29-12-2025 level=info msg=1, focus is 0 +time=17:43:40 29-12-2025 level=info msg=field database_type, capture keys false +time=17:43:41 29-12-2025 level=info msg=sections database, focus 1, fields len 1 +time=17:43:41 29-12-2025 level=info msg=1, focus is 0 +time=17:43:41 29-12-2025 level=info msg=field debian_root, capture keys false +time=17:43:41 29-12-2025 level=info msg=sections paths, focus 1, fields len 1 diff --git a/setup/final.go b/setup/final.go index b7622f7..8c582de 100644 --- a/setup/final.go +++ b/setup/final.go @@ -37,9 +37,9 @@ func (m *FinalModel) Init() tea.Cmd { } func (m *FinalModel) initViewport(width, height int) { - m.viewport = viewport.New(width, height-4) - m.viewport.Width = width - 4 - m.viewport.Height = height - 6 + m.viewport = viewport.New(width, height-8) + m.viewport.Width = width + m.viewport.Height = height - 10 m.ready = true m.rebuildTable(m.viewport.Width) m.viewport.SetContent(m.table.Render()) @@ -50,22 +50,22 @@ func (m *FinalModel) rebuildTable(width int) { m.table = table.New().Headers("Нет переменных") return } - availableWidth := width - 8 // примерно: 2 (отступы) + 4 (бордер) + запас - if availableWidth < 60 { - availableWidth = 60 + availableWidth := width - 12 // примерно: 2 (отступы) + 4 (бордер) + запас + if availableWidth < 70 { + availableWidth = 70 } t := table.New(). - Border(lipgloss.ThickBorder()). - BorderStyle(ui.TableBorderStyle). + Border(lipgloss.RoundedBorder()). + BorderStyle(ui.TableBorder). StyleFunc(func(row, col int) lipgloss.Style { var style lipgloss.Style switch { case row == table.HeaderRow: - return ui.TableHeaderStyle + return ui.TableHeader case row%2 == 0: - style = ui.TableEvenRowStyle + style = ui.TableRowEven.Padding(0, 2) default: - style = ui.TableOddRowStyle + style = ui.TableRowOdd.Padding(0, 2) } switch col { case 0, 1: @@ -125,26 +125,76 @@ func (m *FinalModel) View() string { if !m.ready { return "Загрузка таблицы" } - header := lipgloss.NewStyle(). - Width(m.viewport.Width). - Align(lipgloss.Center). - Bold(true). - Background(ui.ColorPurple). - Foreground(ui.ColorWhite). - Padding(1). - Render("Итоговые значения переменных") - footer := lipgloss.NewStyle(). - Width(m.viewport.Width). - Align(lipgloss.Center). - Foreground(ui.ColorGrey). - Padding(0, 1). - Render(fmt.Sprintf("Строк: %d • q — выход • ↑↓ / jk — скролл", len(m.variables))) + contentWidth := ui.ContentWidth(m.width) + m.rebuildTable(contentWidth) + + header := ui.HeaderStyle.Width(contentWidth).Render("Итоговые значения переменных") + footer := ui.FooterStyle.Width(contentWidth).Render(fmt.Sprintf("Строк: %d • q — выход • ↑↓ / jk — скролл", len(m.variables))) + + m.viewport.Width = contentWidth - 8 + m.viewport.Height = m.height - 12 + m.viewport.SetContent(m.table.Render()) body := m.viewport.View() - return lipgloss.JoinVertical(lipgloss.Center, - header, - body, - footer, + + content := lipgloss.JoinVertical(lipgloss.Left, header, body, footer) + container := ui.AppContainer.Width(contentWidth).Render(content) + + return lipgloss.Place(m.width, m.height, + lipgloss.Center, lipgloss.Center, + container, + lipgloss.WithWhitespaceChars("░"), + lipgloss.WithWhitespaceForeground(ui.ColorGrey), ) + + //contentWidth := ui.AppContentWidth(m.width) + //m.viewport.Width = contentWidth - 6 + //m.viewport.Height = m.height - 12 + //m.rebuildTable(contentWidth) + // + //header := ui.AppHeaderStyle. + // Width(contentWidth). + // Render("Итоговые значения переменных") + // + //footer := ui.AppFooterStyle. + // Width(contentWidth). + // Render(fmt.Sprintf("Строк: %d • q — выход • ↑↓ / jk — скролл", len(m.variables))) + // + //body := m.viewport.View() + //content := lipgloss.JoinVertical(lipgloss.Left, header, body, footer) + // + //container := ui.AppContainerStyle. + // Width(contentWidth). + // Render(content) + // + //return lipgloss.Place(m.width, m.height, + // lipgloss.Center, lipgloss.Center, + // container, + // lipgloss.WithWhitespaceChars("░"), + // lipgloss.WithWhitespaceForeground(ui.ColorGrey), + //) + + //header := lipgloss.NewStyle(). + // Width(m.viewport.Width). + // Align(lipgloss.Center). + // Bold(true). + // Background(ui.ColorPurple). + // Foreground(ui.ColorWhite). + // Padding(1). + // Render("Итоговые значения переменных") + // + //footer := lipgloss.NewStyle(). + // Width(m.viewport.Width). + // Align(lipgloss.Center). + // Foreground(ui.ColorGrey). + // Padding(0, 1). + // Render(fmt.Sprintf("Строк: %d • q — выход • ↑↓ / jk — скролл", len(m.variables))) + // + //body := m.viewport.View() + //return lipgloss.JoinVertical(lipgloss.Center, + // header, + // body, + // footer, + //) } diff --git a/setup/form.go b/setup/form.go index 10e9af3..6f4031a 100644 --- a/setup/form.go +++ b/setup/form.go @@ -7,6 +7,8 @@ import ( "fmt" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/glamour" + "github.com/charmbracelet/lipgloss" + "github.com/muesli/reflow/wordwrap" "github.com/sirupsen/logrus" "strings" ) @@ -16,9 +18,11 @@ type FormModel struct { FieldModels []fields.FieldModel focus int renderedContent string + width int + height int } -func NewFormModel(application *types.PackageApplication, sectionIdx, focus int) *FormModel { +func NewFormModel(application *types.PackageApplication, sectionIdx, focus, width, height int) *FormModel { currentSection := &application.Sections[sectionIdx] var fieldModels []fields.FieldModel for _, sectionField := range currentSection.Fields { @@ -69,12 +73,14 @@ func NewFormModel(application *types.PackageApplication, sectionIdx, focus int) FieldModels: fieldModels, focus: focus, CurrentSection: currentSection, + width: width, + height: height, } - if currentSection.Type == "abouta" { //"about" { + if currentSection.Type == types.SectionAbout { md := strings.Join(currentSection.Paragraphs, "\n\n") renderer, _ := glamour.NewTermRenderer( glamour.WithAutoStyle(), - glamour.WithWordWrap(80)) + glamour.WithWordWrap(width-10)) rendered, _ := renderer.Render(md) m.renderedContent = rendered m.focus = 0 @@ -88,23 +94,61 @@ func (m *FormModel) Init() tea.Cmd { func (m *FormModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg := msg.(type) { + case tea.WindowSizeMsg: + m.width = msg.Width + m.height = msg.Height + if m.CurrentSection.Type == types.SectionAbout { + md := strings.Join(m.CurrentSection.Paragraphs, "\n\n") + renderer, _ := glamour.NewTermRenderer( + glamour.WithAutoStyle(), + glamour.WithWordWrap(m.width-10), + ) + rendered, _ := renderer.Render(md) + m.renderedContent = rendered + } + return m, nil case tea.KeyMsg: switch msg.String() { case "ctrl+c", "q": return m, tea.Quit case "esc": + for _, field := range m.FieldModels { + if field.CaptureKeys() { + return m, m.updateFields(msg) + } + } + return m, func() tea.Msg { + return types.ShowModalMsg{ + Type: types.ModalWarning, + Title: "Прервать настройку", + Message: "Вы уверены, что хотите прервать настройку?\nВсе введенные данные будут потеряны.", + ModalButtons: []types.ModalButton{ + {Text: "Да, выйти", Value: true}, + {Text: "Продолжить настройку", Value: false}, + }, + OnResult: func(result any) tea.Cmd { + if result == true { + return func() tea.Msg { return types.BackToListMsg{} } + } + return nil + }, + } + } //вернуться к списку приложений, чтобы выбрать другое приложение. case "enter": logrus.Infof("sections %s, focus %d, fields len %d", m.CurrentSection.Name, m.focus, len(m.FieldModels)) if len(m.FieldModels) == m.focus { + m.focus = 0 return m, func() tea.Msg { return types.NextSectionMsg{} } } case "tab", "down", "shift+tab", "up": + //Итак, тут надо учесть динамические поля. + // key := msg.String() if m.focus != len(m.FieldModels) { - logrus.Infof("1") + logrus.Infof("1, focus is %d", m.focus) //todo исправить фокус field := m.FieldModels[m.focus] logrus.Infof("field %s, capture keys %t", field.GetLabel(), field.CaptureKeys()) @@ -137,30 +181,66 @@ func (m *FormModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } func (m *FormModel) View() string { - res := "" + contentWidth := ui.ContentWidth(m.width) - if len(m.FieldModels) > 0 { - for i := range m.FieldModels { - inputView := m.FieldModels[i].View() - if m.focus == i { - res += ui.FormFocusedStyle.Render(inputView) - } else { - res += ui.FormUnfocusedStyle.Render(inputView) - } - res += "\n" + var res strings.Builder + + for i, field := range m.FieldModels { + fieldStr := wordwrap.String(field.View(), contentWidth-10) + if i == m.focus { + res.WriteString(ui.FormFieldFocused.Render(fieldStr)) + } else { + res.WriteString(ui.FormFieldUnfocused.Render(fieldStr)) } + res.WriteString("\n\n") } - button := "\n[ next section ]" - if m.CurrentSection.Type == "about" { - res += m.renderedContent + if m.CurrentSection.Type == types.SectionAbout { + res.WriteString(m.renderedContent) } + + nextBtn := "[ next section ]" if m.focus == len(m.FieldModels) { - res += ui.FormFocusedStyle.Render(button) + res.WriteString(ui.FormNextButton.Render(nextBtn)) } else { - res += ui.FormUnfocusedStyle.Render(button) + res.WriteString(ui.FormFieldUnfocused.Render(nextBtn)) } - return ui.FormStyle.Render(res) + + container := ui.FormContainer.Width(contentWidth).Render(res.String()) + return lipgloss.Place(m.width, m.height, + lipgloss.Center, lipgloss.Center, + container, + lipgloss.WithWhitespaceChars(" "), + ) + + //for i, field := range m.FieldModels { + // fieldStr := wordwrap.String(field.View(), contentWidth-8) + // if i == m.focus { + // res.WriteString(ui.FormFocusedStyle.Render(fieldStr)) + // } else { + // res.WriteString(ui.FormUnfocusedStyle.Render(fieldStr)) + // } + // res.WriteString("\n\n") + //} + // + //if m.CurrentSection.Type == types.SectionAbout { + // res.WriteString("\n\n" + m.renderedContent) + //} + // + //button := "[ next section ]" + //if m.focus == len(m.FieldModels) { + // res.WriteString(ui.FormFocusedStyle.Render(button)) + //} else { + // res.WriteString(ui.FormUnfocusedStyle.Render(button)) + //} + // + //container := ui.FormStyle.Width(contentWidth).Render(res.String()) + // + //return lipgloss.Place(m.width, m.height, + // lipgloss.Center, lipgloss.Top, + // container, + // lipgloss.WithWhitespaceChars(" "), + //) } func (m *FormModel) updateFields(msg tea.Msg) tea.Cmd { diff --git a/setup/list.go b/setup/list.go index afb7c73..09ffe4b 100644 --- a/setup/list.go +++ b/setup/list.go @@ -3,67 +3,138 @@ package setup import ( "bubble/setup/ui" "bubble/types" + "fmt" + "github.com/charmbracelet/bubbles/list" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" ) -type ListModel struct { - options []string - focus int +type ListApplicationItem struct { + Application types.PackageApplication } -func NewListModel(applications []types.PackageApplication) *ListModel { - options := make([]string, len(applications)) +func (i ListApplicationItem) Title() string { + return fmt.Sprintf("%s, v%s", i.Application.Name, i.Application.Version) +} +func (i ListApplicationItem) Description() string { + return fmt.Sprintf("%s", i.Application.Description) +} + +func (i ListApplicationItem) FilterValue() string { + return i.Application.Name +} + +type ListApplicationsModel struct { + List list.Model + width int + height int +} + +func NewListApplicationsModel(applications []types.PackageApplication, width, height int) *ListApplicationsModel { + + items := make([]list.Item, len(applications)) for i, app := range applications { - options[i] = app.Name + items[i] = ListApplicationItem{app} } - return &ListModel{ - options: options, + + delegate := list.NewDefaultDelegate() + delegate.Styles.SelectedTitle = ui.ListSelectedStyle + delegate.Styles.SelectedDesc = ui.ListSelectedStyle + delegate.ShowDescription = false + l := list.New(items, delegate, width, height) + l.Title = "Выберите приложение для настройки" + l.Styles.Title = ui.ListTitleStyle + + return &ListApplicationsModel{ + List: l, + width: width, + height: height, } } -func (m *ListModel) Init() tea.Cmd { +func (m *ListApplicationsModel) Init() tea.Cmd { return nil } -func (m *ListModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { +func (m *ListApplicationsModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { var cmd tea.Cmd switch msg := msg.(type) { + case tea.WindowSizeMsg: + m.width = msg.Width + m.height = msg.Height + return m, nil case tea.KeyMsg: switch msg.String() { case "ctrl+c", "q": return m, tea.Quit - case "up", "w", "shift+tab": - m.focus-- - if m.focus < 0 { - m.focus = len(m.options) - 1 - } - case "down", "s", "tab": - m.focus++ - if m.focus == len(m.options) { - m.focus = 0 - } case "enter": + selected := m.List.SelectedItem().(ListApplicationItem) return m, func() tea.Msg { - return types.SelectedAppMsg{Index: m.focus} + return types.ShowModalMsg{ + Type: types.ModalConfirm, + Title: "Начать установку", + Message: fmt.Sprintf("Вы хотит настроить приложение:\n\n%s\n\nПродолжить?", selected.Title()), + ModalButtons: []types.ModalButton{ + {Text: "Да", Value: true}, + {Text: "Нет", Value: false}, + }, + OnResult: func(result any) tea.Cmd { + if result == true { + return func() tea.Msg { + return types.SelectedAppMsg{App: selected.Application} + } + } + return nil + }, + } } } } + m.List, cmd = m.List.Update(msg) return m, cmd } -func (m *ListModel) View() string { - res := "" - listStyle := lipgloss.NewStyle().Padding(1, 1).Foreground(ui.ColorPurple) - for i := range m.options { - if i == m.focus { - res += "[x] " + m.options[i] - } else { - res += "[ ] " + m.options[i] - } - if i != len(m.options)-1 { - res += "\n" - } +func (m *ListApplicationsModel) View() string { + contentWidth := ui.ContentWidth(m.width) + listWidth := int(float64(contentWidth) * ui.ListWithRatio) + m.List.SetSize(listWidth-6, m.height-10) + + listView := m.List.View() + + var desc string + if sel := m.List.SelectedItem(); sel != nil { + desc = sel.(ListApplicationItem).Description() } - return listStyle.Render(res) + descView := ui.ListDescriptionStyle.Width(contentWidth - listWidth - 10).Render(desc) + body := lipgloss.JoinHorizontal(lipgloss.Top, listView, " ", descView) + container := ui.ListContainer.Width(contentWidth).Render(body) + + return lipgloss.Place(m.width, m.height, + lipgloss.Center, lipgloss.Center, + container, + lipgloss.WithWhitespaceChars("░"), + lipgloss.WithWhitespaceForeground(ui.ColorGrey), + ) + + //var desc string + //if selected := m.List.SelectedItem(); selected != nil { + // desc = selected.(ListApplicationItem).Description() + //} + //descView := ui.ListDescriptionStyle. + // Width(contentWidth/2 - 4). + // Render(desc) + // + //body := lipgloss.JoinHorizontal(lipgloss.Top, listView, " ", descView) + // + //container := ui.ListStyle. + // Width(contentWidth). + // Render(body) + // + //return lipgloss.Place(m.width, m.height, + // lipgloss.Center, lipgloss.Center, + // container, + // lipgloss.WithWhitespaceChars("░"), + // lipgloss.WithWhitespaceForeground(ui.ColorGrey), + //) + } diff --git a/setup/main.go b/setup/main.go index dc7e8f8..95083b1 100644 --- a/setup/main.go +++ b/setup/main.go @@ -17,34 +17,54 @@ func Start() { type Main struct { current tea.Model + overlay tea.Model Applications []types.PackageApplication CurrentApplication *types.PackageApplication CurrentSection *types.Section CurrentSectionIdx int - width int - height int + Width int + Height int } func NewMain() *Main { apps := bootstrap.LoadApplications() - return &Main{ + m := &Main{ Applications: apps, - current: NewListModel(apps), CurrentSectionIdx: 0, } + m.current = NewListApplicationsModel(m.Applications, m.Width, m.Height) + return m } + func (m *Main) Init() tea.Cmd { return m.current.Init() } func (m *Main) Update(msg tea.Msg) (tea.Model, tea.Cmd) { - var cmd tea.Cmd - m.current, cmd = m.current.Update(msg) + var cmds []tea.Cmd + if m.overlay != nil { + var cmd tea.Cmd + m.overlay, cmd = m.overlay.Update(msg) + if cmd != nil { + cmds = append(cmds, cmd) + } + } else { + var cmd tea.Cmd + m.current, cmd = m.current.Update(msg) + if cmd != nil { + cmds = append(cmds, cmd) + } + } switch msg := msg.(type) { case tea.WindowSizeMsg: - m.width = msg.Width - m.height = msg.Height - m.current, cmd = m.current.Update(msg) + m.Width = msg.Width + m.Height = msg.Height + if m.current != nil { + m.current, _ = m.current.Update(msg) + } + if m.overlay != nil { + m.overlay, _ = m.overlay.Update(msg) + } case tea.KeyMsg: switch msg.String() { case "ctrl+c", "q": @@ -65,21 +85,31 @@ func (m *Main) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } } if m.CurrentSection == nil { - logrus.Infof("next section, eq nil, out, idx %d", m.CurrentSectionIdx) - return m, tea.Quit + return m, func() tea.Msg { + return types.ShowModalMsg{ + Type: types.ModalError, + Title: "Ошибка конфигурации", + Message: "Это приложение не имеет доступных секций для настройки\n" + + "Возможно оно повреждено или не поддерживается", + ModalButtons: []types.ModalButton{{Text: "Выйти", Value: true}}, + OnResult: func(result any) tea.Cmd { + return tea.Quit + }, + } + } } //todo переделать. Учесть динамические индексы. - if m.CurrentSectionIdx == len(m.CurrentApplication.Sections)-1 && m.CurrentSection.Type == "final" { - m.current = NewFinalModel(m.CurrentApplication.Variables, m.width, m.height) + if m.CurrentSectionIdx == len(m.CurrentApplication.Sections)-1 && m.CurrentSection.Type == types.SectionFinal { + m.current = NewFinalModel(m.CurrentApplication.Variables, m.Width, m.Height) } else { - m.current = NewFormModel(m.CurrentApplication, m.CurrentSectionIdx, 0) + m.current = NewFormModel(m.CurrentApplication, m.CurrentSectionIdx, 0, m.Width, m.Height) } case types.SugarMsg: m.Sugar(msg) case types.SelectedAppMsg: //получаем сообщение о выборе приложения - m.CurrentApplication = &m.Applications[msg.Index] + m.CurrentApplication = &msg.App //ищем доступную секцию for sectionIdx := 0; sectionIdx < len(m.CurrentApplication.Sections); sectionIdx++ { @@ -96,13 +126,29 @@ func (m *Main) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return m, tea.Quit } //создаем новую форму - m.current = NewFormModel(m.CurrentApplication, m.CurrentSectionIdx, 0) + m.current = NewFormModel(m.CurrentApplication, m.CurrentSectionIdx, 0, m.Width, m.Height) + case types.BackToListMsg: + m.CurrentApplication = nil + m.CurrentSection = nil + m.CurrentSectionIdx = 0 + m.current = NewListApplicationsModel(m.Applications, m.Width, m.Height) + case types.ShowModalMsg: + m.overlay = NewModalModel(msg, m.Width, m.Height) + return m, nil + case types.CloseModalMsg: + logrus.Infof("modal close msg get") + m.overlay = nil + return m, tea.Batch(cmds...) } - return m, cmd + return m, tea.Batch(cmds...) } func (m *Main) View() string { - return m.current.View() + view := m.current.View() + if m.overlay != nil { + view = m.overlay.View() + } + return view } func (m *Main) Sugar(sugar types.SugarMsg) { @@ -113,7 +159,7 @@ func (m *Main) Sugar(sugar types.SugarMsg) { if sugar.Compute != "" { Recompute(m.CurrentApplication) } - m.current = NewFormModel(m.CurrentApplication, m.CurrentSectionIdx, sugar.Focus) + m.current = NewFormModel(m.CurrentApplication, m.CurrentSectionIdx, sugar.Focus, m.Width, m.Height) } } diff --git a/setup/modal.go b/setup/modal.go new file mode 100644 index 0000000..5065427 --- /dev/null +++ b/setup/modal.go @@ -0,0 +1,185 @@ +package setup + +import ( + "bubble/setup/ui" + "bubble/types" + tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/lipgloss" +) + +type ModalModel struct { + modalType types.ModalType + title string + message string + buttons []types.ModalButton + focus int + width int + height int + onResult func(result any) tea.Cmd +} + +func NewModalModel(msg types.ShowModalMsg, width, height int) *ModalModel { + if len(msg.ModalButtons) == 0 { + msg.ModalButtons = []types.ModalButton{{Text: "Ok", Value: true}} + } + return &ModalModel{ + modalType: msg.Type, + title: msg.Title, + message: msg.Message, + buttons: msg.ModalButtons, + focus: 0, + width: width, + height: height, + onResult: msg.OnResult, + } +} + +func (m *ModalModel) Init() tea.Cmd { + return nil +} + +func (m *ModalModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + switch msg := msg.(type) { + case tea.WindowSizeMsg: + m.width = msg.Width + m.height = msg.Height + return m, nil + case tea.KeyMsg: + switch msg.String() { + case "left", "h", "shift+tab": + m.focus-- + if m.focus < 0 { + m.focus = len(m.buttons) - 1 + } + case "right", "l", "tab": + m.focus++ + if m.focus >= len(m.buttons) { + m.focus = 0 + } + case "enter", " ": + //вытаскиваем значение кнопки + result := m.buttons[m.focus].Value + var cmd tea.Cmd + if m.onResult != nil { //если функция кнопки не пустая, мы передаем в функцию значение кнопки + cmd = m.onResult(result) + } + return m, tea.Batch( //объединяем 2 действия, функцию кнопки и закрытие модального окна + cmd, + func() tea.Msg { return types.CloseModalMsg{} }, + ) + } + } + return m, nil +} + +func (m *ModalModel) View() string { + contentWidth := ui.ContentWidth(m.width) + modalWidth := min(contentWidth-10, ui.ModalMaxHeight) + if modalWidth < 60 { + modalWidth = 60 + } + + content := lipgloss.NewStyle(). + Width(modalWidth-8). + Padding(1, 3). + Render(m.message) + + var buttons []string + + for i, btn := range m.buttons { + style := ui.ModalButtonNormal + if i == m.focus { + style = ui.ModalButtonFocused + } + buttons = append(buttons, style.Render(btn.Text)) + } + buttonRow := lipgloss.JoinHorizontal(lipgloss.Center, buttons...) + + body := lipgloss.JoinVertical(lipgloss.Center, content, "\n\n", buttonRow) + + // Бордер и цвет в зависимости от типа + var modalStyle lipgloss.Style + var titleStyle lipgloss.Style + switch m.modalType { + case types.ModalError: + modalStyle = ui.ModalBorderError + titleStyle = ui.ModalTitleError + case types.ModalWarning, types.ModalConfirm: + modalStyle = ui.ModalBorderWarning + titleStyle = ui.ModalTitleWarning + default: + modalStyle = ui.ModalBorderInfo + titleStyle = ui.ModalTitleDefault + } + + modal := lipgloss.NewStyle(). + Width(modalWidth). + Padding(2, 4). + Border(lipgloss.DoubleBorder()). + BorderForeground(modalStyle.GetBorderBottomForeground()). + Render(body) + + if m.title != "" { + title := titleStyle.Width(modalWidth).Align(lipgloss.Center).Render(m.title) + modal = lipgloss.JoinVertical(lipgloss.Center, title, modal) + } + return lipgloss.Place(m.width, m.height, + lipgloss.Center, lipgloss.Center, + modal, + lipgloss.WithWhitespaceChars("."), + lipgloss.WithWhitespaceForeground(ui.ColorGrey)) + + //modalWidth := min(m.width-20, ui.ModalMaxHeight) + //if modalWidth < 60 { + // modalWidth = 60 + //} + //var borderColor lipgloss.Color + //var titleStyle lipgloss.Style + // + //switch m.modalType { + //case types.ModalError: + // borderColor = ui.ColorRed + // titleStyle = lipgloss.NewStyle().Foreground(ui.ColorRed).Bold(true) + //case types.ModalWarning, types.ModalConfirm: + // borderColor = ui.ColorPurple + // titleStyle = lipgloss.NewStyle().Foreground(ui.ColorPurple).Bold(true) + //default: + // borderColor = ui.ColorBlue + // titleStyle = lipgloss.NewStyle().Foreground(ui.ColorBlue).Bold(true) + //} + //content := lipgloss.NewStyle().Width(m.width-10).Padding(1, 2).Render(m.message) + //var buttons []string + //for i, btn := range m.buttons { + // style := lipgloss.NewStyle(). + // Padding(0, 2). + // Border(lipgloss.RoundedBorder()). + // BorderForeground(ui.ColorGrey) + // if i == m.focus { + // style = style.Foreground(ui.ColorRed).BorderForeground(ui.ColorRed).Bold(true) + // } else { + // style = style.Foreground(ui.ColorGreenCyan) + // } + // buttons = append(buttons, style.Render(btn.Text)) + //} + //buttonShow := lipgloss.JoinHorizontal(lipgloss.Center, buttons...) + //body := lipgloss.JoinVertical(lipgloss.Center, content, "\n", buttonShow) + // + //modal := lipgloss.NewStyle(). + // Width(modalWidth). + // Padding(1, 3). + // Border(lipgloss.DoubleBorder()). + // BorderForeground(borderColor). + // Render(body) + // + //if m.title != "" { + // title := titleStyle.Width(modalWidth - 6).Render(m.title) + // modal = lipgloss.JoinVertical(lipgloss.Center, title, modal) + //} + // + //return lipgloss.Place(m.width, m.height, + // lipgloss.Center, lipgloss.Center, + // modal, + // lipgloss.WithWhitespaceChars("."), + // lipgloss.WithWhitespaceForeground(ui.ColorPurple), + //) +} diff --git a/setup/ui/style.go b/setup/ui/style.go index 671fa06..b8efb98 100644 --- a/setup/ui/style.go +++ b/setup/ui/style.go @@ -2,35 +2,164 @@ package ui import "github.com/charmbracelet/lipgloss" +const ( + AppMaxWidth = 110 + AppMinWidth = 80 + AppSidePadding = 4 + ModalMaxWidth = 80 + ModalMaxHeight = 30 + ListWithRatio = 0.4 +) + +func AppContentWidth(screenWidth int) int { + w := screenWidth - 2*AppSidePadding + if w > AppMaxWidth { + return AppMaxWidth + } + if w < AppMinWidth { + return AppMinWidth + } + return w +} + var ( - ColorWhite = lipgloss.Color("#ffffff") - ColorGrey = lipgloss.Color("#626262") - ColorBlack = lipgloss.Color("#000000") - ColorRed = lipgloss.Color("#FF0000") - ColorGreen = lipgloss.Color("#00FF00") - ColorBlue = lipgloss.Color("#0000FF") - ColorPurple = lipgloss.Color("#6f03fc") - ColorGreenCyan = lipgloss.Color("#11CAAA") + ColorFg = lipgloss.Color("#eeeeee") + ColorBg = lipgloss.Color("#0d1117") + ColorAccent = lipgloss.Color("#a6e3a1") + ColorAccent2 = lipgloss.Color("#747cec") + ColorWarning = lipgloss.Color("#f9e2af") + ColorError = lipgloss.Color("#f38ba8") + ColorPurple = lipgloss.Color("#cba6f7") + ColorGrey = lipgloss.Color("#585b70") + ColorDarkGrey = lipgloss.Color("#313244") - TableHeaderStyle = lipgloss.NewStyle().Foreground(ColorPurple).Bold(true).Align(lipgloss.Center) - TableCellStyle = lipgloss.NewStyle().Padding(0, 1).Width(14) - TableOddRowStyle = TableCellStyle.Foreground(ColorWhite).Background(ColorGrey) - TableEvenRowStyle = TableCellStyle.Foreground(ColorWhite) - TableBorderStyle = lipgloss.NewStyle().Foreground(ColorPurple) + AppContainer = lipgloss.NewStyle(). + Padding(2, 4). + Border(lipgloss.RoundedBorder()). + BorderForeground(ColorPurple) + HeaderStyle = lipgloss.NewStyle(). + Bold(true). + Foreground(ColorPurple). + Background(ColorDarkGrey). + Padding(1, 2). + MarginBottom(1). + Align(lipgloss.Center) + + FooterStyle = lipgloss.NewStyle(). + Foreground(ColorGrey). + Padding(0, 2). + MarginTop(1). + Align(lipgloss.Center) + + ListContainer = lipgloss.NewStyle(). + Padding(1, 3). + Border(lipgloss.RoundedBorder()). + BorderForeground(ColorAccent) + + ListTitleStyle = lipgloss.NewStyle().Foreground(ColorPurple).Italic(true).Bold(true) + ListSelectedStyle = lipgloss.NewStyle().Foreground(ColorError).Bold(true) + ListDescriptionStyle = lipgloss.NewStyle().Foreground(ColorAccent2).MarginTop(1) + + ModalBorderError = lipgloss.NewStyle().BorderForeground(ColorError) + ModalBorderWarning = lipgloss.NewStyle().BorderForeground(ColorWarning) + ModalBorderConfirm = lipgloss.NewStyle().BorderForeground(ColorPurple) + ModalBorderInfo = lipgloss.NewStyle().BorderForeground(ColorAccent) + + ModalTitleError = lipgloss.NewStyle().BorderForeground(ColorError).Bold(true) + ModalTitleWarning = lipgloss.NewStyle().BorderForeground(ColorWarning).Bold(true) + ModalTitleDefault = lipgloss.NewStyle().BorderForeground(ColorPurple).Bold(true) + + ModalButtonNormal = lipgloss.NewStyle(). + Padding(0, 3). + Border(lipgloss.RoundedBorder()). + BorderForeground(ColorGrey). + Foreground(ColorAccent2) + + ModalButtonFocused = lipgloss.NewStyle(). + Padding(0, 3). + Border(lipgloss.RoundedBorder()). + BorderForeground(ColorError). + Foreground(ColorError). + Bold(true) + + FormContainer = lipgloss.NewStyle(). + Padding(2, 4). + Border(lipgloss.RoundedBorder()). + BorderForeground(ColorAccent) + + FormFieldFocused = lipgloss.NewStyle().Foreground(ColorError).Bold(true) + FormFieldUnfocused = lipgloss.NewStyle().Foreground(ColorAccent2) + FormNextButton = lipgloss.NewStyle().Foreground(ColorPurple).Bold(true) + + TableBorder = lipgloss.NewStyle().Foreground(ColorPurple) + TableHeader = lipgloss.NewStyle().Foreground(ColorPurple).Bold(true).Align(lipgloss.Center) + TableRowEven = lipgloss.NewStyle().Foreground(ColorFg).Background(ColorDarkGrey) + TableRowOdd = lipgloss.NewStyle().Foreground(ColorFg) + TableCell = lipgloss.NewStyle().Padding(0, 2) + + //ColorWhite = lipgloss.Color("#ffffff") + //ColorGrey = lipgloss.Color("#626262") + //ColorBlack = lipgloss.Color("#000000") + //ColorRed = lipgloss.Color("#FF0000") + //ColorGreen = lipgloss.Color("#00FF00") + //ColorBlue = lipgloss.Color("#0000FF") + //ColorPurple = lipgloss.Color("#6f03fc") + //ColorGreenCyan = lipgloss.Color("#11CAAA") + // + //AppContainerStyle = lipgloss.NewStyle(). + // Padding(1, 3). + // Border(lipgloss.RoundedBorder()). + // BorderForeground(ColorPurple) + // + //AppHeaderStyle = lipgloss.NewStyle(). + // Bold(true). + // Foreground(ColorPurple). + // Align(lipgloss.Center). + // Padding(1, 0). + // Background(ColorWhite). + // Foreground(ColorBlack) + // + //AppFooterStyle = lipgloss.NewStyle(). + // Foreground(ColorGrey). + // Align(lipgloss.Center). + // Padding(0, 1) + // + //TableHeaderStyle = lipgloss.NewStyle().Foreground(ColorPurple).Bold(true).Align(lipgloss.Center) + //TableCellStyle = lipgloss.NewStyle().Padding(0, 1).Width(14) + //TableOddRowStyle = TableCellStyle.Foreground(ColorWhite).Background(ColorGrey) + //TableEvenRowStyle = TableCellStyle.Foreground(ColorWhite) + //TableBorderStyle = lipgloss.NewStyle().Foreground(ColorPurple) + // SelectFileDirDeniedStyle = lipgloss.NewStyle().Foreground(ColorGrey) SelectFileDirParentStyle = lipgloss.NewStyle().Foreground(ColorPurple) - SelectFileStyle = lipgloss.NewStyle().Foreground(ColorGreenCyan) - SelectDirStyle = lipgloss.NewStyle().Foreground(ColorBlue) - - SelectStyle = lipgloss.NewStyle().Foreground(ColorGreenCyan) - InputStyle = lipgloss.NewStyle().Foreground(ColorGreenCyan) - ComputeStyle = lipgloss.NewStyle().Foreground(ColorGrey) - CheckboxStyle = lipgloss.NewStyle().Foreground(ColorGreenCyan) - - FormFocusedStyle = lipgloss.NewStyle().Foreground(ColorRed).Bold(true). - Border(lipgloss.NormalBorder(), false, false, false, true).BorderForeground(ColorRed) - FormUnfocusedStyle = lipgloss.NewStyle().Foreground(ColorGreenCyan) - FormStyle = lipgloss.NewStyle().Padding(1, 2).Foreground(ColorBlack). - Border(lipgloss.RoundedBorder()).BorderForeground(ColorRed) + SelectFileStyle = lipgloss.NewStyle().Foreground(ColorAccent) + SelectDirStyle = lipgloss.NewStyle().Foreground(ColorAccent2) + // + //SelectStyle = lipgloss.NewStyle().Foreground(ColorGreenCyan) + //InputStyle = lipgloss.NewStyle().Foreground(ColorGreenCyan) + //ComputeStyle = lipgloss.NewStyle().Foreground(ColorGrey) + //CheckboxStyle = lipgloss.NewStyle().Foreground(ColorGreenCyan) + // + //FormFocusedStyle = lipgloss.NewStyle().Foreground(ColorRed).Bold(true). + // Border(lipgloss.NormalBorder(), false, false, false, true).BorderForeground(ColorRed) + //FormUnfocusedStyle = lipgloss.NewStyle().Foreground(ColorGreenCyan) + //FormStyle = lipgloss.NewStyle().Padding(1, 2).Foreground(ColorBlack). + // Border(lipgloss.RoundedBorder()).BorderForeground(ColorRed) + // + //ListStyle = lipgloss.NewStyle().Padding(1, 3).Border(lipgloss.RoundedBorder()).BorderForeground(ColorRed) + //ListSelectedItemStyle = lipgloss.NewStyle().Foreground(ColorRed).Bold(true) + //ListTitleStyle = lipgloss.NewStyle().Foreground(ColorPurple).Italic(true) + //ListDescriptionStyle = lipgloss.NewStyle().Foreground(ColorGreenCyan).MarginTop(1) ) + +func ContentWidth(screenWidth int) int { + w := screenWidth - 2*AppSidePadding + if w > AppMaxWidth { + return AppMaxWidth + } + if w < AppMinWidth { + return AppMinWidth + } + return w +} diff --git a/types/modal.go b/types/modal.go new file mode 100644 index 0000000..0780c68 --- /dev/null +++ b/types/modal.go @@ -0,0 +1,10 @@ +package types + +type ModalType string + +const ( + ModalInfo ModalType = "info" + ModalWarning ModalType = "warning" + ModalError ModalType = "error" + ModalConfirm ModalType = "confirm" +) diff --git a/types/msg.go b/types/msg.go index fc1e7c8..5918059 100644 --- a/types/msg.go +++ b/types/msg.go @@ -1,9 +1,13 @@ package types +import tea "github.com/charmbracelet/bubbletea" + type SelectedAppMsg struct { - Index int + App PackageApplication } +type BackToListMsg struct{} + type SelectedOptionMsg struct { FieldIndex int Value string @@ -27,3 +31,18 @@ type SelectedFileMsg struct { Filename string Index int } + +type ShowModalMsg struct { + Type ModalType + Title string + Message string + ModalButtons []ModalButton + OnResult func(result any) tea.Cmd +} + +type ModalButton struct { + Text string + Value any +} + +type CloseModalMsg struct{} diff --git a/types/names.go b/types/names.go new file mode 100644 index 0000000..c091c5c --- /dev/null +++ b/types/names.go @@ -0,0 +1,4 @@ +package types + +const SectionAbout = "abouta" +const SectionFinal = "final"