mirror of https://github.com/gmbrax/Pilgrim.git
Started adding the reference system to add images
by adding a hash to each photo and also the possibility to add a reference by pressing i whilst the sidebar is on focus
This commit is contained in:
parent
2973620573
commit
d80825d3a1
|
|
@ -2,6 +2,9 @@ from typing import Optional, List
|
|||
import asyncio
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
import hashlib
|
||||
import re
|
||||
import time
|
||||
|
||||
from textual.app import ComposeResult
|
||||
from textual.screen import Screen
|
||||
|
|
@ -23,13 +26,15 @@ class EditEntryScreen(Screen):
|
|||
TITLE = "Pilgrim - Edit"
|
||||
|
||||
BINDINGS = [
|
||||
Binding("ctrl+s", "save", "Save"),
|
||||
Binding("ctrl+n", "next_entry", "Next/New Entry"),
|
||||
Binding("ctrl+b", "prev_entry", "Previous Entry"),
|
||||
Binding("ctrl+r", "rename_entry", "Rename Entry"),
|
||||
Binding("escape", "back_to_list", "Back to List"),
|
||||
Binding("f8", "toggle_sidebar", "Toggle Sidebar"),
|
||||
Binding("f9", "toggle_focus", "Focus Sidebar/Editor"),
|
||||
("ctrl+q", "quit", "Quit"),
|
||||
("ctrl+s", "save", "Save"),
|
||||
("ctrl+n", "new_entry", "New Entry"),
|
||||
("ctrl+shift+n", "next_entry", "Next Entry"),
|
||||
("ctrl+shift+p", "prev_entry", "Previous Entry"),
|
||||
("ctrl+r", "rename_entry", "Rename Entry"),
|
||||
("f8", "toggle_sidebar", "Toggle Photos"),
|
||||
("f9", "toggle_focus", "Toggle Focus"),
|
||||
("escape", "back_to_list", "Back to List"),
|
||||
]
|
||||
|
||||
def __init__(self, diary_id: int = 1):
|
||||
|
|
@ -50,6 +55,11 @@ class EditEntryScreen(Screen):
|
|||
self.sidebar_visible = False
|
||||
self.sidebar_focused = False
|
||||
self._sidebar_opened_once = False
|
||||
self._active_tooltip = None
|
||||
self._last_photo_suggestion_notification = None
|
||||
self._last_photo_suggestion_type = None
|
||||
self._active_notification = None
|
||||
self._notification_timer = None
|
||||
|
||||
# Main header
|
||||
self.header = Header(name="Pilgrim v6", classes="EditEntryScreen-header")
|
||||
|
|
@ -108,6 +118,153 @@ class EditEntryScreen(Screen):
|
|||
"""Forces footer refresh to show updated bindings"""
|
||||
self.refresh()
|
||||
|
||||
def _generate_photo_hash(self, photo: Photo) -> str:
|
||||
"""Generate a short, unique hash for a photo"""
|
||||
unique_string = f"{photo.name}_{photo.id}_{photo.addition_date}"
|
||||
hash_object = hashlib.md5(unique_string.encode())
|
||||
return hash_object.hexdigest()[:8]
|
||||
|
||||
def _fuzzy_search(self, query: str, photos: List[Photo]) -> List[Photo]:
|
||||
"""Fuzzy search for photos by name or hash"""
|
||||
if not query:
|
||||
return []
|
||||
|
||||
query = query.lower()
|
||||
results = []
|
||||
|
||||
for photo in photos:
|
||||
photo_hash = self._generate_photo_hash(photo)
|
||||
photo_name = photo.name.lower()
|
||||
|
||||
# Check if query is in name (substring match)
|
||||
if query in photo_name:
|
||||
results.append((photo, 1, f"Name match: {query} in {photo.name}"))
|
||||
continue
|
||||
|
||||
# Check if query is in hash (substring match)
|
||||
if query in photo_hash:
|
||||
results.append((photo, 2, f"Hash match: {query} in {photo_hash}"))
|
||||
continue
|
||||
|
||||
# Fuzzy match for name (check if all characters are present in order)
|
||||
if self._fuzzy_match(query, photo_name):
|
||||
results.append((photo, 3, f"Fuzzy name match: {query} in {photo.name}"))
|
||||
continue
|
||||
|
||||
# Fuzzy match for hash
|
||||
if self._fuzzy_match(query, photo_hash):
|
||||
results.append((photo, 4, f"Fuzzy hash match: {query} in {photo_hash}"))
|
||||
continue
|
||||
|
||||
# Sort by priority (lower number = higher priority)
|
||||
results.sort(key=lambda x: x[1])
|
||||
return [photo for photo, _, _ in results]
|
||||
|
||||
def _fuzzy_match(self, query: str, text: str) -> bool:
|
||||
"""Check if query characters appear in text in order (fuzzy match)"""
|
||||
if not query:
|
||||
return True
|
||||
|
||||
query_idx = 0
|
||||
for char in text:
|
||||
if query_idx < len(query) and char == query[query_idx]:
|
||||
query_idx += 1
|
||||
if query_idx == len(query):
|
||||
return True
|
||||
return False
|
||||
|
||||
def _show_photo_tooltip(self, hash_value: str, cursor_position: tuple = None):
|
||||
"""Show tooltip with photo info when typing hash"""
|
||||
# Temporarily disabled - using notifications instead
|
||||
pass
|
||||
|
||||
def _hide_tooltip(self):
|
||||
"""Hide the current tooltip"""
|
||||
# Temporarily disabled
|
||||
pass
|
||||
|
||||
def _check_hash_tooltips(self, text: str):
|
||||
"""Check for hash patterns and show tooltips"""
|
||||
# Temporarily disabled - using notifications instead
|
||||
pass
|
||||
|
||||
def _get_cursor_position(self) -> tuple:
|
||||
"""Get current cursor position for tooltip placement"""
|
||||
try:
|
||||
# Get cursor position from text area
|
||||
cursor_location = self.text_entry.cursor_location
|
||||
if cursor_location:
|
||||
# Get the text area region
|
||||
text_region = self.text_entry.region
|
||||
if text_region:
|
||||
# Calculate position relative to text area
|
||||
# Position tooltip below the current line, not over it
|
||||
x = text_region.x + min(cursor_location[0], text_region.width - 40) # Keep within bounds
|
||||
y = text_region.y + cursor_location[1] + 2 # 2 lines below cursor
|
||||
return (x, y)
|
||||
except:
|
||||
pass
|
||||
return None
|
||||
|
||||
def _update_photo_notification(self, message: str, severity: str = "info", timeout: int = 5):
|
||||
"""Update existing notification or create new one"""
|
||||
# Cancel previous timer if exists
|
||||
if self._notification_timer:
|
||||
self._notification_timer.stop()
|
||||
self._notification_timer = None
|
||||
|
||||
# Update existing notification or create new one
|
||||
if self._active_notification:
|
||||
# Try to update existing notification
|
||||
try:
|
||||
self._active_notification.update(message)
|
||||
print(f"DEBUG: Updated existing notification: {message}")
|
||||
except:
|
||||
# If update fails, create new notification
|
||||
self._active_notification = self.notify(message, severity=severity, timeout=timeout)
|
||||
print(f"DEBUG: Created new notification: {message}")
|
||||
else:
|
||||
# Create new notification
|
||||
self._active_notification = self.notify(message, severity=severity, timeout=timeout)
|
||||
print(f"DEBUG: Created new notification: {message}")
|
||||
|
||||
# Set timer to clear notification after inactivity
|
||||
self._notification_timer = self.set_timer(timeout, self._clear_photo_notification)
|
||||
|
||||
def _show_photo_suggestion(self, message: str, timeout: int = 5):
|
||||
# Temporarily disabled
|
||||
pass
|
||||
|
||||
def _hide_photo_suggestion(self):
|
||||
# Temporarily disabled
|
||||
pass
|
||||
|
||||
def _clear_photo_notification(self):
|
||||
"""Clear the active photo notification"""
|
||||
self._active_notification = None
|
||||
self._notification_timer = None
|
||||
print("DEBUG: Cleared photo notification")
|
||||
|
||||
def _resolve_photo_references(self, text: str) -> str:
|
||||
"""Resolve photo references in text to actual photo information"""
|
||||
def replace_photo_ref(match):
|
||||
name_part = match.group(1) if match.group(1) else ""
|
||||
hash_part = match.group(2)
|
||||
|
||||
photos = self._load_photos_for_diary(self.diary_id)
|
||||
|
||||
# Find photo by hash (most reliable)
|
||||
matching_photos = [p for p in photos if self._generate_photo_hash(p) == hash_part]
|
||||
|
||||
if matching_photos:
|
||||
photo = matching_photos[0]
|
||||
return f"\n[📷 {photo.name}]({photo.filepath})\n" + (f"*{photo.caption}*\n" if photo.caption else "")
|
||||
else:
|
||||
return f"\n[❌ Photo not found: hash={hash_part}]\n"
|
||||
|
||||
# Match both formats: [[photo:name:hash]] and [[photo::hash]]
|
||||
return re.sub(r'\[\[photo:([^:]*):([a-f0-9]{8})\]\]', replace_photo_ref, text)
|
||||
|
||||
def compose(self) -> ComposeResult:
|
||||
print("DEBUG: EditEntryScreen COMPOSE", getattr(self, 'sidebar_visible', None))
|
||||
yield self.header
|
||||
|
|
@ -130,6 +287,7 @@ class EditEntryScreen(Screen):
|
|||
|
||||
# Initialize footer with editor context
|
||||
self._update_footer_context()
|
||||
# self.app.mount(self._photo_suggestion_widget) # Temporarily disabled
|
||||
|
||||
def update_diary_info(self):
|
||||
"""Updates diary information"""
|
||||
|
|
@ -249,37 +407,48 @@ class EditEntryScreen(Screen):
|
|||
|
||||
def _update_sidebar_content(self):
|
||||
"""Updates the sidebar content with photos for the current diary"""
|
||||
photos = self._load_photos_for_diary(self.diary_id)
|
||||
try:
|
||||
photos = self._load_photos_for_diary(self.diary_id)
|
||||
|
||||
# Clear existing options safely
|
||||
self.photo_list.clear_options()
|
||||
# Clear existing options safely
|
||||
self.photo_list.clear_options()
|
||||
|
||||
# Add 'Ingest Photo' option at the top
|
||||
self.photo_list.add_option("➕ Ingest Photo")
|
||||
# Add 'Ingest Photo' option at the top
|
||||
self.photo_list.add_option("➕ Ingest Photo")
|
||||
|
||||
if not photos:
|
||||
self.photo_info.update("No photos found for this diary")
|
||||
self.help_text.update("📸 No photos available\n\nUse Photo Manager to add photos")
|
||||
return
|
||||
if not photos:
|
||||
self.photo_info.update("No photos found for this diary")
|
||||
self.help_text.update("📸 No photos available\n\nUse Photo Manager to add photos")
|
||||
return
|
||||
|
||||
# Add photos to the list
|
||||
for photo in photos:
|
||||
self.photo_list.add_option(f"📷 {photo.name}")
|
||||
# Add photos to the list with hash
|
||||
for photo in photos:
|
||||
# Show name and hash in the list
|
||||
photo_hash = self._generate_photo_hash(photo)
|
||||
self.photo_list.add_option(f"📷 {photo.name} \\[{photo_hash}\\]")
|
||||
|
||||
self.photo_info.update(f"📸 {len(photos)} photos in diary")
|
||||
self.photo_info.update(f"📸 {len(photos)} photos in diary")
|
||||
|
||||
# English, visually distinct help text
|
||||
help_text = (
|
||||
"[b]⌨️ Sidebar Shortcuts[/b]\n"
|
||||
"[b][green]i[/green][/b]: Insert photo into entry\n"
|
||||
"[b][green]n[/green][/b]: Add new photo\n"
|
||||
"[b][green]d[/green][/b]: Delete selected photo\n"
|
||||
"[b][green]e[/green][/b]: Edit selected photo\n"
|
||||
"[b][yellow]Tab[/yellow][/b]: Back to editor\n"
|
||||
"[b][yellow]F8[/yellow][/b]: Show/hide sidebar\n"
|
||||
"[b][yellow]F9[/yellow][/b]: Switch focus (if needed)"
|
||||
)
|
||||
self.help_text.update(help_text)
|
||||
# Updated help text with hash information
|
||||
help_text = (
|
||||
"[b]⌨️ Sidebar Shortcuts[/b]\n"
|
||||
"[b][green]i[/green][/b]: Insert photo into entry\n"
|
||||
"[b][green]n[/green][/b]: Add new photo\n"
|
||||
"[b][green]d[/green][/b]: Delete selected photo\n"
|
||||
"[b][green]e[/green][/b]: Edit selected photo\n"
|
||||
"[b][yellow]Tab[/yellow][/b]: Back to editor\n"
|
||||
"[b][yellow]F8[/yellow][/b]: Show/hide sidebar\n"
|
||||
"[b][yellow]F9[/yellow][/b]: Switch focus (if needed)\n\n"
|
||||
"[b]📝 Photo References[/b]\n"
|
||||
"Use: \\[\\[photo:name:hash\\]\\]\n"
|
||||
"Or: \\[\\[photo::hash\\]\\]"
|
||||
)
|
||||
self.help_text.update(help_text)
|
||||
except Exception as e:
|
||||
self.notify(f"Error updating sidebar: {str(e)}", severity="error")
|
||||
# Set fallback content
|
||||
self.photo_info.update("Error loading photos")
|
||||
self.help_text.update("Error loading sidebar content")
|
||||
|
||||
def _load_photos_for_diary(self, diary_id: int) -> List[Photo]:
|
||||
"""Loads all photos for the specific diary"""
|
||||
|
|
@ -297,40 +466,49 @@ class EditEntryScreen(Screen):
|
|||
|
||||
def action_toggle_sidebar(self):
|
||||
"""Toggles the sidebar visibility"""
|
||||
print("DEBUG: TOGGLE SIDEBAR", self.sidebar_visible)
|
||||
self.sidebar_visible = not self.sidebar_visible
|
||||
try:
|
||||
print("DEBUG: TOGGLE SIDEBAR", self.sidebar_visible)
|
||||
self.sidebar_visible = not self.sidebar_visible
|
||||
|
||||
if self.sidebar_visible:
|
||||
self.sidebar.display = True
|
||||
self._update_sidebar_content()
|
||||
# Automatically focus the sidebar when opening
|
||||
self.sidebar_focused = True
|
||||
self.photo_list.focus()
|
||||
# Notification when opening the sidebar for the first time
|
||||
if not self._sidebar_opened_once:
|
||||
self.notify(
|
||||
"Sidebar opened and focused! Use the shortcuts shown in the help panel.",
|
||||
severity="info"
|
||||
)
|
||||
self._sidebar_opened_once = True
|
||||
else:
|
||||
if self.sidebar_visible:
|
||||
self.sidebar.display = True
|
||||
self._update_sidebar_content()
|
||||
# Automatically focus the sidebar when opening
|
||||
self.sidebar_focused = True
|
||||
self.photo_list.focus()
|
||||
# Notification when opening the sidebar for the first time
|
||||
if not self._sidebar_opened_once:
|
||||
self.notify(
|
||||
"Sidebar opened and focused! Use the shortcuts shown in the help panel.",
|
||||
severity="info"
|
||||
)
|
||||
self._sidebar_opened_once = True
|
||||
else:
|
||||
self.sidebar.display = False
|
||||
self.sidebar_focused = False # Reset focus when hiding
|
||||
self.text_entry.focus() # Return focus to editor
|
||||
|
||||
# Update footer after context change
|
||||
self._update_footer_context()
|
||||
self.refresh(layout=True)
|
||||
except Exception as e:
|
||||
self.notify(f"Error toggling sidebar: {str(e)}", severity="error")
|
||||
# Reset state on error
|
||||
self.sidebar_visible = False
|
||||
self.sidebar_focused = False
|
||||
self.sidebar.display = False
|
||||
self.sidebar_focused = False # Reset focus when hiding
|
||||
self.text_entry.focus() # Return focus to editor
|
||||
|
||||
# Update footer after context change
|
||||
self._update_footer_context()
|
||||
self.refresh(layout=True)
|
||||
|
||||
def action_toggle_focus(self):
|
||||
"""Toggles focus between editor and sidebar"""
|
||||
print("DEBUG: TOGGLE FOCUS", self.sidebar_visible, self.sidebar_focused)
|
||||
print("DEBUG: TOGGLE FOCUS called", self.sidebar_visible, self.sidebar_focused)
|
||||
if not self.sidebar_visible:
|
||||
# If sidebar is not visible, show it and focus it
|
||||
print("DEBUG: Sidebar not visible, opening it")
|
||||
self.action_toggle_sidebar()
|
||||
return
|
||||
|
||||
self.sidebar_focused = not self.sidebar_focused
|
||||
print("DEBUG: Sidebar focused changed to", self.sidebar_focused)
|
||||
if self.sidebar_focused:
|
||||
self.photo_list.focus()
|
||||
else:
|
||||
|
|
@ -359,19 +537,36 @@ class EditEntryScreen(Screen):
|
|||
return
|
||||
|
||||
selected_photo = photos[photo_index]
|
||||
photo_hash = self._generate_photo_hash(selected_photo)
|
||||
|
||||
# Insert photo reference into text
|
||||
photo_ref = f"\n[📷 {selected_photo.name}]({selected_photo.filepath})\n"
|
||||
if selected_photo.caption:
|
||||
photo_ref += f"*{selected_photo.caption}*\n"
|
||||
# Insert photo reference using hash format without escaping
|
||||
# Using raw string to avoid markup conflicts with [[
|
||||
photo_ref = f"[[photo::{photo_hash}]]"
|
||||
|
||||
# Insert at cursor position or at end
|
||||
current_text = self.text_entry.text
|
||||
cursor_position = len(current_text) # Insert at end for now
|
||||
new_text = current_text + photo_ref
|
||||
self.text_entry.text = new_text
|
||||
# Insert at cursor position
|
||||
self.text_entry.insert(photo_ref)
|
||||
|
||||
self.notify(f"Inserted photo: {selected_photo.name}")
|
||||
# Switch focus back to editor
|
||||
self.sidebar_focused = False
|
||||
self.text_entry.focus()
|
||||
|
||||
# Update footer context
|
||||
self._update_footer_context()
|
||||
|
||||
# Show selected photo info
|
||||
photo_details = f"📷 {selected_photo.name}\n"
|
||||
photo_details += f"🔗 {photo_hash}\n"
|
||||
photo_details += f"📅 {selected_photo.addition_date}\n"
|
||||
photo_details += f"💬 {selected_photo.caption or 'No caption'}\n"
|
||||
photo_details += f"📁 {selected_photo.filepath}\n\n"
|
||||
photo_details += f"[b]Reference formats:[/b]\n"
|
||||
photo_details += f"\\[\\[photo:{selected_photo.name}:{photo_hash}\\]\\]\n"
|
||||
photo_details += f"\\[\\[photo::{photo_hash}\\]\\]"
|
||||
|
||||
self.photo_info.update(photo_details)
|
||||
|
||||
# Show notification without escaping brackets
|
||||
self.notify(f"Inserted photo: {selected_photo.name} \\[{photo_hash}\\]", severity="information")
|
||||
|
||||
def action_ingest_new_photo(self):
|
||||
"""Ingest a new photo using modal"""
|
||||
|
|
@ -564,30 +759,45 @@ class EditEntryScreen(Screen):
|
|||
"""Handles photo selection in the sidebar"""
|
||||
if not self.sidebar_visible:
|
||||
return
|
||||
# If 'Ingest Photo' is selected (always index 0)
|
||||
if event.option_index == 0:
|
||||
self.action_ingest_new_photo()
|
||||
return
|
||||
|
||||
photos = self._load_photos_for_diary(self.diary_id)
|
||||
if not photos or event.option_index <= 0: # Skip 'Ingest Photo' option
|
||||
return
|
||||
|
||||
# Adjust index because of 'Ingest Photo' at the top
|
||||
photo_index = event.option_index - 1
|
||||
if not photos or photo_index >= len(photos):
|
||||
if photo_index >= len(photos):
|
||||
return
|
||||
|
||||
selected_photo = photos[photo_index]
|
||||
self.notify(f"Selected photo: {selected_photo.name}")
|
||||
# Update photo info with details
|
||||
photo_hash = self._generate_photo_hash(selected_photo)
|
||||
self.notify(f"Selected photo: {selected_photo.name} \\[{photo_hash}\\]")
|
||||
|
||||
# Update photo info with details including hash
|
||||
photo_details = f"📷 {selected_photo.name}\n"
|
||||
photo_details += f"🔗 {photo_hash}\n"
|
||||
photo_details += f"📅 {selected_photo.addition_date}\n"
|
||||
if selected_photo.caption:
|
||||
photo_details += f"💬 {selected_photo.caption}\n"
|
||||
photo_details += f"📁 {selected_photo.filepath}"
|
||||
photo_details += f"📁 {selected_photo.filepath}\n\n"
|
||||
photo_details += f"[b]Reference formats:[/b]\n"
|
||||
photo_details += f"\\[\\[photo:{selected_photo.name}:{photo_hash}\\]\\]\n"
|
||||
photo_details += f"\\[\\[photo::{photo_hash}\\]\\]"
|
||||
|
||||
self.photo_info.update(photo_details)
|
||||
|
||||
def on_text_area_changed(self, event) -> None:
|
||||
"""Detects text changes to mark as unsaved"""
|
||||
"""Detects text changes and shows photo tooltips"""
|
||||
if (hasattr(self, 'text_entry') and not self.text_entry.read_only and
|
||||
not getattr(self, '_updating_display', False) and hasattr(self, '_original_content')):
|
||||
current_content = self.text_entry.text
|
||||
|
||||
# Check for hash patterns and show tooltips
|
||||
self._check_hash_tooltips(current_content)
|
||||
|
||||
# Check for photo reference pattern
|
||||
# self._check_photo_reference(current_content) # Temporarily disabled
|
||||
|
||||
if current_content != self._original_content:
|
||||
if not self.has_unsaved_changes:
|
||||
self.has_unsaved_changes = True
|
||||
|
|
@ -800,18 +1010,24 @@ class EditEntryScreen(Screen):
|
|||
self.notify(f"Error updating entry: {str(e)}")
|
||||
|
||||
def on_key(self, event):
|
||||
print("DEBUG: on_key called with", event.key, "sidebar_focused:", self.sidebar_focused, "sidebar_visible:", self.sidebar_visible)
|
||||
# Sidebar contextual shortcuts
|
||||
if self.sidebar_focused and self.sidebar_visible:
|
||||
print("DEBUG: Processing sidebar shortcut for key:", event.key)
|
||||
if event.key == "i":
|
||||
print("DEBUG: Calling action_insert_photo")
|
||||
self.action_insert_photo()
|
||||
event.stop()
|
||||
elif event.key == "n":
|
||||
print("DEBUG: Calling action_ingest_new_photo")
|
||||
self.action_ingest_new_photo()
|
||||
event.stop()
|
||||
elif event.key == "d":
|
||||
print("DEBUG: Calling action_delete_photo")
|
||||
self.action_delete_photo()
|
||||
event.stop()
|
||||
elif event.key == "e":
|
||||
print("DEBUG: Calling action_edit_photo")
|
||||
self.action_edit_photo()
|
||||
event.stop()
|
||||
# Shift+Tab: remove indent
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ from textual.screen import Screen
|
|||
from textual.widgets import Static, Input, Button
|
||||
from textual.containers import Horizontal, Container
|
||||
from .file_picker_modal import FilePickerModal
|
||||
import hashlib
|
||||
|
||||
class AddPhotoModal(Screen):
|
||||
"""Modal for adding a new photo"""
|
||||
|
|
@ -12,6 +13,14 @@ class AddPhotoModal(Screen):
|
|||
super().__init__()
|
||||
self.diary_id = diary_id
|
||||
self.result = None
|
||||
self.created_photo = None
|
||||
|
||||
def _generate_photo_hash(self, photo_data: dict) -> str:
|
||||
"""Generate a short, unique hash for a photo"""
|
||||
# Use temporary data for hash generation
|
||||
unique_string = f"{photo_data['name']}_{photo_data.get('photo_id', 0)}_new"
|
||||
hash_object = hashlib.md5(unique_string.encode())
|
||||
return hash_object.hexdigest()[:8]
|
||||
|
||||
def compose(self) -> ComposeResult:
|
||||
yield Container(
|
||||
|
|
@ -72,13 +81,23 @@ class AddPhotoModal(Screen):
|
|||
)
|
||||
|
||||
if new_photo:
|
||||
self.notify(f"Photo '{new_photo.name}' added successfully!")
|
||||
self.created_photo = new_photo
|
||||
# Generate hash for the new photo
|
||||
photo_hash = self._generate_photo_hash({
|
||||
"name": new_photo.name,
|
||||
"photo_id": new_photo.id
|
||||
})
|
||||
|
||||
self.notify(f"Photo '{new_photo.name}' added successfully!\nHash: {photo_hash}\nReference: \\[\\[photo:{new_photo.name}:{photo_hash}\\]\\]",
|
||||
severity="information", timeout=5)
|
||||
|
||||
# Return the created photo data to the calling screen
|
||||
self.result = {
|
||||
"filepath": photo_data["filepath"],
|
||||
"name": photo_data["name"],
|
||||
"caption": photo_data["caption"],
|
||||
"photo_id": new_photo.id
|
||||
"photo_id": new_photo.id,
|
||||
"hash": photo_hash
|
||||
}
|
||||
self.dismiss(self.result)
|
||||
else:
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ from textual.screen import Screen
|
|||
from textual.widgets import Static, Input, Button
|
||||
from textual.containers import Container, Horizontal
|
||||
from pilgrim.models.photo import Photo
|
||||
import hashlib
|
||||
|
||||
class EditPhotoModal(Screen):
|
||||
"""Modal for editing an existing photo (name and caption only)"""
|
||||
|
|
@ -11,7 +12,16 @@ class EditPhotoModal(Screen):
|
|||
self.photo = photo
|
||||
self.result = None
|
||||
|
||||
def _generate_photo_hash(self, photo: Photo) -> str:
|
||||
"""Generate a short, unique hash for a photo"""
|
||||
unique_string = f"{photo.name}_{photo.id}_{photo.addition_date}"
|
||||
hash_object = hashlib.md5(unique_string.encode())
|
||||
return hash_object.hexdigest()[:8]
|
||||
|
||||
def compose(self) -> ComposeResult:
|
||||
# Generate hash for this photo
|
||||
photo_hash = self._generate_photo_hash(self.photo)
|
||||
|
||||
yield Container(
|
||||
Static("✏️ Edit Photo", classes="EditPhotoModal-Title"),
|
||||
Static("File path (read-only):", classes="EditPhotoModal-Label"),
|
||||
|
|
@ -35,6 +45,10 @@ class EditPhotoModal(Screen):
|
|||
id="caption-input",
|
||||
classes="EditPhotoModal-Input"
|
||||
),
|
||||
Static(f"🔗 Photo Hash: {photo_hash}", classes="EditPhotoModal-Hash"),
|
||||
Static("Reference formats:", classes="EditPhotoModal-Label"),
|
||||
Static(f"\\[\\[photo:{self.photo.name}:{photo_hash}\\]\\]", classes="EditPhotoModal-Reference"),
|
||||
Static(f"\\[\\[photo::{photo_hash}\\]\\]", classes="EditPhotoModal-Reference"),
|
||||
Horizontal(
|
||||
Button("Save Changes", id="save-button", classes="EditPhotoModal-Button"),
|
||||
Button("Cancel", id="cancel-button", classes="EditPhotoModal-Button"),
|
||||
|
|
|
|||
Loading…
Reference in New Issue