Files
Browser/ren_browser/ui/settings.py
Ivan 3cddaeb2b9 Update:
1. Add basic Micron parser and link support
2. Improve styling/layout
3. Add hot reloading for RNS
2025-11-16 00:34:51 -06:00

462 lines
15 KiB
Python

"""Settings interface for Ren Browser.
Provides configuration management, log viewing, and storage
information display.
"""
import flet as ft
from ren_browser.logs import ERROR_LOGS, RET_LOGS
from ren_browser.storage.storage import get_storage_manager
def open_settings_tab(page: ft.Page, tab_manager):
"""Open a settings tab with configuration and debugging options.
Args:
page: Flet page instance for UI updates.
tab_manager: Tab manager to add the settings tab to.
"""
storage = get_storage_manager(page)
try:
config_text = storage.load_config()
except Exception as ex:
config_text = f"Error reading config: {ex}"
app_settings = storage.load_app_settings()
config_field = ft.TextField(
label="Reticulum Configuration",
value=config_text,
expand=True,
multiline=True,
min_lines=15,
max_lines=20,
border_color=ft.Colors.GREY_700,
focused_border_color=ft.Colors.BLUE_400,
text_style=ft.TextStyle(font_family="monospace", size=12),
)
horizontal_scroll_switch = ft.Switch(
label="Enable Horizontal Scroll (preserve ASCII art)",
value=app_settings.get("horizontal_scroll", False),
)
page_bgcolor_field = ft.TextField(
label="Page Background Color (hex)",
value=app_settings.get("page_bgcolor", "#000000"),
hint_text="#000000",
width=200,
border_color=ft.Colors.GREY_700,
focused_border_color=ft.Colors.BLUE_400,
)
color_preview = ft.Container(
width=40,
height=40,
bgcolor=app_settings.get("page_bgcolor", "#000000"),
border_radius=8,
border=ft.border.all(1, ft.Colors.GREY_700),
)
def on_bgcolor_change(e):
try:
color_preview.bgcolor = page_bgcolor_field.value
page.update()
except Exception:
pass
page_bgcolor_field.on_change = on_bgcolor_change
def on_save_config(ev):
try:
success = storage.save_config(config_field.value)
if success:
snack = ft.SnackBar(
content=ft.Row(
controls=[
ft.Icon(ft.Icons.CHECK_CIRCLE, color=ft.Colors.GREEN_400, size=20),
ft.Text("Configuration saved! Restart app to apply changes.", color=ft.Colors.WHITE),
],
tight=True,
),
bgcolor=ft.Colors.GREEN_900,
duration=3000,
)
page.overlay.append(snack)
snack.open = True
page.update()
else:
snack = ft.SnackBar(
content=ft.Row(
controls=[
ft.Icon(ft.Icons.ERROR, color=ft.Colors.RED_400, size=20),
ft.Text("Failed to save configuration", color=ft.Colors.WHITE),
],
tight=True,
),
bgcolor=ft.Colors.RED_900,
duration=3000,
)
page.overlay.append(snack)
snack.open = True
page.update()
except Exception as ex:
snack = ft.SnackBar(
content=ft.Row(
controls=[
ft.Icon(ft.Icons.ERROR, color=ft.Colors.RED_400, size=20),
ft.Text(f"Error: {ex}", color=ft.Colors.WHITE),
],
tight=True,
),
bgcolor=ft.Colors.RED_900,
duration=4000,
)
page.overlay.append(snack)
snack.open = True
page.update()
def on_save_and_reload_config(ev):
try:
success = storage.save_config(config_field.value)
if not success:
snack = ft.SnackBar(
content=ft.Row(
controls=[
ft.Icon(ft.Icons.ERROR, color=ft.Colors.RED_400, size=20),
ft.Text("Failed to save configuration", color=ft.Colors.WHITE),
],
tight=True,
),
bgcolor=ft.Colors.RED_900,
duration=3000,
)
page.overlay.append(snack)
snack.open = True
page.update()
return
loading_snack = ft.SnackBar(
content=ft.Row(
controls=[
ft.ProgressRing(width=16, height=16, stroke_width=2, color=ft.Colors.BLUE_400),
ft.Text("Reloading Reticulum...", color=ft.Colors.WHITE),
],
tight=True,
),
bgcolor=ft.Colors.BLUE_900,
duration=10000,
)
page.overlay.append(loading_snack)
loading_snack.open = True
page.update()
def on_reload_complete(success, error):
loading_snack.open = False
page.update()
if success:
snack = ft.SnackBar(
content=ft.Row(
controls=[
ft.Icon(ft.Icons.CHECK_CIRCLE, color=ft.Colors.GREEN_400, size=20),
ft.Text("Reticulum reloaded successfully!", color=ft.Colors.WHITE),
],
tight=True,
),
bgcolor=ft.Colors.GREEN_900,
duration=3000,
)
else:
snack = ft.SnackBar(
content=ft.Row(
controls=[
ft.Icon(ft.Icons.ERROR, color=ft.Colors.RED_400, size=20),
ft.Text(f"Reload failed: {error}", color=ft.Colors.WHITE),
],
tight=True,
),
bgcolor=ft.Colors.RED_900,
duration=4000,
)
page.overlay.append(snack)
snack.open = True
page.update()
import ren_browser.app as app_module
app_module.reload_reticulum(page, on_reload_complete)
except Exception as ex:
snack = ft.SnackBar(
content=ft.Row(
controls=[
ft.Icon(ft.Icons.ERROR, color=ft.Colors.RED_400, size=20),
ft.Text(f"Error: {ex}", color=ft.Colors.WHITE),
],
tight=True,
),
bgcolor=ft.Colors.RED_900,
duration=4000,
)
page.overlay.append(snack)
snack.open = True
page.update()
def on_save_app_settings(ev):
try:
new_settings = {
"horizontal_scroll": horizontal_scroll_switch.value,
"page_bgcolor": page_bgcolor_field.value,
}
success = storage.save_app_settings(new_settings)
if success:
tab_manager.apply_settings(new_settings)
snack = ft.SnackBar(
content=ft.Row(
controls=[
ft.Icon(ft.Icons.CHECK_CIRCLE, color=ft.Colors.GREEN_400, size=20),
ft.Text("Appearance settings saved and applied!", color=ft.Colors.WHITE),
],
tight=True,
),
bgcolor=ft.Colors.GREEN_900,
duration=2000,
)
page.overlay.append(snack)
snack.open = True
page.update()
else:
snack = ft.SnackBar(
content=ft.Row(
controls=[
ft.Icon(ft.Icons.ERROR, color=ft.Colors.RED_400, size=20),
ft.Text("Failed to save appearance settings", color=ft.Colors.WHITE),
],
tight=True,
),
bgcolor=ft.Colors.RED_900,
duration=3000,
)
page.overlay.append(snack)
snack.open = True
page.update()
except Exception as ex:
snack = ft.SnackBar(
content=ft.Row(
controls=[
ft.Icon(ft.Icons.ERROR, color=ft.Colors.RED_400, size=20),
ft.Text(f"Error: {ex}", color=ft.Colors.WHITE),
],
tight=True,
),
bgcolor=ft.Colors.RED_900,
duration=4000,
)
page.overlay.append(snack)
snack.open = True
page.update()
save_btn = ft.ElevatedButton(
"Save Configuration",
icon=ft.Icons.SAVE,
on_click=on_save_config,
bgcolor=ft.Colors.BLUE_700,
color=ft.Colors.WHITE,
)
save_reload_btn = ft.ElevatedButton(
"Save & Hot Reload",
icon=ft.Icons.REFRESH,
on_click=on_save_and_reload_config,
bgcolor=ft.Colors.GREEN_700,
color=ft.Colors.WHITE,
)
save_appearance_btn = ft.ElevatedButton(
"Save Appearance",
icon=ft.Icons.PALETTE,
on_click=on_save_app_settings,
bgcolor=ft.Colors.BLUE_700,
color=ft.Colors.WHITE,
)
error_field = ft.TextField(
label="Error Logs",
value="",
expand=True,
multiline=True,
read_only=True,
min_lines=15,
max_lines=20,
border_color=ft.Colors.GREY_700,
text_style=ft.TextStyle(font_family="monospace", size=12),
)
ret_field = ft.TextField(
label="Reticulum Logs",
value="",
expand=True,
multiline=True,
read_only=True,
min_lines=15,
max_lines=20,
border_color=ft.Colors.GREY_700,
text_style=ft.TextStyle(font_family="monospace", size=12),
)
storage_info = storage.get_storage_info()
storage_text = "\n".join([f"{key}: {value}" for key, value in storage_info.items()])
storage_field = ft.TextField(
label="Storage Information",
value=storage_text,
expand=True,
multiline=True,
read_only=True,
min_lines=10,
max_lines=15,
border_color=ft.Colors.GREY_700,
text_style=ft.TextStyle(font_family="monospace", size=12),
)
content_placeholder = ft.Container(expand=True)
appearance_content = ft.Column(
spacing=16,
controls=[
ft.Text("Appearance Settings", size=18, weight=ft.FontWeight.BOLD),
horizontal_scroll_switch,
ft.Row(
controls=[
page_bgcolor_field,
color_preview,
],
alignment=ft.MainAxisAlignment.START,
spacing=16,
),
save_appearance_btn,
],
)
def show_config(ev):
content_placeholder.content = config_field
page.update()
def show_appearance(ev):
content_placeholder.content = appearance_content
page.update()
def show_errors(ev):
error_field.value = "\n".join(ERROR_LOGS) or "No errors logged."
content_placeholder.content = error_field
page.update()
def show_ret_logs(ev):
ret_field.value = "\n".join(RET_LOGS) or "No Reticulum logs."
content_placeholder.content = ret_field
page.update()
def show_storage_info(ev):
storage_info = storage.get_storage_info()
storage_field.value = "\n".join(
[f"{key}: {value}" for key, value in storage_info.items()],
)
content_placeholder.content = storage_field
page.update()
def refresh_current_view(ev):
if content_placeholder.content == error_field:
show_errors(ev)
elif content_placeholder.content == ret_field:
show_ret_logs(ev)
elif content_placeholder.content == storage_field:
show_storage_info(ev)
elif content_placeholder.content == appearance_content:
show_appearance(ev)
elif content_placeholder.content == config_field:
show_config(ev)
btn_config = ft.FilledButton(
"Configuration",
icon=ft.Icons.SETTINGS,
on_click=show_config,
)
btn_appearance = ft.FilledButton(
"Appearance",
icon=ft.Icons.PALETTE,
on_click=show_appearance,
)
btn_errors = ft.FilledButton(
"Errors",
icon=ft.Icons.ERROR_OUTLINE,
on_click=show_errors,
)
btn_ret = ft.FilledButton(
"Reticulum Logs",
icon=ft.Icons.TERMINAL,
on_click=show_ret_logs,
)
btn_storage = ft.FilledButton(
"Storage",
icon=ft.Icons.STORAGE,
on_click=show_storage_info,
)
btn_refresh = ft.IconButton(
icon=ft.Icons.REFRESH,
tooltip="Refresh",
on_click=refresh_current_view,
icon_color=ft.Colors.BLUE_400,
)
nav_card = ft.Container(
content=ft.Row(
controls=[btn_config, btn_appearance, btn_errors, btn_ret, btn_storage, btn_refresh],
spacing=8,
wrap=True,
),
padding=ft.padding.all(16),
border_radius=12,
bgcolor=ft.Colors.GREY_900,
)
content_card = ft.Container(
content=content_placeholder,
expand=True,
padding=ft.padding.all(16),
border_radius=12,
bgcolor=ft.Colors.GREY_900,
)
action_row = ft.Container(
content=ft.Row(
controls=[save_btn, save_reload_btn],
alignment=ft.MainAxisAlignment.END,
spacing=8,
),
padding=ft.padding.symmetric(horizontal=16, vertical=8),
)
content_placeholder.content = config_field
settings_content = ft.Column(
expand=True,
spacing=16,
controls=[
ft.Container(
content=ft.Text(
"Settings",
size=24,
weight=ft.FontWeight.BOLD,
color=ft.Colors.BLUE_400,
),
padding=ft.padding.only(left=16, top=16),
),
nav_card,
content_card,
action_row,
],
)
tab_manager._add_tab_internal("Settings", settings_content)
idx = len(tab_manager.manager.tabs) - 1
tab_manager.select_tab(idx)
page.update()