Комментирование, правки кода, исправление модальных окон

This commit is contained in:
XeRo 2026-01-15 17:55:52 +05:00
parent 2e8eee135e
commit 300afb5d26
8 changed files with 299 additions and 618 deletions

436
log.txt
View file

@ -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

View file

@ -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 := &section.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 {

View file

@ -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())
}

View file

@ -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")

View file

@ -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).

View file

@ -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 := &section.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
}
}
}
}

View file

@ -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,

View file

@ -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)