From 12edb4fdfe726069cfb568f5901dca47c320adf2 Mon Sep 17 00:00:00 2001 From: Sudo-Ivan Date: Wed, 28 May 2025 22:11:14 -0500 Subject: [PATCH] Add shortcuts, syntax fixes and enhancements. --- ren_browser/announces/announces.py | 12 ++++---- ren_browser/app.py | 8 ++++-- ren_browser/controls/shortcuts.py | 40 +++++++++++++++++++++++++++ ren_browser/pages/page_request.py | 11 +++++--- ren_browser/profiler/profiler.py | 2 +- ren_browser/renderer/micron/micron.py | 6 ++-- ren_browser/storage/storage.py | 2 +- ren_browser/tabs/tabs.py | 7 +++-- ren_browser/ui/ui.py | 15 +++++++--- 9 files changed, 80 insertions(+), 23 deletions(-) create mode 100644 ren_browser/controls/shortcuts.py diff --git a/ren_browser/announces/announces.py b/ren_browser/announces/announces.py index 35c86e0..734c88f 100644 --- a/ren_browser/announces/announces.py +++ b/ren_browser/announces/announces.py @@ -1,12 +1,13 @@ -import RNS import time from dataclasses import dataclass -from typing import Optional, List + +import RNS + @dataclass class Announce: destination_hash: str - display_name: Optional[str] + display_name: str | None timestamp: int class AnnounceService: @@ -14,11 +15,12 @@ class AnnounceService: Service to listen for Reticulum announces and collect them. Calls update_callback whenever a new announce is received. """ + def __init__(self, update_callback): # Accept all announce aspects self.aspect_filter = "nomadnetwork.node" self.receive_path_responses = True - self.announces: List[Announce] = [] + self.announces: list[Announce] = [] self.update_callback = update_callback # Initialize Reticulum transport once try: @@ -48,6 +50,6 @@ class AnnounceService: if self.update_callback: self.update_callback(self.announces) - def get_announces(self) -> List[Announce]: + def get_announces(self) -> list[Announce]: """Return collected announces.""" return self.announces diff --git a/ren_browser/app.py b/ren_browser/app.py index b4af46c..02989a8 100644 --- a/ren_browser/app.py +++ b/ren_browser/app.py @@ -1,8 +1,10 @@ -import flet as ft -from flet import Page, AppView import argparse import subprocess import sys + +import flet as ft +from flet import AppView, Page + from ren_browser.ui.ui import build_ui # Current renderer name @@ -68,4 +70,4 @@ def android_dev(): def ios_dev(): """Launch Ren Browser in iOS mode via Flet CLI with hot reload.""" rc = subprocess.call(["flet", "run", "--ios", "-d", "-r", "ren_browser/app.py"]) - sys.exit(rc) \ No newline at end of file + sys.exit(rc) diff --git a/ren_browser/controls/shortcuts.py b/ren_browser/controls/shortcuts.py new file mode 100644 index 0000000..f5c8be0 --- /dev/null +++ b/ren_browser/controls/shortcuts.py @@ -0,0 +1,40 @@ +import flet as ft + + +class Shortcuts: + def __init__(self, page: ft.Page, tab_manager): + """Attach keyboard event handler to page and delegate actions to tab_manager and UI.""" + self.page = page + self.tab_manager = tab_manager + page.on_keyboard_event = self.on_keyboard + + def on_keyboard(self, e: ft.KeyboardEvent): + # Support Ctrl (and Meta on macOS) + ctrl = e.ctrl or e.meta + if not ctrl: + return + key = e.key + # New tab: Ctrl+T + if key.lower() == "t": + self.tab_manager._on_add_click(None) + # Close tab: Ctrl+W + elif key.lower() == "w": + self.tab_manager._on_close_click(None) + # Focus URL bar: Ctrl+L + elif key.lower() == "l": + idx = self.tab_manager.manager.index + field = self.tab_manager.manager.tabs[idx]["url_field"] + field.focus() + # Show announces drawer: Ctrl+A + elif key.lower() == "a": + self.page.drawer.open = True + # Cycle through tabs: Ctrl+Tab / Ctrl+Shift+Tab + elif key == "Tab": + idx = self.tab_manager.manager.index + count = len(self.tab_manager.manager.tabs) + new_idx = (idx - 1) % count if e.shift else (idx + 1) % count + self.tab_manager.select_tab(new_idx) + else: + return + # Apply UI updates + self.page.update() diff --git a/ren_browser/pages/page_request.py b/ren_browser/pages/page_request.py index 48242a9..d9e8940 100644 --- a/ren_browser/pages/page_request.py +++ b/ren_browser/pages/page_request.py @@ -1,17 +1,20 @@ -from typing import Optional, Dict -from pydantic import BaseModel -import os, time, threading +import threading +import time + import RNS +from pydantic import BaseModel + class PageRequest(BaseModel): destination_hash: str page_path: str - field_data: Optional[Dict] = None + field_data: dict | None = None class PageFetcher: """ Fetcher to download pages from the Reticulum network. """ + def __init__(self): # Initialize Reticulum with default config (singleton) try: diff --git a/ren_browser/profiler/profiler.py b/ren_browser/profiler/profiler.py index cb2612b..17b803e 100644 --- a/ren_browser/profiler/profiler.py +++ b/ren_browser/profiler/profiler.py @@ -1 +1 @@ -# Add a profiler to the browser. \ No newline at end of file +# Add a profiler to the browser. diff --git a/ren_browser/renderer/micron/micron.py b/ren_browser/renderer/micron/micron.py index baa0603..a5d50c5 100644 --- a/ren_browser/renderer/micron/micron.py +++ b/ren_browser/renderer/micron/micron.py @@ -1,8 +1,8 @@ import flet as ft + def render_micron(content: str) -> ft.Control: - """ - Render micron markup content to a Flet control placeholder. + """Render micron markup content to a Flet control placeholder. Currently displays raw content. """ return ft.Text( @@ -10,4 +10,4 @@ def render_micron(content: str) -> ft.Control: selectable=True, font_family="monospace", expand=True, - ) \ No newline at end of file + ) diff --git a/ren_browser/storage/storage.py b/ren_browser/storage/storage.py index 18c5f28..4b2cb56 100644 --- a/ren_browser/storage/storage.py +++ b/ren_browser/storage/storage.py @@ -1 +1 @@ -# Add storage system/management, eg handling downloading files, saving bookmarks, caching, tabs and history. \ No newline at end of file +# Add storage system/management, eg handling downloading files, saving bookmarks, caching, tabs and history. diff --git a/ren_browser/tabs/tabs.py b/ren_browser/tabs/tabs.py index 78afcf7..ebe3153 100644 --- a/ren_browser/tabs/tabs.py +++ b/ren_browser/tabs/tabs.py @@ -1,7 +1,10 @@ -import flet as ft from types import SimpleNamespace -from ren_browser.renderer.plaintext.plaintext import render_plaintext + +import flet as ft + from ren_browser.renderer.micron.micron import render_micron +from ren_browser.renderer.plaintext.plaintext import render_plaintext + class TabsManager: def __init__(self, page: ft.Page): diff --git a/ren_browser/ui/ui.py b/ren_browser/ui/ui.py index ce64d7e..9f999e2 100644 --- a/ren_browser/ui/ui.py +++ b/ren_browser/ui/ui.py @@ -1,10 +1,12 @@ import flet as ft from flet import Page -from ren_browser.tabs.tabs import TabsManager -from ren_browser.renderer.plaintext.plaintext import render_plaintext -from ren_browser.renderer.micron.micron import render_micron + from ren_browser.announces.announces import AnnounceService +from ren_browser.controls.shortcuts import Shortcuts from ren_browser.pages.page_request import PageFetcher, PageRequest +from ren_browser.renderer.micron.micron import render_micron +from ren_browser.renderer.plaintext.plaintext import render_plaintext +from ren_browser.tabs.tabs import TabsManager def build_ui(page: Page): @@ -45,7 +47,11 @@ def build_ui(page: Page): result = page_fetcher.fetch_page(req) except Exception as ex: result = f"Error: {ex}" - tab = tab_manager.manager.tabs[idx] + # Skip update if tab has been closed or index out of range + try: + tab = tab_manager.manager.tabs[idx] + except IndexError: + return # Use micron renderer for .mu pages, fallback to plaintext if req.page_path.endswith(".mu"): new_control = render_micron(result) @@ -78,6 +84,7 @@ def build_ui(page: Page): # Dynamic tabs manager for pages tab_manager = TabsManager(page) + Shortcuts(page, tab_manager) # Main area: tab bar and content main_area = ft.Column( expand=True,