140 lines
4.9 KiB
Python
140 lines
4.9 KiB
Python
|
|
"""Modal for displaying files/URLs to access in web mode."""
|
||
|
|
|
||
|
|
from textual.screen import ModalScreen
|
||
|
|
from textual.containers import Container, Vertical, Horizontal
|
||
|
|
from textual.widgets import Static, Button, Label
|
||
|
|
from textual.app import ComposeResult
|
||
|
|
import logging
|
||
|
|
|
||
|
|
logger = logging.getLogger(__name__)
|
||
|
|
|
||
|
|
|
||
|
|
class AccessModal(ModalScreen):
|
||
|
|
"""Modal to display a file/URL that can be accessed from phone browser."""
|
||
|
|
|
||
|
|
CSS = """
|
||
|
|
Screen {
|
||
|
|
align: center middle;
|
||
|
|
}
|
||
|
|
|
||
|
|
#access-container {
|
||
|
|
width: 80;
|
||
|
|
height: auto;
|
||
|
|
border: thick $primary;
|
||
|
|
background: $surface;
|
||
|
|
}
|
||
|
|
|
||
|
|
#access-header {
|
||
|
|
dock: top;
|
||
|
|
height: 3;
|
||
|
|
background: $boost;
|
||
|
|
border-bottom: solid $accent;
|
||
|
|
content-align: center middle;
|
||
|
|
}
|
||
|
|
|
||
|
|
#access-content {
|
||
|
|
height: auto;
|
||
|
|
width: 1fr;
|
||
|
|
padding: 1 2;
|
||
|
|
border-bottom: solid $accent;
|
||
|
|
}
|
||
|
|
|
||
|
|
#access-footer {
|
||
|
|
dock: bottom;
|
||
|
|
height: 3;
|
||
|
|
background: $boost;
|
||
|
|
border-top: solid $accent;
|
||
|
|
align: center middle;
|
||
|
|
}
|
||
|
|
|
||
|
|
.access-url {
|
||
|
|
width: 1fr;
|
||
|
|
height: auto;
|
||
|
|
margin-bottom: 1;
|
||
|
|
border: solid $accent;
|
||
|
|
padding: 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
.access-label {
|
||
|
|
width: 1fr;
|
||
|
|
height: auto;
|
||
|
|
margin-bottom: 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
Button {
|
||
|
|
margin-right: 1;
|
||
|
|
}
|
||
|
|
"""
|
||
|
|
|
||
|
|
def __init__(self, title: str, content: str, is_url: bool = False):
|
||
|
|
"""Initialize access modal.
|
||
|
|
|
||
|
|
Args:
|
||
|
|
title: Title of the item being accessed
|
||
|
|
content: The URL or file path
|
||
|
|
is_url: Whether this is a URL (True) or file path (False)
|
||
|
|
"""
|
||
|
|
super().__init__()
|
||
|
|
self.item_title = title
|
||
|
|
self.item_content = content
|
||
|
|
self.is_url = is_url
|
||
|
|
|
||
|
|
def compose(self) -> ComposeResult:
|
||
|
|
"""Create the modal layout."""
|
||
|
|
with Container(id="access-container"):
|
||
|
|
with Vertical(id="access-header"):
|
||
|
|
yield Label(f"[bold]{self.item_title}[/bold]")
|
||
|
|
yield Label("[dim]Click link below to open in your browser[/dim]")
|
||
|
|
|
||
|
|
with Vertical(id="access-content"):
|
||
|
|
if self.is_url:
|
||
|
|
yield Label("[bold cyan]Link:[/bold cyan]", classes="access-label")
|
||
|
|
else:
|
||
|
|
yield Label("[bold cyan]File:[/bold cyan]", classes="access-label")
|
||
|
|
|
||
|
|
# Display as clickable link using HTML link element for web mode
|
||
|
|
# Rich link markup `[link=URL]` has parsing issues with URLs containing special chars
|
||
|
|
# Instead, use the HTML link markup that Textual-serve renders as <a> tag
|
||
|
|
# Format: [link=URL "tooltip"]text[/link] - the quotes help with parsing
|
||
|
|
link_text = f'[link="{self.item_content}"]Open in Browser[/link]'
|
||
|
|
content_box = Static(link_text, classes="access-url")
|
||
|
|
yield content_box
|
||
|
|
|
||
|
|
# Also show the URL for reference/copying
|
||
|
|
yield Label(self.item_content, classes="access-label")
|
||
|
|
|
||
|
|
yield Label("\n[yellow]↑ Click the link above to open on your device[/yellow]", classes="access-label")
|
||
|
|
|
||
|
|
with Horizontal(id="access-footer"):
|
||
|
|
yield Button("Copy URL", id="copy-btn", variant="primary")
|
||
|
|
yield Button("Close", id="close-btn", variant="default")
|
||
|
|
|
||
|
|
def on_button_pressed(self, event: Button.Pressed) -> None:
|
||
|
|
"""Handle button presses."""
|
||
|
|
if event.button.id == "copy-btn":
|
||
|
|
# Copy to clipboard (optional - not critical if fails)
|
||
|
|
logger.info(f"Attempting to copy: {self.item_content}")
|
||
|
|
try:
|
||
|
|
# Try to use pyperclip if available
|
||
|
|
try:
|
||
|
|
import pyperclip
|
||
|
|
pyperclip.copy(self.item_content)
|
||
|
|
logger.info("URL copied to clipboard via pyperclip")
|
||
|
|
except ImportError:
|
||
|
|
# Fallback: try xclip on Linux or pbcopy on Mac
|
||
|
|
import subprocess
|
||
|
|
import sys
|
||
|
|
if sys.platform == "win32":
|
||
|
|
# Windows: use clipboard via pyperclip (already tried)
|
||
|
|
logger.debug("Windows clipboard not available without pyperclip")
|
||
|
|
else:
|
||
|
|
# Linux/Mac
|
||
|
|
process = subprocess.Popen(['xclip', '-selection', 'clipboard'], stdin=subprocess.PIPE)
|
||
|
|
process.communicate(self.item_content.encode('utf-8'))
|
||
|
|
logger.info("URL copied to clipboard via xclip")
|
||
|
|
except Exception as e:
|
||
|
|
logger.debug(f"Clipboard copy not available: {e}")
|
||
|
|
# Not critical - just informational
|
||
|
|
elif event.button.id == "close-btn":
|
||
|
|
self.dismiss()
|