diff --git a/log.txt b/log.txt deleted file mode 100644 index 12d6817..0000000 --- a/log.txt +++ /dev/null @@ -1,436 +0,0 @@ -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/condition.go b/setup/condition.go index c37dc14..b12b8b2 100644 --- a/setup/condition.go +++ b/setup/condition.go @@ -155,6 +155,7 @@ func ComputeValue(computeExpr string, app *types.PackageApplication) (interface{ return result, nil } +// Recompute производит перерасчет всех полей func Recompute(app *types.PackageApplication) { for i := range app.Sections { section := &app.Sections[i] @@ -162,7 +163,8 @@ func Recompute(app *types.PackageApplication) { field := §ion.Fields[j] if field.FieldType == "compute" && field.Compute != "" { if field.Variable != nil { - continue //?почему мы пропускаем + // todo почему мы пропускаем? + continue } value, err := ComputeValue(field.Compute, app) if err != nil { @@ -174,8 +176,10 @@ func Recompute(app *types.PackageApplication) { } } +// DefineExprCustomFunctions возвращает собственные функции expr func DefineExprCustomFunctions() []expr.Option { return []expr.Option{ + //вычисляет значение по заданному регулярному выражению expr.Function("regex", func(args ...interface{}) (interface{}, error) { if len(args) != 3 { @@ -191,6 +195,7 @@ func DefineExprCustomFunctions() []expr.Option { return re.ReplaceAllString(input, repl), nil }, new(func(string, string, string) string), ), + //проверяет совпадение переданного значения и указанного регулярного выражения expr.Function("regex_match", func(args ...interface{}) (interface{}, error) { if len(args) != 2 { @@ -207,6 +212,7 @@ func DefineExprCustomFunctions() []expr.Option { }, new(func(string, string) bool), ), + //возвращает собственный текст ошибки expr.Function("error", func(args ...interface{}) (interface{}, error) { if len(args) == 0 { @@ -216,6 +222,7 @@ func DefineExprCustomFunctions() []expr.Option { }, new(func(string) error), ), + //возвращает тело ответа по указанному адресу expr.Function("http_get", func(args ...interface{}) (interface{}, error) { if len(args) == 0 { @@ -232,6 +239,7 @@ func DefineExprCustomFunctions() []expr.Option { }, new(func(string) string), ), + //возвращает результат выполнения команды из оболочки expr.Function("shell", func(args ...interface{}) (interface{}, error) { if len(args) == 0 { @@ -250,6 +258,7 @@ func DefineExprCustomFunctions() []expr.Option { }, new(func(string) error), ), + //возвращает значение указанной переменной из env expr.Function("env", func(args ...interface{}) (interface{}, error) { if len(args) == 0 { @@ -258,6 +267,7 @@ func DefineExprCustomFunctions() []expr.Option { return os.Getenv(fmt.Sprintf("%v", args[0])), nil }, new(func(string) error), ), + //проверяет наличие файла в системе expr.Function("file_exists", func(args ...interface{}) (interface{}, error) { if len(args) == 0 { @@ -268,6 +278,7 @@ func DefineExprCustomFunctions() []expr.Option { return err == nil, nil }, new(func(string) bool), ), + //проверяет наличие директории в системе expr.Function("dir_exists", func(args ...interface{}) (interface{}, error) { if len(args) == 0 { @@ -284,6 +295,7 @@ func DefineExprCustomFunctions() []expr.Option { return false, nil }, new(func(string) bool), ), + //проверяет доступность порта expr.Function("port_free", func(args ...interface{}) (interface{}, error) { if len(args) == 0 { @@ -294,6 +306,7 @@ func DefineExprCustomFunctions() []expr.Option { return cmd.Run() != nil, nil }, new(func(string) error), ), + //проверяет наличие пользователя в системе expr.Function("user_exists", func(args ...interface{}) (interface{}, error) { if len(args) == 0 { @@ -307,6 +320,7 @@ func DefineExprCustomFunctions() []expr.Option { } } +// SpecialValueHandle получаем необычные значения полей func SpecialValueHandle(special string) interface{} { specialValue := strings.Split(special, "code:")[1] switch specialValue { diff --git a/setup/final.go b/setup/final.go index ea9334d..455bc3f 100644 --- a/setup/final.go +++ b/setup/final.go @@ -10,21 +10,21 @@ import ( "github.com/charmbracelet/lipgloss/table" ) +// FinalModel структур описывающая модель отрисовки таблицы переменных type FinalModel struct { - variables []types.Variable - table *table.Table - viewport viewport.Model - ready bool - width int - height int + variables []types.Variable //Список переменных + table *table.Table //Таблица для вывода + viewport viewport.Model //Объект куда помещена таблица для возможности прокручивать её + width int //Ширина окна + height int //Высота окна } +// NewFinalModel создается новую модель таблицы переменных func NewFinalModel(variables []types.Variable, width, height int) *FinalModel { m := &FinalModel{ variables: variables, width: width, height: height, - ready: false, } if width > 0 && height > 0 { m.initViewport(width, height) @@ -36,60 +36,70 @@ func (m *FinalModel) Init() tea.Cmd { return nil } +// Update отслеживает действия со таблицей func (m *FinalModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { var cmd tea.Cmd switch msg := msg.(type) { case tea.KeyMsg: + //Отслеживаем нажатие клавиш switch msg.String() { case "up": + //Обработчик up для прокручивания вверх на 1 позицию m.viewport.ScrollUp(1) case "down": + //Обработчик down для прокручивания вниз на 1 позицию m.viewport.ScrollDown(1) case "left": + //Обработчик left для прокручивания влево на 1 позицию m.viewport.ScrollLeft(1) case "right": + //Обработчик right для прокручивания вправо на 1 позицию m.viewport.ScrollRight(1) case "pageup": + //Обработчик pageup для прокручивания вверх на половину допустимой высоты viewport m.viewport.HalfPageUp() case "pagedown": + //Обработчик pagedown для прокручивания вниз на половину допустимой высоты viewport m.viewport.HalfPageDown() case "home": + //Обработчик home для перехода в самый вверх m.viewport.GotoTop() case "end": + //Обработчик end для перехода в самый низ m.viewport.GotoBottom() } case tea.WindowSizeMsg: + //Отслеживаем и записываем новые размер окна, вносим изменения в размеры viewport m.height = msg.Height m.width = msg.Width m.initViewport(msg.Width, msg.Height) } + //Если сообщение было иным, передаем его напрямую в viewport + //todo проверить есть ли эти функции уже у viewport.Update, возможно мой код дублирует m.viewport, cmd = m.viewport.Update(msg) return m, cmd } +// View формирует строку для вывода таблицы переменных func (m *FinalModel) View() string { - if !m.ready { - return "Загрузка таблицы" - } + //Получаем размеры контейнера и ширину контента containerWidth, containerHeight, contentWidth, _ := ui.ContentSizes(m.width, m.height) - m.rebuildTable(contentWidth) - + //Формируем шапку с заголовком header := ui.FinalHeader. Width(contentWidth). Render("Итоговые значения переменных") + //Формируем "подвал" footer := ui.FinalFooter. Width(contentWidth). - Render(fmt.Sprintf("Строк: %d • q — выход • ↑↓ / jk — скролл", len(m.variables))) + Render(fmt.Sprintf("Количество переменных: %d", len(m.variables))) - renderedTable := m.table.Render() - content := lipgloss.NewStyle(). - Align(lipgloss.Center). - Render(renderedTable) - m.viewport.SetContent(content) - body := m.viewport.View() - fullContent := lipgloss.JoinVertical(lipgloss.Top, header, body, footer) + //Обновляем отображение таблицы + m.rebuildTable(contentWidth) + //Формируем единый контент из шапки, видимой области и подвала + fullContent := lipgloss.JoinVertical(lipgloss.Top, header, m.viewport.View(), footer) + //Формируем главный контейнер container := ui.AppContainer. Width(containerWidth). Height(containerHeight). @@ -103,6 +113,7 @@ func (m *FinalModel) View() string { ) } +// initViewport создание видимой области таблицы func (m *FinalModel) initViewport(width, height int) { _, _, contentWidth, contentHeight := ui.ContentSizes(width, height) @@ -110,15 +121,15 @@ func (m *FinalModel) initViewport(width, height int) { tableWidth := contentWidth - 2*1 tableHeight := int(float64(contentHeight)*ui.TableWithRatio) - 2*1 + //Создание видимой области, куда будет помещена таблица m.viewport = viewport.New(contentWidth, tableHeight) - //размеры для viewport в нем будет находиться таблица и перемещаться в зависимости от команд - m.ready = true + //Обновление таблицы m.rebuildTable(tableWidth) - m.viewport.SetContent(m.table.Render()) } func (m *FinalModel) rebuildTable(width int) { + //Если переменных нет, то делаем спец таблицу if len(m.variables) == 0 { m.table = table.New(). Border(lipgloss.RoundedBorder()). @@ -126,9 +137,11 @@ func (m *FinalModel) rebuildTable(width int) { StyleFunc(func(row, col int) lipgloss.Style { return ui.FinalTableHeader }).Headers("Нет переменных") + m.viewport.SetContent(m.table.Render()) return } - variableNameColWidth := 15 + //Вычисляем размеры колонок + variableNameColWidth := 20 defaultValueColWidth := 25 usedValueColWidth := width - variableNameColWidth - defaultValueColWidth - 4*1 //ширина таблицы - ширина первой колонки - ширина второй колонки - 4 границы (2 крайние, 2 между колонками) @@ -136,6 +149,7 @@ func (m *FinalModel) rebuildTable(width int) { usedValueColWidth = 30 } + //Создаем модель таблицы t := table.New(). Width(variableNameColWidth+defaultValueColWidth+usedValueColWidth+4). Border(lipgloss.RoundedBorder()). @@ -162,6 +176,7 @@ func (m *FinalModel) rebuildTable(width int) { }). Headers("Variable name", "Default value", "Used value") + //Заполняем таблицу данными for _, v := range m.variables { used := fmt.Sprintf("%v", v.Value) if used == "" { @@ -169,5 +184,9 @@ func (m *FinalModel) rebuildTable(width int) { } t.Row(v.Name, v.Default, used) } + //записываем таблицу в модель m.table = t + + //Помещение в видимую область таблицы + m.viewport.SetContent(m.table.Render()) } diff --git a/setup/form.go b/setup/form.go index ea345fc..049d9ff 100644 --- a/setup/form.go +++ b/setup/form.go @@ -10,20 +10,23 @@ import ( "github.com/muesli/reflow/wordwrap" "github.com/sirupsen/logrus" "strings" - "unicode/utf8" ) +// FormModel структура описывающая модель отрисовки формы настройки type FormModel struct { - CurrentSection *types.Section - FieldModels []fields.FieldModel - focus int - renderedContent string - width int - height int + CurrentSection *types.Section //Указатель секции с которой надо работать + FieldModels []fields.FieldModel //Массив моделей полей + focus int //Фокус для отслеживания с каким объектом надо работать + renderedContent string //Строка хранения информации о приложении + width int //Ширина окна + height int //Высота окна } +// NewFormModel создает новую модель формы на основе данных настраиваемого приложения func NewFormModel(application *types.PackageApplication, sectionIdx, focus, width, height int) *FormModel { + //Получаем секцию которую надо отрисовать currentSection := &application.Sections[sectionIdx] + //Заполняем массив моделей полей с учетом условий var fieldModels []fields.FieldModel for _, sectionField := range currentSection.Fields { if !CheckCondition(sectionField.Condition, application) { @@ -66,6 +69,7 @@ func NewFormModel(application *types.PackageApplication, sectionIdx, focus, widt fieldModels = append(fieldModels, fields.NewInputModel(sectionField.Name, fmt.Sprintf("%v", sectionField.Variable.Value))) } } + //Устанавливаем фокус (фокус может быть не равен нулю, если новая форма сформирована после Sugar события, который обновляет форму в ТЕКУЩЕМ состоянии) if len(fieldModels) > focus { fieldModels[focus].Focus() } @@ -76,6 +80,7 @@ func NewFormModel(application *types.PackageApplication, sectionIdx, focus, widt width: width, height: height, } + //Формируем описание приложения если секция установлена About if currentSection.Type == types.SectionAbout { m.renderedContent = simpleAboutRender(currentSection.Paragraphs, width) m.focus = 0 @@ -87,10 +92,12 @@ func (m *FormModel) Init() tea.Cmd { return nil } +// Update отслеживает действия с формой func (m *FormModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg := msg.(type) { case tea.WindowSizeMsg: + //Отслеживаем и записываем новые размеры окна, обновляем рендер about m.width = msg.Width m.height = msg.Height if m.CurrentSection.Type == types.SectionAbout { @@ -98,13 +105,22 @@ func (m *FormModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } return m, nil case tea.KeyMsg: + //Отслеживаем нажатие клавиш формы switch msg.String() { case "esc": + //Обработчик клавиши escape на форме нужен для закрытия полей поддерживающих открытие. + //Обходим все доступные в текущей форме поля и закрываем те, что были открыты. + //Проверять на фокус не нужно, так как не предполагается, что у нас может быть открыто сразу несколько полей. + //Поэтому просто возвращаем команду по возврату сообщение об обновлении полей. for _, field := range m.FieldModels { + //проверяем открыто ли поле if field.CaptureKeys() { + //обновляем поля если оно открыто return m, m.updateFields(msg) } } + //Если открытых полей не было найдено, значит escape была нажата для закрытия настройки, + //возвращаем команду по возврату сообщения об открытии модального окна return m, func() tea.Msg { return types.ShowModalMsg{ Type: types.ModalWarning, @@ -123,23 +139,28 @@ func (m *FormModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } } case "enter": - logrus.Infof("sections %s, focus %d, fields len %d", m.CurrentSection.Name, m.focus, len(m.FieldModels)) + //Обработчик клавиши enter на форме нужен для перехода на следующую секцию. + //Проверяем где сейчас находится фокус, если его позиция равняется количеству полей, + //значит он вышел за пределы формы и находится на кнопке next section, что ставится последней. + //После этого мы сбрасываем фокус и возвращаем команду по возврату сообщения о переходе на следующую секцию if len(m.FieldModels) == m.focus { m.focus = 0 return m, func() tea.Msg { return types.NextSectionMsg{} } } - case "tab", "down", "shift+tab", "up": - //Итак, тут надо учесть динамические поля. - // + case "tab", "shift+tab", "down", "up": + //Обработчик клавиш tab, shift+tab, down, up на форме нужен для перехода между объектами формы. + //Получаем точное имя клавиши нажатия, после проверяем где сейчас находится фокус. + //Если фокус не равен количеству полей, то значит фокус сейчас у поля формы. + //Получаем имя поля (так как это старый фокус, то и поле на котором мы находимся это предыдущее поле, отсюда и название переменной), + //Если поле не открыто, то мы можем перейти на следующее поле, увеличив или уменьшив фокус. + //Если фокус равен количеству полей, значит он находится на кнопке переключения секций. Увеличиваем или уменьшаем фокус. key := msg.String() if m.focus != len(m.FieldModels) { - 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()) - if !field.CaptureKeys() { + prevField := m.FieldModels[m.focus] + if !prevField.CaptureKeys() { if key == "tab" || key == "down" { m.focus++ } else if key == "shift+tab" || key == "up" { @@ -147,13 +168,17 @@ func (m *FormModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } } } else { - logrus.Infof("2") if key == "tab" || key == "down" { m.focus++ } else if key == "shift+tab" || key == "up" { m.focus-- } } + //Проверка правильности фокуса, чтобы не выйти за границы массива полей формы, необходимо проверить новое значение фокуса. + //Если фокус стал больше чем количество полей формы, значит он вышел за пределы всей формы и находится уже даже ниже кнопки, + //переводим его на первую позицию, то есть на 0. + //Если фокус стал меньше 0, значит он вышел за пределы формы и находится выше первого поля формы, + //переводим его на последнюю позицию, то есть на кнопку переключения секции, равную положение которой = количество полей на форме. if m.focus == len(m.FieldModels)+1 { m.focus = 0 } else if m.focus < 0 { @@ -161,30 +186,39 @@ func (m *FormModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } } case types.SelectedOptionMsg: + //Отслеживаем выбор опции в поле типа select + //todo тут вообще логика нужна? Мы ведь в select, через m.chosen записываем значение, а после GetValue метод возвращаем m.chosen logrus.Infof("chose %+v", msg) } + //Если не было возвращено сообщений, значит действие было произведено с полем, а не с самой формой. + //Возвращаем результат функции обновления полей. return m, m.updateFields(msg) } +// View формирует строку для вывода формы и её контента. func (m *FormModel) View() string { - + //Данные о размерах контейнера и ширины контента внутри containerWidth, containerHeight, contentWidth, _ := ui.ContentSizes(m.width, m.height) - formWidth := contentWidth - 2*1 - var strokes []string + var strokes []string //отображаемые строки for i, field := range m.FieldModels { + //Строка с учетом переноса поля. + //Имеется ввиду, что если вводимое значение слишком длинное, то оно будет делиться на несколько строк. fieldStroke := wordwrap.String(field.View(), formWidth) + //Если фокус равен текущему полю, то выделяем его. if i == m.focus { strokes = append(strokes, ui.FormFieldFocused.Render(fieldStroke)) } else { strokes = append(strokes, ui.FormFieldUnfocused.Render(fieldStroke)) } } - + //Если текущая секция это секция About, то выводим её информацию. + //Не предполагается, что информация о приложении и поля для настройки будут в одной секции, поэтому тут идет обнуление прошлых строк. if m.CurrentSection.Type == types.SectionAbout { strokes = append([]string{}, m.renderedContent) } - + //Строка кнопки переключения секций. + //Если позиция фокуса равняется количеству полей, значит он сейчас находится на кнопке. nextBtn := "[ next section ]" if m.focus == len(m.FieldModels) { strokes = append(strokes, ui.FormNextButton.Render(nextBtn)) @@ -192,6 +226,8 @@ func (m *FormModel) View() string { strokes = append(strokes, ui.FormFieldUnfocused.Render(nextBtn)) } + //Основной контейнер приложения. + //Устанавливаем ему ширину и высоту, объединяем все строки в одну с соединением, через двойной перевод строки. container := ui.AppContainer. Width(containerWidth). Height(containerHeight). @@ -204,20 +240,28 @@ func (m *FormModel) View() string { ) } +// updateFields обновляет поля и их внутренние данные, формирует единую команду действий для всех полей, в том числе возврат SugarMsg func (m *FormModel) updateFields(msg tea.Msg) tea.Cmd { + //Массив команд для выполнения var cmds []tea.Cmd for i, fieldModel := range m.FieldModels { var fieldCmd tea.Cmd var updated fields.FieldModel - if i == m.focus { //если фокус установлен на текущем поле, то текущее поле активируем и передаем ему сообщения для обновления + //Если фокус установлен на текущем поле, то нужно активировать его и передать ему сообщение для обновления. + //Если фокус не установлен на текущем поле, то снимаем с него фокус и передаем ему сообщение не связанное с нажатием кнопок клавиатуры + if i == m.focus { fieldModel.Focus() updated, fieldCmd = fieldModel.Update(msg) - } else { //если фокус не установлен на текущем поле, то убираем с неё фокус, передаем сообщения, но только те, что не содержат действий клавиатуры + } else { fieldModel.Blur() if _, ok := msg.(tea.KeyMsg); !ok { updated, fieldCmd = fieldModel.Update(msg) } } + //Если поле успешно вернуло обновленное поле, то необходимо: + //обновить модель поля в текущей форме; + //проверить изменилось ли значение переменной в настройках приложения, + //если изменилось, в массив команд помещаем команду по возврату сообщения Sugar if updated != nil { m.FieldModels[i] = updated for _, sectionField := range m.CurrentSection.Fields { @@ -234,17 +278,21 @@ func (m *FormModel) updateFields(msg tea.Msg) tea.Cmd { } }) sectionField.Variable.Value = newValue //recompute вызывать. + //todo что? надо на гите посмотреть в истории, может я это уже сделал, ведь sugar Msg отправляется } } } } + //Если есть команда поля, то также помещаем её в массив команд if fieldCmd != nil { cmds = append(cmds, fieldCmd) } } + //Объединяем весь массив команду в одну return tea.Batch(cmds...) } +// simpleAboutRender формирует единую строку, описывающую настраиваемое приложение. func simpleAboutRender(paragraphs []string, width int) string { var sb strings.Builder for _, p := range paragraphs { @@ -256,9 +304,10 @@ func simpleAboutRender(paragraphs []string, width int) string { switch { case strings.HasPrefix(p, "# "): // Заголовок 1 уровня - h1Len := utf8.RuneCountInString(p[2:]) - h1Offset := strings.Repeat(" ", (width-h1Len)/2) - sb.WriteString(ui.AboutTitle.Render(h1Offset+p[2:]) + "\n\n") + //h1Len := utf8.RuneCountInString(p[2:]) + //h1Offset := strings.Repeat(" ", (width-h1Len)/2) + //sb.WriteString(ui.AboutTitle.Render(h1Offset+p[2:]) + "\n\n") + sb.WriteString(ui.AboutTitle.Render(p[2:]) + "\n\n") case strings.HasPrefix(p, "## "): // Заголовок 2 уровня sb.WriteString(ui.AboutH2.Render(p[3:]) + "\n\n") diff --git a/setup/list.go b/setup/list.go index 4695153..7583b28 100644 --- a/setup/list.go +++ b/setup/list.go @@ -10,38 +10,46 @@ import ( "github.com/sirupsen/logrus" ) +// ListApplicationItem структура для описывающая один элемент списка type ListApplicationItem struct { - Application types.PackageApplication + Application types.PackageApplication // Приложения для настройки } +// Title метод для получения имени пункта списка func (i ListApplicationItem) Title() string { return fmt.Sprintf("%s, v%s", i.Application.Name, i.Application.Version) } + +// Description метод для получения описания пункта списка func (i ListApplicationItem) Description() string { return fmt.Sprintf("%s", i.Application.Description) } +// FilterValue метод для получения строки, по которой будет происходить фильтрация func (i ListApplicationItem) FilterValue() string { return i.Application.Name } +// ListApplicationsModel структура описывающая модель отрисовки списка приложений type ListApplicationsModel struct { - List list.Model - width int - height int + List list.Model //Готовый компонент модели списка + width int //Ширина окна + height int //Высота окна } +// NewListApplicationsModel создает новую модель списка на основе переданного массива приложений func NewListApplicationsModel(applications []types.PackageApplication, width, height int) *ListApplicationsModel { - + //Заполняем массив приложений items := make([]list.Item, len(applications)) for i, app := range applications { items[i] = ListApplicationItem{app} } - + //delegate нужен для функционала работы с элементом списка 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 @@ -57,16 +65,22 @@ func (m *ListApplicationsModel) Init() tea.Cmd { return nil } +// Update отслеживает действия со списком приложений 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 "enter": + //Обработчик enter нужен для подтверждения элемента списка. + //Получаем информацию о выбранном приложении, + //возвращаем команду по возврату сообщения о показе модального окна подтверждения настройки приложения. selected := m.List.SelectedItem().(ListApplicationItem) return m, func() tea.Msg { return types.ShowModalMsg{ @@ -89,23 +103,27 @@ func (m *ListApplicationsModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } } } + //Если ранее не было возвращено команд, то передаем сообщение напрямую в список m.List, cmd = m.List.Update(msg) return m, cmd } +// View формирует строку для вывода списка приложений func (m *ListApplicationsModel) View() string { + //Строка отступа между списком и описанием gapStroke := " " + //Данные о размерах контейнера и контента внутри containerWidth, containerHeight, contentWidth, contentHeight := ui.ContentSizes(m.width, m.height) + //Ширина списка = пропорция от ширины контента - границы для контейнера списка listWidth := int(float64(contentWidth)*ui.ListWithRatio) - 2*1 - //ширина листа = пропорция от ширины контента - границы для контейнера листа + //Высота списка = высота контента - границы для контейнера списка listHeight := contentHeight - 2*1 - //высота листа = высота контента - границы для контейнера листа + //Ширина описания = обратная пропорция списка от ширины контента - границы для контейнера описания - отступ от контейнера списка descWidth := int(float64(contentWidth)*(1-ui.ListWithRatio)) - 2*1 - len(gapStroke) - //ширина описания = обратная пропорция листа от ширины контента - границы для контейнера описания - отступ от контейнера листа + //Высота описания = высота контента - границы для контейнера описания descHeight := contentHeight - 2*1 - //высота листа = высота контента - границы для контейнера описания - + //Установка размеров списка m.List.SetSize(listWidth, listHeight) //logs.go logrus.DebugLevel set @@ -118,30 +136,26 @@ func (m *ListApplicationsModel) View() string { descWidth, descHeight, ) - listView := lipgloss.NewStyle(). + //Формируем строку списка + listView := ui.ListContainerStyle. Width(listWidth). Height(listHeight). - Border(lipgloss.RoundedBorder()). - BorderForeground(ui.ColorPurple). - AlignVertical(lipgloss.Top). Render(m.List.View()) + //Получаем описание var desc string if sel := m.List.SelectedItem(); sel != nil { desc = sel.(ListApplicationItem).Description() } - - descView := ui.ListDescriptionStyle. + //Формируем строку с описанием + descView := ui.ListDescriptionContainerStyle. Width(descWidth). Height(descHeight). - Border(lipgloss.RoundedBorder()). - BorderForeground(ui.ColorError). - AlignVertical(lipgloss.Top). - Render( - desc, - ) + Render(desc) + //Объединяем список и описание в один контент с отступом body := lipgloss.JoinHorizontal(lipgloss.Center, listView, gapStroke, descView) + //Формируем главный контейнер container := ui.AppContainer. Width(containerWidth). Height(containerHeight). diff --git a/setup/main.go b/setup/main.go index acd7a21..4b65b44 100644 --- a/setup/main.go +++ b/setup/main.go @@ -3,9 +3,7 @@ package setup import ( "bubble/bootstrap" "bubble/types" - "fmt" tea "github.com/charmbracelet/bubbletea" - "github.com/sirupsen/logrus" ) func Start() { @@ -15,23 +13,27 @@ func Start() { } } +// Main структура описывающая объект настроек type Main struct { - current tea.Model - overlay tea.Model - Applications []types.PackageApplication - CurrentApplication *types.PackageApplication - CurrentSection *types.Section - CurrentSectionIdx int - Width int - Height int + current tea.Model //Текущая модель для отображения + overlay tea.Model //Модель для отображения модальных окон + Applications []types.PackageApplication //Список доступных приложений для настройки + CurrentApplication *types.PackageApplication //Выбранное приложение для настройки + CurrentSection *types.Section //Текущая секция + CurrentSectionIdx int //Индекс текущей секции + Width int //Ширина окна + Height int //Высота окна } +// NewMain создаем новый объект настройки func NewMain() *Main { + //Читаем все конфиги приложении apps := bootstrap.LoadApplications() m := &Main{ Applications: apps, CurrentSectionIdx: 0, } + //Записываем в текущую модель список приложений m.current = NewListApplicationsModel(m.Applications, m.Width, m.Height) return m } @@ -40,16 +42,20 @@ func (m *Main) Init() tea.Cmd { return m.current.Init() } +// Update отслеживает действия в объекте настроек func (m *Main) Update(msg tea.Msg) (tea.Model, tea.Cmd) { var cmds []tea.Cmd + var cmd 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) @@ -57,17 +63,17 @@ func (m *Main) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } switch msg := msg.(type) { case tea.WindowSizeMsg: + //Отслеживаем и записываем новые размеры окна. + //Передавать сообщение в модель текущую или модального окна для вызова их обработки изменения размеров не нужно, + //они получили WindowSizeMsg выше, когда мы передаем им переменную 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": + //Обработчик клавиш ctrl+c у приложения нужен для его закрытия. + //Возвращаем команду по возврату сообщения о показе модального окна return m, func() tea.Msg { return types.ShowModalMsg{ Type: types.ModalWarning, @@ -87,8 +93,12 @@ func (m *Main) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } } case types.NextSectionMsg: + //Получаем сообщение о переходе на следующую секцию настраиваемого приложения. + //Увеличиваем индекс текущей секции. m.CurrentSectionIdx++ + //Если новый индекс равен if m.CurrentSectionIdx == len(m.CurrentApplication.Sections) { + //todo что делать когда секций больше нужного? return m, tea.Quit } for sectionIdx := m.CurrentSectionIdx; sectionIdx < len(m.CurrentApplication.Sections); sectionIdx++ { @@ -134,28 +144,51 @@ func (m *Main) Update(msg tea.Msg) (tea.Model, tea.Cmd) { break } } - //если секция не была найдена, то выходим + //Если секция доступная к выводу не была найдена, то возвращаем модальное окно с выходом к списку приложений if m.CurrentSection == nil { - logrus.Infof("select app, eq nil, out") - return m, tea.Quit + return m, func() tea.Msg { + return types.ShowModalMsg{ + Type: types.ModalError, + Title: "Ошибка", + Message: "Ошибка выбора приложения. Указанное приложение не имеет доступных секций." + + "Проверьте конфиг настраиваемого приложения на предмет выполнения условий секций", + ModalButtons: []types.ModalButton{{Text: "Вернуться к списку приложений", Value: true}}, + OnResult: func(result any) tea.Cmd { + return func() tea.Msg { + return types.BackToListMsg{} + } + }, + } + } } //создаем новую форму 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: + //Получаем сообщение о закрытии модального окна. + //Стираем модальное окно. m.overlay = nil - return m, tea.Batch(cmds...) + return m, nil + //предохранитель + //return m,tea.Batch(cmds...) } + //Возвращаем команду собранную из массива команд текущей модели return m, tea.Batch(cmds...) } +// View формирует строку на основе модального окна или текущей модели func (m *Main) View() string { view := m.current.View() if m.overlay != nil { @@ -164,10 +197,14 @@ func (m *Main) View() string { return view } +// Sugar проверяет поле указанное в сообщение на важность в форме и приложении. func (m *Main) Sugar(sugar types.SugarMsg) { + //Если полей с указанной переменной несколько, то выключаем все прочие поля кроме, указанного в сообщении if m.IsMultiField(sugar.VariableName) { m.DeactivateField(sugar.VariableName, sugar.FieldName) } + //Если от переменной поля зависит секции или поля, + //то вызываем перерасчет значений compute полей и формируем новую форму с учетом прошлого фокуса на форме if IsParent(sugar.VariableName, m.CurrentApplication.Sections) { if sugar.Compute != "" { Recompute(m.CurrentApplication) @@ -176,6 +213,7 @@ func (m *Main) Sugar(sugar types.SugarMsg) { } } +// IsParent проверяет являться ли указанная переменная условие для поля или секции func IsParent(variableName string, sections []types.Section) bool { for _, section := range sections { for _, parent := range section.Parent { @@ -195,6 +233,7 @@ func IsParent(variableName string, sections []types.Section) bool { return false } +// IsMultiField проверяет переменную на использование несколькими полями. func (m *Main) IsMultiField(variableName string) bool { var count int for _, section := range m.CurrentApplication.Sections { @@ -207,6 +246,7 @@ func (m *Main) IsMultiField(variableName string) bool { return count > 1 } +// DeactivateField отключает все поля использующие указанную переменную, кроме поля с указанным именем func (m *Main) DeactivateField(variableName, fieldName string) { for sectionIdx, section := range m.CurrentApplication.Sections { for fieldIdx, field := range section.Fields { @@ -220,23 +260,3 @@ func (m *Main) DeactivateField(variableName, fieldName string) { } } } - -func (m *Main) RecomputeFields() { - for i := range m.CurrentApplication.Sections { - section := &m.CurrentApplication.Sections[i] - for j := range section.Fields { - field := §ion.Fields[j] - if field.FieldType == "compute" && field.Compute != "" { - if field.Variable != nil { - continue - } - value, err := ComputeValue(field.Compute, m.CurrentApplication) - if err != nil { - value = fmt.Sprintf("Ошибка вычисления: %v", err) - } - field.Variable.Value = value - - } - } - } -} diff --git a/setup/modal.go b/setup/modal.go index c3ddb6b..83729d0 100644 --- a/setup/modal.go +++ b/setup/modal.go @@ -7,21 +7,20 @@ import ( "github.com/charmbracelet/lipgloss" ) +// ModalModel структура описывающая модель отрисовки модального окна 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 + modalType types.ModalType //Тип модального окна + title string //Заголовок модального окна + message string //Сообщение модального окна + buttons []types.ModalButton //Кнопки модального окна + focus int //Фокус для отслеживания с каким объектом надо работать + width int //Ширина окна + height int //Высота окна + onResult func(result any) tea.Cmd //Какое действие надо выполнить при нажатии } +// NewModalModel создает новую модель модального окна на основе переданного сообщения 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, @@ -38,32 +37,46 @@ func (m *ModalModel) Init() tea.Cmd { return nil } +// Update отслеживает действия с модальным окном 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": + case "left", "shift+tab": + //Обработчик нажатия клавиш left и shift+tab нужен для перевода фокуса на прошлую кнопку. + //Уменьшаем фокус, если фокус стал меньше 0, значит он вышел за пределы кнопок, + //переводим его на последнюю кнопку. m.focus-- if m.focus < 0 { m.focus = len(m.buttons) - 1 } - case "right", "l", "tab": + case "right", "tab": + //Обработчик нажатия клавиш right и tab нужен для перевода фокуса на следующую кнопку. + //Увеличиваем фокус, если фокус стал равен количество кнопок, значит он вышел за их пределы, + //переводим его на первую позицию, то есть на 0 m.focus++ if m.focus >= len(m.buttons) { m.focus = 0 } case "enter", " ": - //вытаскиваем значение кнопки + //Обработчик клавиши enter нужен для подтверждения кнопки. + //Получаем значение кнопки, а после если у модального окна прописана функция, + //то передаем значение кнопки в функцию модального окна. result := m.buttons[m.focus].Value var cmd tea.Cmd - if m.onResult != nil { //если функция кнопки не пустая, мы передаем в функцию значение кнопки + if m.onResult != nil { cmd = m.onResult(result) } - return m, tea.Batch( //объединяем 2 действия, функцию кнопки и закрытие модального окна + //Возвращаем единую команду составленную из двух, + //результата выполнения функции модального окна, + //функцию возвращения сообщения о закрытии модального окна. + return m, tea.Batch( cmd, func() tea.Msg { return types.CloseModalMsg{} }, ) @@ -72,13 +85,14 @@ func (m *ModalModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return m, nil } +// View формирует строку для вывода модального окна и её контента func (m *ModalModel) View() string { - + //Данные о размерах контейнера containerWidth, containerHeight, _, _ := ui.ContentSizes(m.width, m.height) - + //Модальное окно не должно занимать все пространство, поэтому получаем минимально допустимые размеры modalWidth := min(containerWidth, ui.ModalMaxWidth) modalHeight := min(containerHeight, ui.ModalMaxHeight) - + //Формируем просто сообщение и кнопки с учетом фокуса message := lipgloss.NewStyle().Render(m.message) var buttons []string for i, button := range m.buttons { @@ -88,12 +102,14 @@ func (m *ModalModel) View() string { } buttons = append(buttons, style.Render(button.Text)) } + //Строка кнопок buttonRow := lipgloss.JoinHorizontal(lipgloss.Center, buttons...) + //Тело контента модального окна modalBody := lipgloss.JoinVertical(lipgloss.Center, message, "", buttonRow) + //В зависимости от типа модального окна разные цвета var borderColor lipgloss.Color var modalTitleStyle lipgloss.Style - switch m.modalType { case types.ModalError: borderColor = ui.ColorError @@ -105,33 +121,19 @@ func (m *ModalModel) View() string { borderColor = ui.ColorAccent modalTitleStyle = ui.ModalTitleDefault } - // - //switch m.modalType { - //case types.ModalError: - // modalStyle = ui.ModalBorderError - // modalTitleStyle = ui.ModalTitleError - //case types.ModalWarning, types.ModalConfirm: - // modalStyle = ui.ModalBorderWarning - // modalTitleStyle = ui.ModalTitleWarning - //default: - // modalStyle = ui.ModalBorderInfo - // modalTitleStyle = ui.ModalTitleDefault - //} - var modalView string + //Формируем модальный контейнер с контентом внутри и указанным цветом границ. + modalView := ui.ModalContainer. + Width(modalWidth). + Height(modalHeight). + Align(lipgloss.Center). + Border(lipgloss.DoubleBorder()). + BorderForeground(borderColor). + Render(modalBody) + symbolToRepeat := "*" + //Если у модального окна есть заголовок, то в modalView записываем новое значение, + //ставя заголовок сверху модального контейнера if m.title != "" { - modalView = ui.ModalContainer. - Width(modalWidth). - Height(modalHeight). - Align(lipgloss.Center, lipgloss.Center). - Border(lipgloss.DoubleBorder()). - BorderForeground(borderColor). - Render(modalBody) - //modalView = ui.ModalContainer. - // Width(modalWidth). - // Height(modalHeight). - // BorderForeground(borderColor). - // Render(modalBody) //repeatCount := modalWidth - utf8.RuneCountInString(m.title) //if repeatCount < 0 { // repeatCount = 0 @@ -139,19 +141,6 @@ func (m *ModalModel) View() string { //symbolFill := strings.Repeat(symbolToRepeat, repeatCount/2) //modalView = lipgloss.JoinVertical(lipgloss.Center, modalTitleStyle.Render(symbolFill+m.title+symbolFill), modalView) modalView = lipgloss.JoinVertical(lipgloss.Center, modalTitleStyle.Render(m.title), modalView) - } else { - modalView = ui.ModalContainer. - Width(modalWidth). - Height(modalHeight). - BorderStyle(lipgloss.ASCIIBorder()). - BorderForeground(borderColor). - Render(modalBody) - //modalView = modalStyle. - // Width(modalWidth). - // Height(modalHeight). - // Padding(1, 3). - // Border(lipgloss.DoubleBorder()). - // Render(modalBody) } return lipgloss.Place(m.width, m.height, lipgloss.Center, lipgloss.Center, diff --git a/setup/ui/style.go b/setup/ui/style.go index 6ecce68..7b5e7e1 100644 --- a/setup/ui/style.go +++ b/setup/ui/style.go @@ -59,6 +59,14 @@ func ContentSizes(screenWidth, screenHeight int) (containerWidth, containerHeigh // Используемые цвета var ( + // ColorForeground = lipgloss.Color("#c0caf5") + // ColorBackground = lipgloss.Color("#1a1b26") + // ColorAccent = lipgloss.Color("#7aa2f7") + // ColorSuccess = lipgloss.Color("#9ece6a") + // ColorError = lipgloss.Color("#f7768e") + // ColorWarning = lipgloss.Color("#e0af68") + // ColorInfo = lipgloss.Color("#7dcfff") + ColorFg = lipgloss.Color("#eeeeee") ColorBg = lipgloss.Color("#0d1117") ColorAccent = lipgloss.Color("#a6e3a1") @@ -77,6 +85,17 @@ var AppContainer = lipgloss.NewStyle(). // Стили для списка приложений var ( + ListContainerStyle = lipgloss.NewStyle(). + BorderStyle(lipgloss.RoundedBorder()). + BorderForeground(ColorPurple). + AlignVertical(lipgloss.Top) + + ListDescriptionContainerStyle = lipgloss.NewStyle(). + BorderStyle(lipgloss.RoundedBorder()). + BorderForeground(ColorError). + Foreground(ColorAccent2). + AlignVertical(lipgloss.Top) + ListTitleStyle = lipgloss.NewStyle().Foreground(ColorPurple).Italic(true).Bold(true) ListSelectedStyle = lipgloss.NewStyle().Foreground(ColorError).Bold(true) ListDescriptionStyle = lipgloss.NewStyle().Foreground(ColorAccent2) @@ -124,12 +143,11 @@ var ( Bold(true). Align(lipgloss.Center). MarginBottom(1). - Padding(1, 2) + Padding(1, 0) FinalFooter = lipgloss.NewStyle(). Foreground(ColorGrey). MarginTop(1). - Padding(1, 0). Align(lipgloss.Center) FinalTableBorder = lipgloss.NewStyle(). @@ -154,12 +172,6 @@ var ( ModalContainer = lipgloss.NewStyle(). Border(lipgloss.DoubleBorder()). Align(lipgloss.Center, lipgloss.Center) - //Padding(ApplicationVerticalOffset, ApplicationHorizontalOffset) - - 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)