mirror of https://github.com/gmbrax/Pilgrim.git
Added the edit_diary_modal.py and new_diary_modal.py and changed the diary_list_screen.py to be equal to the gist
This commit is contained in:
parent
4b301d0d7e
commit
dc8b5016f3
|
|
@ -1,146 +1,286 @@
|
|||
from typing import Optional, Tuple
|
||||
import asyncio
|
||||
|
||||
from textual.app import ComposeResult
|
||||
from textual.screen import Screen
|
||||
from textual.widgets import Header, Footer, Label, Static, OptionList, Button
|
||||
from textual.binding import Binding
|
||||
from textual.containers import Vertical, Container, Horizontal
|
||||
|
||||
from pilgrim.models.travel_diary import TravelDiary
|
||||
from pilgrim.ui.screens.edit_diary_modal import EditDiaryModal
|
||||
from pilgrim.ui.screens.new_diary_modal import NewDiaryModal
|
||||
|
||||
|
||||
class DiaryListScreen(Screen):
|
||||
TITLE = "Pilgrim - Main"
|
||||
|
||||
BINDINGS = [
|
||||
Binding("n", "new_diary", "Novo Diário"),
|
||||
Binding("^q", "quit", "Sair"),
|
||||
Binding("n", "new_diary", "New diary"),
|
||||
Binding("^q", "quit", "Quit Pilgrim"),
|
||||
Binding("enter", "open_selected_diary", "Open diary"),
|
||||
Binding("e", "edit_selected_diary", "Edit diary"),
|
||||
Binding("r", "force_refresh", "Force refresh"),
|
||||
]
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.selected_diary_index = None # Armazena o índice do diário selecionado
|
||||
self.selected_diary_index = None
|
||||
self.diary_id_map = {}
|
||||
self.is_refreshing = False
|
||||
|
||||
self.header = Header()
|
||||
self.footer = Footer()
|
||||
self.diary_list = OptionList(classes="DiaryListScreen-DiaryListOptions")
|
||||
self.new_diary_button = Button("New diary", id="new_diary", classes="DiaryListScreen-NewDiaryButton")
|
||||
self.edit_diary_button = Button("Edit diary", id="edit_diary", classes="DiaryListScreen-EditDiaryButton")
|
||||
self.open_diary = Button("Open diary", id="open_diary", classes="DiaryListScreen-OpenDiaryButton")
|
||||
self.buttons_grid = Horizontal(
|
||||
self.new_diary_button, self.edit_diary_button, self.open_diary,
|
||||
classes="DiaryListScreen-ButtonsGrid"
|
||||
)
|
||||
self.tips = Static(
|
||||
"Tip: use ↑↓ to navigate • ENTER to Select • "
|
||||
"TAB to alternate the fields • SHIFT + TAB to alternate back • "
|
||||
"Ctrl+P for command palette • R to force refresh",
|
||||
classes="DiaryListScreen-DiaryListTips"
|
||||
)
|
||||
self.container = Container(
|
||||
self.diary_list, self.buttons_grid, self.tips,
|
||||
classes="DiaryListScreen-DiaryListContainer"
|
||||
)
|
||||
|
||||
def compose(self) -> ComposeResult:
|
||||
yield Header()
|
||||
yield Container(
|
||||
Static("Pilgrim", classes="app-title"),
|
||||
Label("Select a diary"),
|
||||
OptionList(id="option-list", classes="diary-options"),
|
||||
Horizontal(
|
||||
Button("New diary", id="new-diary"),
|
||||
|
||||
Button("Edit diary", id="edit-diary"),
|
||||
Button("🔄 Refresh", id="refresh-btn"),
|
||||
classes="actions-buttons",
|
||||
),
|
||||
classes="dialog-container"
|
||||
)
|
||||
yield Static(
|
||||
"Tip: use ↑↓ to navigate • ENTER to Select • "
|
||||
"TAB to alternate the fields • SHIFT + TAB to alternate back ",
|
||||
classes="tips"
|
||||
)
|
||||
yield Footer()
|
||||
yield self.header
|
||||
yield self.container
|
||||
yield self.footer
|
||||
|
||||
def on_mount(self) -> None:
|
||||
# Usa versão síncrona para o mount inicial
|
||||
self.refresh_diaries()
|
||||
self.update_buttons_state() # Atualiza estado inicial dos botões
|
||||
self.update_buttons_state()
|
||||
|
||||
def refresh_diaries(self):
|
||||
"""Versão síncrona do refresh"""
|
||||
try:
|
||||
service_manager = self.app.service_manager
|
||||
option_list = self.query_one(".diary-options")
|
||||
option_list.clear_options()
|
||||
travel_diary_service = service_manager.get_travel_diary_service()
|
||||
|
||||
# Usa método síncrono
|
||||
diaries = travel_diary_service.read_all()
|
||||
|
||||
# Salva o estado atual
|
||||
current_diary_id = None
|
||||
if (self.selected_diary_index is not None and
|
||||
self.selected_diary_index in self.diary_id_map):
|
||||
current_diary_id = self.diary_id_map[self.selected_diary_index]
|
||||
|
||||
# Limpa e reconstrói
|
||||
self.diary_list.clear_options()
|
||||
self.diary_id_map = {}
|
||||
|
||||
if not diaries:
|
||||
# Para OptionList vazio, você pode adicionar uma string simples
|
||||
option_list.add_option("[dim]Nenhum diário encontrado. Pressione 'N' para criar um novo![/dim]")
|
||||
self.diary_list.add_option("[dim]Nenhum diário encontrado. Pressione 'N' para criar um novo![/dim]")
|
||||
self.selected_diary_index = None
|
||||
else:
|
||||
for diary in diaries:
|
||||
# Adiciona cada opção como string com markup rich
|
||||
option_list.add_option(f"[b]{diary.name}[/b]\n[dim]ID: {diary.id}[/dim]")
|
||||
new_selected_index = 0
|
||||
|
||||
for index, diary in enumerate(diaries):
|
||||
self.diary_id_map[index] = diary.id
|
||||
self.diary_list.add_option(f"[b]{diary.name}[/b]\n[dim]ID: {diary.id}[/dim]")
|
||||
|
||||
# Mantém a seleção se possível
|
||||
if current_diary_id and diary.id == current_diary_id:
|
||||
new_selected_index = index
|
||||
|
||||
self.selected_diary_index = new_selected_index
|
||||
|
||||
# Atualiza o highlight
|
||||
self.set_timer(0.05, lambda: self._update_highlight(new_selected_index))
|
||||
|
||||
# Força refresh visual
|
||||
self.diary_list.refresh()
|
||||
self.update_buttons_state()
|
||||
|
||||
except Exception as e:
|
||||
self.notify("Error: " + str(e))
|
||||
self.notify(f"Erro ao carregar diários: {str(e)}")
|
||||
|
||||
def _update_highlight(self, index: int):
|
||||
"""Atualiza o highlight do OptionList"""
|
||||
try:
|
||||
if index < len(self.diary_list.options):
|
||||
self.diary_list.highlighted = index
|
||||
self.diary_list.refresh()
|
||||
except Exception as e:
|
||||
self.notify(f"Erro ao atualizar highlight: {str(e)}")
|
||||
|
||||
async def async_refresh_diaries(self):
|
||||
"""Versão assíncrona do refresh"""
|
||||
if self.is_refreshing:
|
||||
return
|
||||
|
||||
self.is_refreshing = True
|
||||
|
||||
try:
|
||||
service_manager = self.app.service_manager
|
||||
travel_diary_service = service_manager.get_travel_diary_service()
|
||||
|
||||
# Usa método assíncrono
|
||||
diaries = await travel_diary_service.async_read_all()
|
||||
|
||||
# Salva o estado atual
|
||||
current_diary_id = None
|
||||
if (self.selected_diary_index is not None and
|
||||
self.selected_diary_index in self.diary_id_map):
|
||||
current_diary_id = self.diary_id_map[self.selected_diary_index]
|
||||
|
||||
# Limpa e reconstrói
|
||||
self.diary_list.clear_options()
|
||||
self.diary_id_map = {}
|
||||
|
||||
if not diaries:
|
||||
self.diary_list.add_option("[dim]Nenhum diário encontrado. Pressione 'N' para criar um novo![/dim]")
|
||||
self.selected_diary_index = None
|
||||
else:
|
||||
new_selected_index = 0
|
||||
|
||||
for index, diary in enumerate(diaries):
|
||||
self.diary_id_map[index] = diary.id
|
||||
self.diary_list.add_option(f"[b]{diary.name}[/b]\n[dim]ID: {diary.id}[/dim]")
|
||||
|
||||
if current_diary_id and diary.id == current_diary_id:
|
||||
new_selected_index = index
|
||||
|
||||
self.selected_diary_index = new_selected_index
|
||||
self.set_timer(0.05, lambda: self._update_highlight(new_selected_index))
|
||||
|
||||
self.diary_list.refresh()
|
||||
self.update_buttons_state()
|
||||
|
||||
except Exception as e:
|
||||
self.notify(f"Erro ao carregar diários: {str(e)}")
|
||||
finally:
|
||||
self.is_refreshing = False
|
||||
|
||||
def on_option_list_option_highlighted(self, event: OptionList.OptionHighlighted) -> None:
|
||||
"""Handle quando uma opção é destacada"""
|
||||
if self.diary_id_map and event.option_index in self.diary_id_map:
|
||||
self.selected_diary_index = event.option_index
|
||||
else:
|
||||
self.selected_diary_index = None
|
||||
|
||||
self.update_buttons_state()
|
||||
|
||||
def on_option_list_option_selected(self, event: OptionList.OptionSelected) -> None:
|
||||
"""Handle quando uma opção é selecionada"""
|
||||
diaries = self.app.service_manager.get_travel_diary_service().read_all()
|
||||
|
||||
if diaries and event.option_index < len(diaries):
|
||||
if self.diary_id_map and event.option_index in self.diary_id_map:
|
||||
self.selected_diary_index = event.option_index
|
||||
selected_diary = diaries[event.option_index]
|
||||
self.notify(f"Diário selecionado: {selected_diary.name}")
|
||||
self.action_open_diary()
|
||||
else:
|
||||
# Caso seja a opção "nenhum diário encontrado"
|
||||
self.selected_diary_index = None
|
||||
|
||||
self.update_buttons_state()
|
||||
|
||||
def update_buttons_state(self):
|
||||
"""Atualiza o estado dos botões baseado na seleção"""
|
||||
edit_button = self.query_one("#edit-diary")
|
||||
|
||||
|
||||
# Só habilita os botões se há um diário selecionado
|
||||
has_selection = self.selected_diary_index is not None
|
||||
edit_button.disabled = not has_selection
|
||||
"""Atualiza o estado dos botões"""
|
||||
has_selection = (self.selected_diary_index is not None and
|
||||
self.selected_diary_index in self.diary_id_map)
|
||||
|
||||
self.edit_diary_button.disabled = not has_selection
|
||||
self.open_diary.disabled = not has_selection
|
||||
|
||||
def on_button_pressed(self, event: Button.Pressed) -> None:
|
||||
"""Handle cliques nos botões"""
|
||||
button_id = event.button.id
|
||||
|
||||
if button_id == "new-diary":
|
||||
if button_id == "new_diary":
|
||||
self.action_new_diary()
|
||||
elif button_id == "edit-diary":
|
||||
self.action_edit_diary()
|
||||
elif button_id == "refresh-btn":
|
||||
self.refresh_diaries()
|
||||
self.notify("Lista atualizada manualmente!")
|
||||
elif button_id == "edit_diary":
|
||||
self.action_edit_selected_diary()
|
||||
elif button_id == "open_diary":
|
||||
self.action_open_diary()
|
||||
|
||||
def action_new_diary(self):
|
||||
"""Ação para criar novo diário"""
|
||||
self.notify("Criando novo diário...")
|
||||
# Aqui você pode navegar para uma tela de criação de diário
|
||||
self.app.push_screen(NewDiaryModal(),self._on_new_diary_submitted)
|
||||
|
||||
def action_edit_diary(self):
|
||||
def _on_new_diary_submitted(self,result):
|
||||
self.notify(str(result))
|
||||
if result:
|
||||
self.notify(f"Creating Diary:{result}'...")
|
||||
self.call_later(self._async_create_diary,result)
|
||||
else:
|
||||
self.notify(f"Canceled...")
|
||||
|
||||
async def _async_create_diary(self,name: str):
|
||||
|
||||
try:
|
||||
service = self.app.service_manager.get_travel_diary_service()
|
||||
created_diary = await service.async_create(name)
|
||||
if created_diary:
|
||||
self.diary_id_map[created_diary.id] = created_diary.id
|
||||
await self.async_refresh_diaries()
|
||||
self.notify(f"Diary: '{name}' created!")
|
||||
else:
|
||||
self.notify("Error Creating the diary")
|
||||
except Exception as e:
|
||||
self.notify(f"Exception on creating the diary: {str(e)}")
|
||||
|
||||
|
||||
|
||||
def action_edit_selected_diary(self):
|
||||
"""Ação para editar diário selecionado"""
|
||||
if self.selected_diary_index is not None:
|
||||
diaries = self.app.service_manager.get_travel_diary_service().read_all()
|
||||
if self.selected_diary_index < len(diaries):
|
||||
selected_diary = diaries[self.selected_diary_index]
|
||||
self.notify(f"Editando diário: {selected_diary.name}")
|
||||
# Aqui você pode navegar para uma tela de edição
|
||||
# self.app.push_screen(EditDiaryScreen(diary=selected_diary))
|
||||
|
||||
def refresh_diaries(self):
|
||||
"""Atualiza a lista de diários no OptionList"""
|
||||
try:
|
||||
service_manager = self.app.service_manager
|
||||
option_list = self.query_one("#option-list") # Usando ID em vez de classe
|
||||
|
||||
# Debug
|
||||
current_count = len(option_list.options) if hasattr(option_list, 'options') else 0
|
||||
self.notify(f"OptionList atual tem {current_count} opções")
|
||||
|
||||
option_list.clear_options()
|
||||
|
||||
travel_diary_service = service_manager.get_travel_diary_service()
|
||||
diaries = travel_diary_service.read_all()
|
||||
|
||||
self.notify(f"Carregando {len(diaries)} diários do serviço")
|
||||
|
||||
if not diaries:
|
||||
option_list.add_option("[dim]Nenhum diário encontrado. Pressione 'N' para criar um novo![/dim]")
|
||||
self.selected_diary_index = None
|
||||
diary_id = self.diary_id_map.get(self.selected_diary_index)
|
||||
if diary_id:
|
||||
self.app.push_screen(
|
||||
EditDiaryModal(diary_id=diary_id),
|
||||
self._on_edited_diary_name_submitted
|
||||
)
|
||||
else:
|
||||
for diary in diaries:
|
||||
option_list.add_option(f"[b]{diary.name}[/b]\n[dim]ID: {diary.id}[/dim]")
|
||||
self.notify("Selecione um diário para editar")
|
||||
|
||||
# Valida se a seleção ainda é válida
|
||||
if (self.selected_diary_index is not None and
|
||||
self.selected_diary_index >= len(diaries)):
|
||||
self.selected_diary_index = None
|
||||
def action_open_diary(self):
|
||||
"""Ação para abrir diário selecionado"""
|
||||
if self.selected_diary_index is not None:
|
||||
diary_id = self.diary_id_map.get(self.selected_diary_index)
|
||||
self.notify(f"Abrindo diário ID: {diary_id}")
|
||||
else:
|
||||
self.notify("Selecione um diário para abrir")
|
||||
|
||||
def _on_edited_diary_name_submitted(self, result: Optional[Tuple[int, str]]) -> None:
|
||||
"""Callback após edição do diário"""
|
||||
if result:
|
||||
diary_id, name = result
|
||||
self.notify(f"Atualizando diário ID {diary_id} para '{name}'...")
|
||||
# Agenda a atualização assíncrona
|
||||
self.call_later(self._async_update_diary, diary_id, name)
|
||||
else:
|
||||
self.notify("Edição cancelada")
|
||||
|
||||
async def _async_update_diary(self, diary_id: int, name: str):
|
||||
"""Atualiza o diário de forma assíncrona"""
|
||||
try:
|
||||
service = self.app.service_manager.get_travel_diary_service()
|
||||
updated_diary = await service.async_update(diary_id, name)
|
||||
|
||||
if updated_diary:
|
||||
self.notify(f"Diário '{name}' atualizado!")
|
||||
# Força refresh após a atualização
|
||||
await self.async_refresh_diaries()
|
||||
else:
|
||||
self.notify("Erro: Diário não encontrado")
|
||||
|
||||
except Exception as e:
|
||||
self.notify("Error no refresh_diaries: " + str(e))
|
||||
self.notify(f"Erro ao atualizar: {str(e)}")
|
||||
|
||||
def action_force_refresh(self):
|
||||
"""Força refresh manual"""
|
||||
self.notify("Forçando refresh...")
|
||||
# Tenta ambas as versões
|
||||
self.refresh_diaries() # Síncrona
|
||||
self.call_later(self.async_refresh_diaries) # Assíncrona
|
||||
|
||||
def action_open_selected_diary(self):
|
||||
"""Ação do binding ENTER"""
|
||||
self.action_open_diary()
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
from textual.app import ComposeResult
|
||||
from textual.containers import Vertical, Horizontal
|
||||
from textual.screen import ModalScreen
|
||||
from textual.widgets import Label, Input, Button
|
||||
|
||||
|
||||
class EditDiaryModal(ModalScreen[tuple[int,str]]):
|
||||
BINDINGS = [
|
||||
("escape", "cancel", "Cancelar"),
|
||||
]
|
||||
|
||||
def __init__(self, diary_id: int):
|
||||
super().__init__()
|
||||
self.diary_id = diary_id
|
||||
self.current_diary_name = self.app.service_manager.get_travel_diary_service().read_by_id(self.diary_id).name
|
||||
self.name_input = Input(value=self.current_diary_name, id="edit_diary_name_input")
|
||||
|
||||
def compose(self) -> ComposeResult:
|
||||
with Vertical(id="edit_diary_dialog"):
|
||||
yield Label(f"Editar Diário: {self.current_diary_name}", classes="dialog-title")
|
||||
yield Label("Novo Nome do Diário:")
|
||||
yield self.name_input
|
||||
with Horizontal(classes="dialog-buttons"):
|
||||
yield Button("Salvar", variant="primary", id="save_diary_button")
|
||||
yield Button("Cancelar", variant="default", id="cancel_button")
|
||||
|
||||
def on_mount(self) -> None:
|
||||
"""Foca no campo de entrada e move o cursor para o final do texto."""
|
||||
self.name_input.focus()
|
||||
self.name_input.cursor_position = len(self.name_input.value)
|
||||
# REMOVIDA A LINHA QUE CAUSA O ERRO: self.name_input.select_text()
|
||||
|
||||
def on_button_pressed(self, event: Button.Pressed) -> None:
|
||||
if event.button.id == "save_diary_button":
|
||||
new_diary_name = self.name_input.value.strip()
|
||||
if new_diary_name and new_diary_name != self.current_diary_name:
|
||||
self.dismiss((self.diary_id, new_diary_name))
|
||||
elif new_diary_name == self.current_diary_name:
|
||||
self.notify("Nenhuma alteração feita.", severity="warning")
|
||||
self.dismiss(None)
|
||||
else:
|
||||
self.notify("O nome do diário não pode estar vazio.", severity="warning")
|
||||
self.name_input.focus()
|
||||
elif event.button.id == "cancel_button":
|
||||
self.dismiss(None)
|
||||
|
||||
def action_cancel(self) -> None:
|
||||
self.dismiss(None)
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
from textual.app import ComposeResult
|
||||
from textual.containers import Vertical, Horizontal
|
||||
from textual.screen import ModalScreen
|
||||
from textual.widgets import Label, Input, Button
|
||||
|
||||
class NewDiaryModal(ModalScreen[str]):
|
||||
BINDINGS = [
|
||||
("escape", "cancel", "Cancelar"),
|
||||
]
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.name_input = Input(id="NewDiaryModal-NameInput") # This ID is fine, it's specific to the input
|
||||
|
||||
def compose(self) -> ComposeResult:
|
||||
# CHANGE THIS LINE: Use the ID that matches your CSS
|
||||
with Vertical(id="new_diary_dialog"): # <--- Changed ID here to match CSS
|
||||
yield Label("Create a new diary", classes="dialog-title")
|
||||
yield Label("Diary Name:")
|
||||
yield self.name_input
|
||||
with Horizontal(classes="dialog-buttons"):
|
||||
yield Button("Create", variant="primary", id="create_diary_button")
|
||||
yield Button("Cancel", variant="default", id="cancel_button")
|
||||
|
||||
def on_mount(self):
|
||||
self.name_input.focus()
|
||||
|
||||
def on_button_pressed(self, event: Button.Pressed) -> None:
|
||||
"""Lida com os cliques dos botões."""
|
||||
if event.button.id == "create_diary_button":
|
||||
diary_name = self.name_input.value.strip()
|
||||
if diary_name:
|
||||
self.dismiss(diary_name)
|
||||
else:
|
||||
self.notify("O nome do diário não pode estar vazio.", severity="warning")
|
||||
self.name_input.focus()
|
||||
elif event.button.id == "cancel_button":
|
||||
self.dismiss("")
|
||||
|
||||
def action_cancel(self) -> None:
|
||||
"""Ação para cancelar a modal."""
|
||||
self.dismiss("")
|
||||
Loading…
Reference in New Issue