mirror of
https://github.com/pdxlocations/contact.git
synced 2026-03-28 17:12:35 +01:00
Compare commits
10 Commits
fix-settin
...
input-vali
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cf4137347f | ||
|
|
12f81f6af6 | ||
|
|
684d298fac | ||
|
|
4179a3f8d0 | ||
|
|
7a61808f47 | ||
|
|
a8680ac0ed | ||
|
|
818575939a | ||
|
|
acaad849b0 | ||
|
|
0b25dda4af | ||
|
|
18329f0128 |
@@ -67,11 +67,3 @@ To quickly connect to localhost, use:
|
|||||||
```sh
|
```sh
|
||||||
contact -t
|
contact -t
|
||||||
```
|
```
|
||||||
## Install in development (editable) mode:
|
|
||||||
```bash
|
|
||||||
git clone https://github.com/pdxlocations/contact.git
|
|
||||||
cd contact
|
|
||||||
python3 -m venv .venv
|
|
||||||
source .venv/bin/activate
|
|
||||||
pip install -e .
|
|
||||||
```
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import curses
|
import curses
|
||||||
import logging
|
import logging
|
||||||
import time
|
|
||||||
import traceback
|
import traceback
|
||||||
from typing import Union
|
from typing import Union
|
||||||
|
|
||||||
@@ -13,7 +12,7 @@ from contact.utilities.input_handlers import get_list_input
|
|||||||
import contact.ui.default_config as config
|
import contact.ui.default_config as config
|
||||||
import contact.ui.dialog
|
import contact.ui.dialog
|
||||||
from contact.ui.nav_utils import move_main_highlight, draw_main_arrows, get_msg_window_lines, wrap_text
|
from contact.ui.nav_utils import move_main_highlight, draw_main_arrows, get_msg_window_lines, wrap_text
|
||||||
from contact.utilities.singleton import ui_state, interface_state, menu_state
|
from contact.utilities.singleton import ui_state, interface_state
|
||||||
|
|
||||||
|
|
||||||
def handle_resize(stdscr: curses.window, firstrun: bool) -> None:
|
def handle_resize(stdscr: curses.window, firstrun: bool) -> None:
|
||||||
@@ -321,15 +320,10 @@ def handle_enter(input_text: str) -> str:
|
|||||||
return input_text
|
return input_text
|
||||||
|
|
||||||
elif len(input_text) > 0:
|
elif len(input_text) > 0:
|
||||||
# TODO: This is a hack to prevent sending messages too quickly. Let's get errors from the node.
|
|
||||||
now = time.monotonic()
|
|
||||||
if now - ui_state.last_sent_time < 2.5:
|
|
||||||
contact.ui.dialog.dialog("Slow down", "Please wait 2 seconds between messages.")
|
|
||||||
return input_text
|
|
||||||
# Enter key pressed, send user input as message
|
# Enter key pressed, send user input as message
|
||||||
send_message(input_text, channel=ui_state.selected_channel)
|
send_message(input_text, channel=ui_state.selected_channel)
|
||||||
draw_messages_window(True)
|
draw_messages_window(True)
|
||||||
ui_state.last_sent_time = now
|
|
||||||
# Clear entry window and reset input text
|
# Clear entry window and reset input text
|
||||||
entry_win.erase()
|
entry_win.erase()
|
||||||
return ""
|
return ""
|
||||||
@@ -363,10 +357,7 @@ def handle_backspace(entry_win: curses.window, input_text: str) -> str:
|
|||||||
def handle_backtick(stdscr: curses.window) -> None:
|
def handle_backtick(stdscr: curses.window) -> None:
|
||||||
"""Handle backtick key events to open the settings menu."""
|
"""Handle backtick key events to open the settings menu."""
|
||||||
curses.curs_set(0)
|
curses.curs_set(0)
|
||||||
previous_window = ui_state.current_window
|
|
||||||
ui_state.current_window = 4
|
|
||||||
settings_menu(stdscr, interface_state.interface)
|
settings_menu(stdscr, interface_state.interface)
|
||||||
ui_state.current_window = previous_window
|
|
||||||
curses.curs_set(1)
|
curses.curs_set(1)
|
||||||
refresh_node_list()
|
refresh_node_list()
|
||||||
handle_resize(stdscr, False)
|
handle_resize(stdscr, False)
|
||||||
@@ -601,8 +592,6 @@ def draw_messages_window(scroll_to_bottom: bool = False) -> None:
|
|||||||
refresh_pad(1)
|
refresh_pad(1)
|
||||||
|
|
||||||
draw_packetlog_win()
|
draw_packetlog_win()
|
||||||
if ui_state.current_window == 4:
|
|
||||||
menu_state.need_redraw = True
|
|
||||||
|
|
||||||
|
|
||||||
def draw_node_list() -> None:
|
def draw_node_list() -> None:
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import sys
|
|||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
from contact.utilities.save_to_radio import save_changes
|
from contact.utilities.save_to_radio import save_changes
|
||||||
import contact.ui.default_config as config
|
|
||||||
from contact.utilities.config_io import config_export, config_import
|
from contact.utilities.config_io import config_export, config_import
|
||||||
from contact.utilities.control_utils import parse_ini_file, transform_menu_path
|
from contact.utilities.control_utils import parse_ini_file, transform_menu_path
|
||||||
from contact.utilities.input_handlers import (
|
from contact.utilities.input_handlers import (
|
||||||
@@ -21,7 +20,9 @@ from contact.ui.dialog import dialog
|
|||||||
from contact.ui.menus import generate_menu_from_protobuf
|
from contact.ui.menus import generate_menu_from_protobuf
|
||||||
from contact.ui.nav_utils import move_highlight, draw_arrows, update_help_window
|
from contact.ui.nav_utils import move_highlight, draw_arrows, update_help_window
|
||||||
from contact.ui.user_config import json_editor
|
from contact.ui.user_config import json_editor
|
||||||
from contact.utilities.singleton import menu_state
|
from contact.ui.ui_state import MenuState
|
||||||
|
|
||||||
|
menu_state = MenuState()
|
||||||
|
|
||||||
# Constants
|
# Constants
|
||||||
width = 80
|
width = 80
|
||||||
@@ -35,19 +36,16 @@ script_dir = os.path.dirname(os.path.abspath(__file__))
|
|||||||
parent_dir = os.path.abspath(os.path.join(script_dir, os.pardir))
|
parent_dir = os.path.abspath(os.path.join(script_dir, os.pardir))
|
||||||
|
|
||||||
# Paths
|
# Paths
|
||||||
# locals_dir = os.path.dirname(os.path.abspath(sys.argv[0])) # Current script directory
|
locals_dir = os.path.dirname(os.path.abspath(sys.argv[0])) # Current script directory
|
||||||
translation_file = os.path.join(parent_dir, "localisations", "en.ini")
|
translation_file = os.path.join(parent_dir, "localisations", "en.ini")
|
||||||
|
|
||||||
# config_folder = os.path.join(locals_dir, "node-configs")
|
config_folder = os.path.join(locals_dir, "node-configs")
|
||||||
config_folder = os.path.abspath(config.node_configs_file_path)
|
|
||||||
|
|
||||||
# Load translations
|
# Load translations
|
||||||
field_mapping, help_text = parse_ini_file(translation_file)
|
field_mapping, help_text = parse_ini_file(translation_file)
|
||||||
|
|
||||||
|
|
||||||
|
def display_menu(menu_state: MenuState) -> tuple[object, object]: # curses.window or pad types
|
||||||
def display_menu() -> tuple[object, object]: # curses.window or pad types
|
|
||||||
|
|
||||||
|
|
||||||
min_help_window_height = 6
|
min_help_window_height = 6
|
||||||
num_items = len(menu_state.current_menu) + (1 if menu_state.show_save_option else 0)
|
num_items = len(menu_state.current_menu) + (1 if menu_state.show_save_option else 0)
|
||||||
@@ -108,7 +106,7 @@ def display_menu() -> tuple[object, object]: # curses.window or pad types
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Draw help window with dynamically updated max_help_lines
|
# Draw help window with dynamically updated max_help_lines
|
||||||
draw_help_window(start_y, start_x, menu_height, max_help_lines, transformed_path)
|
draw_help_window(start_y, start_x, menu_height, max_help_lines, transformed_path, menu_state)
|
||||||
|
|
||||||
menu_win.refresh()
|
menu_win.refresh()
|
||||||
menu_pad.refresh(
|
menu_pad.refresh(
|
||||||
@@ -134,6 +132,7 @@ def draw_help_window(
|
|||||||
menu_height: int,
|
menu_height: int,
|
||||||
max_help_lines: int,
|
max_help_lines: int,
|
||||||
transformed_path: List[str],
|
transformed_path: List[str],
|
||||||
|
menu_state: MenuState,
|
||||||
) -> None:
|
) -> None:
|
||||||
|
|
||||||
global help_win
|
global help_win
|
||||||
@@ -169,32 +168,29 @@ def settings_menu(stdscr: object, interface: object) -> None:
|
|||||||
|
|
||||||
modified_settings = {}
|
modified_settings = {}
|
||||||
|
|
||||||
menu_state.need_redraw = True
|
need_redraw = True
|
||||||
menu_state.show_save_option = False
|
menu_state.show_save_option = False
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
if menu_state.need_redraw:
|
if need_redraw:
|
||||||
menu_state.need_redraw = False
|
|
||||||
options = list(menu_state.current_menu.keys())
|
options = list(menu_state.current_menu.keys())
|
||||||
|
|
||||||
# Determine if save option should be shown
|
|
||||||
path = menu_state.menu_path
|
|
||||||
menu_state.show_save_option = (
|
menu_state.show_save_option = (
|
||||||
(len(path) > 2 and ("Radio Settings" in path or "Module Settings" in path))
|
(
|
||||||
or (len(path) == 2 and "User Settings" in path)
|
len(menu_state.menu_path) > 2
|
||||||
or (len(path) == 3 and "Channels" in path)
|
and ("Radio Settings" in menu_state.menu_path or "Module Settings" in menu_state.menu_path)
|
||||||
|
)
|
||||||
|
or (len(menu_state.menu_path) == 2 and "User Settings" in menu_state.menu_path)
|
||||||
|
or (len(menu_state.menu_path) == 3 and "Channels" in menu_state.menu_path)
|
||||||
)
|
)
|
||||||
|
|
||||||
# Display the menu
|
# Display the menu
|
||||||
menu_win, menu_pad = display_menu()
|
menu_win, menu_pad = display_menu(menu_state)
|
||||||
|
|
||||||
if menu_win is None:
|
need_redraw = False
|
||||||
continue # Skip if menu_win is not initialized
|
|
||||||
|
|
||||||
menu_win.timeout(200) # wait up to 200 ms for a keypress (or less if key is pressed)
|
# Capture user input
|
||||||
key = menu_win.getch()
|
key = menu_win.getch()
|
||||||
if key == -1:
|
|
||||||
continue
|
|
||||||
|
|
||||||
max_index = len(options) + (1 if menu_state.show_save_option else 0) - 1
|
max_index = len(options) + (1 if menu_state.show_save_option else 0) - 1
|
||||||
# max_help_lines = 4
|
# max_help_lines = 4
|
||||||
@@ -228,7 +224,7 @@ def settings_menu(stdscr: object, interface: object) -> None:
|
|||||||
)
|
)
|
||||||
|
|
||||||
elif key == curses.KEY_RESIZE:
|
elif key == curses.KEY_RESIZE:
|
||||||
menu_state.need_redraw = True
|
need_redraw = True
|
||||||
curses.update_lines_cols()
|
curses.update_lines_cols()
|
||||||
|
|
||||||
menu_win.erase()
|
menu_win.erase()
|
||||||
@@ -252,7 +248,7 @@ def settings_menu(stdscr: object, interface: object) -> None:
|
|||||||
)
|
)
|
||||||
|
|
||||||
elif key == curses.KEY_RIGHT or key == ord("\n"):
|
elif key == curses.KEY_RIGHT or key == ord("\n"):
|
||||||
menu_state.need_redraw = True
|
need_redraw = True
|
||||||
menu_state.start_index.append(0)
|
menu_state.start_index.append(0)
|
||||||
menu_win.erase()
|
menu_win.erase()
|
||||||
help_win.erase()
|
help_win.erase()
|
||||||
@@ -394,7 +390,7 @@ def settings_menu(stdscr: object, interface: object) -> None:
|
|||||||
menu_state.start_index.pop()
|
menu_state.start_index.pop()
|
||||||
menu_state.selected_index = 4
|
menu_state.selected_index = 4
|
||||||
continue
|
continue
|
||||||
# menu_state.need_redraw = True
|
# need_redraw = True
|
||||||
|
|
||||||
field_info = menu_state.current_menu.get(selected_option)
|
field_info = menu_state.current_menu.get(selected_option)
|
||||||
if isinstance(field_info, tuple):
|
if isinstance(field_info, tuple):
|
||||||
@@ -513,7 +509,7 @@ def settings_menu(stdscr: object, interface: object) -> None:
|
|||||||
menu_state.selected_index = 0
|
menu_state.selected_index = 0
|
||||||
|
|
||||||
elif key == curses.KEY_LEFT:
|
elif key == curses.KEY_LEFT:
|
||||||
menu_state.need_redraw = True
|
need_redraw = True
|
||||||
|
|
||||||
menu_win.erase()
|
menu_win.erase()
|
||||||
help_win.erase()
|
help_win.erase()
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import json
|
|||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
from typing import Dict
|
from typing import Dict
|
||||||
from contact.ui.colors import setup_colors
|
|
||||||
|
|
||||||
# Get the parent directory of the script
|
# Get the parent directory of the script
|
||||||
script_dir = os.path.dirname(os.path.abspath(__file__))
|
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||||
@@ -14,12 +13,6 @@ parent_dir = os.path.abspath(os.path.join(script_dir, os.pardir))
|
|||||||
# parent_dir = "/tmp/test_nonwritable"
|
# parent_dir = "/tmp/test_nonwritable"
|
||||||
|
|
||||||
|
|
||||||
def reload_config() -> None:
|
|
||||||
loaded_config = initialize_config()
|
|
||||||
assign_config_variables(loaded_config)
|
|
||||||
setup_colors(reinit=True)
|
|
||||||
|
|
||||||
|
|
||||||
def _is_writable_dir(path: str) -> bool:
|
def _is_writable_dir(path: str) -> bool:
|
||||||
"""
|
"""
|
||||||
Return True if we can create & delete a temp file in `path`.
|
Return True if we can create & delete a temp file in `path`.
|
||||||
@@ -64,7 +57,6 @@ config_root = _get_config_root(parent_dir)
|
|||||||
json_file_path = os.path.join(config_root, "config.json")
|
json_file_path = os.path.join(config_root, "config.json")
|
||||||
log_file_path = os.path.join(config_root, "client.log")
|
log_file_path = os.path.join(config_root, "client.log")
|
||||||
db_file_path = os.path.join(config_root, "client.db")
|
db_file_path = os.path.join(config_root, "client.db")
|
||||||
node_configs_file_path = os.path.join(config_root, "node-configs/")
|
|
||||||
|
|
||||||
|
|
||||||
def format_json_single_line_arrays(data: Dict[str, object], indent: int = 4) -> str:
|
def format_json_single_line_arrays(data: Dict[str, object], indent: int = 4) -> str:
|
||||||
@@ -185,7 +177,6 @@ def initialize_config() -> Dict[str, object]:
|
|||||||
"node_list_16ths": "5",
|
"node_list_16ths": "5",
|
||||||
"db_file_path": db_file_path,
|
"db_file_path": db_file_path,
|
||||||
"log_file_path": log_file_path,
|
"log_file_path": log_file_path,
|
||||||
"node_configs_file_path": node_configs_file_path,
|
|
||||||
"message_prefix": ">>",
|
"message_prefix": ">>",
|
||||||
"sent_message_prefix": ">> Sent",
|
"sent_message_prefix": ">> Sent",
|
||||||
"notification_symbol": "*",
|
"notification_symbol": "*",
|
||||||
@@ -226,7 +217,7 @@ def initialize_config() -> Dict[str, object]:
|
|||||||
def assign_config_variables(loaded_config: Dict[str, object]) -> None:
|
def assign_config_variables(loaded_config: Dict[str, object]) -> None:
|
||||||
# Assign values to local variables
|
# Assign values to local variables
|
||||||
|
|
||||||
global db_file_path, log_file_path, node_configs_file_path, message_prefix, sent_message_prefix
|
global db_file_path, log_file_path, message_prefix, sent_message_prefix
|
||||||
global notification_symbol, ack_implicit_str, ack_str, nak_str, ack_unknown_str
|
global notification_symbol, ack_implicit_str, ack_str, nak_str, ack_unknown_str
|
||||||
global node_list_16ths, channel_list_16ths
|
global node_list_16ths, channel_list_16ths
|
||||||
global theme, COLOR_CONFIG
|
global theme, COLOR_CONFIG
|
||||||
@@ -236,7 +227,6 @@ def assign_config_variables(loaded_config: Dict[str, object]) -> None:
|
|||||||
node_list_16ths = loaded_config["node_list_16ths"]
|
node_list_16ths = loaded_config["node_list_16ths"]
|
||||||
db_file_path = loaded_config["db_file_path"]
|
db_file_path = loaded_config["db_file_path"]
|
||||||
log_file_path = loaded_config["log_file_path"]
|
log_file_path = loaded_config["log_file_path"]
|
||||||
node_configs_file_path = loaded_config.get("node_configs_file_path")
|
|
||||||
message_prefix = loaded_config["message_prefix"]
|
message_prefix = loaded_config["message_prefix"]
|
||||||
sent_message_prefix = loaded_config["sent_message_prefix"]
|
sent_message_prefix = loaded_config["sent_message_prefix"]
|
||||||
notification_symbol = loaded_config["notification_symbol"]
|
notification_symbol = loaded_config["notification_symbol"]
|
||||||
@@ -268,7 +258,6 @@ if __name__ == "__main__":
|
|||||||
print("\nLoaded Configuration:")
|
print("\nLoaded Configuration:")
|
||||||
print(f"Database File Path: {db_file_path}")
|
print(f"Database File Path: {db_file_path}")
|
||||||
print(f"Log File Path: {log_file_path}")
|
print(f"Log File Path: {log_file_path}")
|
||||||
print(f"Configs File Path: {node_configs_file_path}")
|
|
||||||
print(f"Message Prefix: {message_prefix}")
|
print(f"Message Prefix: {message_prefix}")
|
||||||
print(f"Sent Message Prefix: {sent_message_prefix}")
|
print(f"Sent Message Prefix: {sent_message_prefix}")
|
||||||
print(f"Notification Symbol: {notification_symbol}")
|
print(f"Notification Symbol: {notification_symbol}")
|
||||||
|
|||||||
@@ -1,18 +1,12 @@
|
|||||||
import curses
|
import curses
|
||||||
from contact.ui.colors import get_color
|
from contact.ui.colors import get_color
|
||||||
from contact.utilities.singleton import menu_state, ui_state
|
|
||||||
|
|
||||||
|
|
||||||
def dialog(title: str, message: str) -> None:
|
def dialog(title: str, message: str) -> None:
|
||||||
"""Display a dialog with a title and message."""
|
|
||||||
|
|
||||||
previous_window = ui_state.current_window
|
|
||||||
ui_state.current_window = 4
|
|
||||||
|
|
||||||
curses.update_lines_cols()
|
|
||||||
height, width = curses.LINES, curses.COLS
|
height, width = curses.LINES, curses.COLS
|
||||||
|
|
||||||
# Parse message into lines and calculate dimensions
|
# Calculate dialog dimensions
|
||||||
message_lines = message.splitlines()
|
message_lines = message.splitlines()
|
||||||
max_line_length = max(len(l) for l in message_lines)
|
max_line_length = max(len(l) for l in message_lines)
|
||||||
dialog_width = max(len(title) + 4, max_line_length + 4)
|
dialog_width = max(len(title) + 4, max_line_length + 4)
|
||||||
@@ -20,44 +14,36 @@ def dialog(title: str, message: str) -> None:
|
|||||||
x = (width - dialog_width) // 2
|
x = (width - dialog_width) // 2
|
||||||
y = (height - dialog_height) // 2
|
y = (height - dialog_height) // 2
|
||||||
|
|
||||||
def draw_window():
|
# Create dialog window
|
||||||
win.erase()
|
|
||||||
win.bkgd(get_color("background"))
|
|
||||||
win.attrset(get_color("window_frame"))
|
|
||||||
win.border(0)
|
|
||||||
|
|
||||||
win.addstr(0, 2, title, get_color("settings_default"))
|
|
||||||
|
|
||||||
for i, line in enumerate(message_lines):
|
|
||||||
msg_x = (dialog_width - len(line)) // 2
|
|
||||||
win.addstr(2 + i, msg_x, line, get_color("settings_default"))
|
|
||||||
|
|
||||||
ok_text = " Ok "
|
|
||||||
win.addstr(
|
|
||||||
dialog_height - 2,
|
|
||||||
(dialog_width - len(ok_text)) // 2,
|
|
||||||
ok_text,
|
|
||||||
get_color("settings_default", reverse=True),
|
|
||||||
)
|
|
||||||
|
|
||||||
win.refresh()
|
|
||||||
|
|
||||||
win = curses.newwin(dialog_height, dialog_width, y, x)
|
win = curses.newwin(dialog_height, dialog_width, y, x)
|
||||||
draw_window()
|
win.bkgd(get_color("background"))
|
||||||
|
win.attrset(get_color("window_frame"))
|
||||||
|
win.border(0)
|
||||||
|
|
||||||
|
# Add title
|
||||||
|
win.addstr(0, 2, title, get_color("settings_default"))
|
||||||
|
|
||||||
|
# Add message (centered)
|
||||||
|
for i, line in enumerate(message_lines):
|
||||||
|
msg_x = (dialog_width - len(line)) // 2
|
||||||
|
win.addstr(2 + i, msg_x, line, get_color("settings_default"))
|
||||||
|
|
||||||
|
# Add centered OK button
|
||||||
|
ok_text = " Ok "
|
||||||
|
win.addstr(
|
||||||
|
dialog_height - 2,
|
||||||
|
(dialog_width - len(ok_text)) // 2,
|
||||||
|
ok_text,
|
||||||
|
get_color("settings_default", reverse=True),
|
||||||
|
)
|
||||||
|
|
||||||
|
# Refresh dialog window
|
||||||
|
win.refresh()
|
||||||
|
|
||||||
|
# Get user input
|
||||||
while True:
|
while True:
|
||||||
win.timeout(200)
|
|
||||||
char = win.getch()
|
char = win.getch()
|
||||||
|
if char in (curses.KEY_ENTER, 10, 13, 32, 27): # Enter, space, or Esc
|
||||||
if menu_state.need_redraw:
|
|
||||||
menu_state.need_redraw = False
|
|
||||||
draw_window()
|
|
||||||
|
|
||||||
if char in (curses.KEY_ENTER, 10, 13, 32, 27): # Enter, space, Esc
|
|
||||||
win.erase()
|
win.erase()
|
||||||
win.refresh()
|
win.refresh()
|
||||||
ui_state.current_window = previous_window
|
|
||||||
return
|
return
|
||||||
|
|
||||||
if char == -1:
|
|
||||||
continue
|
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ class MenuState:
|
|||||||
current_menu: Union[Dict[str, Any], List[Any], str, int] = field(default_factory=dict)
|
current_menu: Union[Dict[str, Any], List[Any], str, int] = field(default_factory=dict)
|
||||||
menu_path: List[str] = field(default_factory=list)
|
menu_path: List[str] = field(default_factory=list)
|
||||||
show_save_option: bool = False
|
show_save_option: bool = False
|
||||||
need_redraw: bool = False
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@@ -25,7 +24,6 @@ class ChatUIState:
|
|||||||
selected_message: int = 0
|
selected_message: int = 0
|
||||||
selected_node: int = 0
|
selected_node: int = 0
|
||||||
current_window: int = 0
|
current_window: int = 0
|
||||||
last_sent_time: float = 0.0
|
|
||||||
|
|
||||||
selected_index: int = 0
|
selected_index: int = 0
|
||||||
start_index: List[int] = field(default_factory=lambda: [0, 0, 0])
|
start_index: List[int] = field(default_factory=lambda: [0, 0, 0])
|
||||||
|
|||||||
@@ -4,10 +4,9 @@ import curses
|
|||||||
from typing import Any, List, Dict
|
from typing import Any, List, Dict
|
||||||
|
|
||||||
from contact.ui.colors import get_color, setup_colors, COLOR_MAP
|
from contact.ui.colors import get_color, setup_colors, COLOR_MAP
|
||||||
import contact.ui.default_config as config
|
from contact.ui.default_config import format_json_single_line_arrays, loaded_config
|
||||||
from contact.ui.nav_utils import move_highlight, draw_arrows
|
from contact.ui.nav_utils import move_highlight, draw_arrows
|
||||||
from contact.utilities.input_handlers import get_list_input
|
from contact.utilities.input_handlers import get_list_input
|
||||||
from contact.utilities.singleton import menu_state
|
|
||||||
|
|
||||||
|
|
||||||
width = 80
|
width = 80
|
||||||
@@ -54,9 +53,7 @@ def edit_value(key: str, current_value: str) -> str:
|
|||||||
# Handle theme selection dynamically
|
# Handle theme selection dynamically
|
||||||
if key == "theme":
|
if key == "theme":
|
||||||
# Load theme names dynamically from the JSON
|
# Load theme names dynamically from the JSON
|
||||||
theme_options = [
|
theme_options = [k.split("_", 2)[2].lower() for k in loaded_config.keys() if k.startswith("COLOR_CONFIG")]
|
||||||
k.split("_", 2)[2].lower() for k in config.loaded_config.keys() if k.startswith("COLOR_CONFIG")
|
|
||||||
]
|
|
||||||
return get_list_input("Select Theme", current_value, theme_options)
|
return get_list_input("Select Theme", current_value, theme_options)
|
||||||
|
|
||||||
elif key == "node_sort":
|
elif key == "node_sort":
|
||||||
@@ -109,7 +106,7 @@ def edit_value(key: str, current_value: str) -> str:
|
|||||||
return user_input if user_input else current_value
|
return user_input if user_input else current_value
|
||||||
|
|
||||||
|
|
||||||
def display_menu() -> tuple[Any, Any, List[str]]:
|
def display_menu(menu_state: Any) -> tuple[Any, Any, List[str]]:
|
||||||
"""
|
"""
|
||||||
Render the configuration menu with a Save button directly added to the window.
|
Render the configuration menu with a Save button directly added to the window.
|
||||||
"""
|
"""
|
||||||
@@ -214,14 +211,14 @@ def json_editor(stdscr: curses.window, menu_state: Any) -> None:
|
|||||||
menu_state.current_menu = data # Track the current level of the menu
|
menu_state.current_menu = data # Track the current level of the menu
|
||||||
|
|
||||||
# Render the menu
|
# Render the menu
|
||||||
menu_win, menu_pad, options = display_menu()
|
menu_win, menu_pad, options = display_menu(menu_state)
|
||||||
menu_state.need_redraw = True
|
need_redraw = True
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
if menu_state.need_redraw == True:
|
if need_redraw:
|
||||||
menu_win, menu_pad, options = display_menu()
|
menu_win, menu_pad, options = display_menu(menu_state)
|
||||||
menu_win.refresh()
|
menu_win.refresh()
|
||||||
menu_state.need_redraw = False
|
need_redraw = False
|
||||||
|
|
||||||
max_index = len(options) + (1 if menu_state.show_save_option else 0) - 1
|
max_index = len(options) + (1 if menu_state.show_save_option else 0) - 1
|
||||||
key = menu_win.getch()
|
key = menu_win.getch()
|
||||||
@@ -251,7 +248,7 @@ def json_editor(stdscr: curses.window, menu_state: Any) -> None:
|
|||||||
|
|
||||||
elif key in (curses.KEY_RIGHT, 10, 13): # 10 = \n, 13 = carriage return
|
elif key in (curses.KEY_RIGHT, 10, 13): # 10 = \n, 13 = carriage return
|
||||||
|
|
||||||
menu_state.need_redraw = True
|
need_redraw = True
|
||||||
menu_win.erase()
|
menu_win.erase()
|
||||||
menu_win.refresh()
|
menu_win.refresh()
|
||||||
|
|
||||||
@@ -290,18 +287,17 @@ def json_editor(stdscr: curses.window, menu_state: Any) -> None:
|
|||||||
menu_state.menu_index.pop()
|
menu_state.menu_index.pop()
|
||||||
menu_state.start_index.pop()
|
menu_state.start_index.pop()
|
||||||
menu_state.current_menu[selected_key] = new_value
|
menu_state.current_menu[selected_key] = new_value
|
||||||
menu_state.need_redraw = True
|
need_redraw = True
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# Save button selected
|
# Save button selected
|
||||||
save_json(file_path, data)
|
save_json(file_path, data)
|
||||||
stdscr.refresh()
|
stdscr.refresh()
|
||||||
# config.reload() # This isn't refreshing the file paths as expected
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
elif key in (27, curses.KEY_LEFT): # Escape or Left Arrow
|
elif key in (27, curses.KEY_LEFT): # Escape or Left Arrow
|
||||||
|
|
||||||
menu_state.need_redraw = True
|
need_redraw = True
|
||||||
menu_win.erase()
|
menu_win.erase()
|
||||||
menu_win.refresh()
|
menu_win.refresh()
|
||||||
|
|
||||||
@@ -329,7 +325,7 @@ def json_editor(stdscr: curses.window, menu_state: Any) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def save_json(file_path: str, data: Dict[str, Any]) -> None:
|
def save_json(file_path: str, data: Dict[str, Any]) -> None:
|
||||||
formatted_json = config.format_json_single_line_arrays(data)
|
formatted_json = format_json_single_line_arrays(data)
|
||||||
with open(file_path, "w", encoding="utf-8") as f:
|
with open(file_path, "w", encoding="utf-8") as f:
|
||||||
f.write(formatted_json)
|
f.write(formatted_json)
|
||||||
setup_colors(reinit=True)
|
setup_colors(reinit=True)
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
from contact.ui.ui_state import ChatUIState, InterfaceState, AppState, MenuState
|
from contact.ui.ui_state import ChatUIState, InterfaceState, AppState
|
||||||
|
|
||||||
ui_state = ChatUIState()
|
ui_state = ChatUIState()
|
||||||
interface_state = InterfaceState()
|
interface_state = InterfaceState()
|
||||||
app_state = AppState()
|
app_state = AppState()
|
||||||
menu_state = MenuState()
|
|
||||||
|
|||||||
Reference in New Issue
Block a user